#![deny(clippy::all)] #![allow(unused)] use napi::bindgen_prelude::*; use ropey::Rope; use std::{collections::HashMap, fs::File, io::Write}; use svg::emit_svg; mod svg; use codespan_reporting::{ diagnostic::{self, Diagnostic as RDiagnostic, Label, LabelStyle, Severity as RSeverity}, files::{SimpleFile, SimpleFiles}, term::{ self, termcolor::{ColorChoice, StandardStream}, }, }; use napi_derive::napi; #[cfg(all( any(windows, unix), target_arch = "x86_64", not(target_env = "musl"), not(debug_assertions) ))] #[global_allocator] static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; #[derive(Clone)] #[napi(object)] pub struct LabelInfo { pub message: String, pub start: u32, pub end: u32, } #[napi] pub fn create_label_info(start: u32, end: u32, message: String) -> LabelInfo { LabelInfo { message, start, end, } } #[napi] pub enum DiagnosticLabelStyle { Primary, Secondary, } impl Into for DiagnosticLabelStyle { fn into(self) -> LabelStyle { match self { DiagnosticLabelStyle::Primary => LabelStyle::Primary, DiagnosticLabelStyle::Secondary => LabelStyle::Secondary, } } } #[derive(Clone)] #[napi(object)] /// a wrapper of `codespan_reporting::diagnostic::Label` pub struct DiagnosticLabel { pub style: DiagnosticLabelStyle, pub file_id: u32, pub info: LabelInfo, } #[napi] pub fn primary_diagnostic_label(file_id: u32, info: LabelInfo) -> DiagnosticLabel { DiagnosticLabel { file_id, style: DiagnosticLabelStyle::Primary, info, } } #[napi] pub fn secondary_diagnostic_label(file_id: u32, info: LabelInfo) -> DiagnosticLabel { DiagnosticLabel { file_id, style: DiagnosticLabelStyle::Secondary, info, } } impl Into> for DiagnosticLabel { fn into(self) -> Label { Label { style: self.style.into(), file_id: self.file_id as usize, range: self.info.start as usize..self.info.end as usize, message: self.info.message, } } } #[napi] pub struct FileMap { files: SimpleFiles, id_map: HashMap, } #[napi] impl FileMap { #[napi(constructor)] pub fn new() -> Self { FileMap { id_map: HashMap::new(), files: SimpleFiles::new(), } } #[napi] pub fn get_file_id(&self, file_name: String) -> i32 { match self.id_map.get(&file_name) { Some(v) => *v as i32, None => -1, } } #[napi] pub fn add_file(&mut self, file_name: String, source_file: String) { let id = self.files.add(file_name.clone(), source_file); self.id_map.insert(file_name, id); } pub fn get_file(&self) -> Option { let res = self.files.get(0).ok()?; Some(res.source().to_string()) } } #[napi] pub enum Severity { Bug, Error, Warning, Note, Help, } impl Into for Severity { fn into(self) -> RSeverity { match self { Severity::Bug => RSeverity::Bug, Severity::Error => RSeverity::Error, Severity::Warning => RSeverity::Warning, Severity::Note => RSeverity::Note, Severity::Help => RSeverity::Help, } } } #[derive(Clone)] #[napi] struct Diagnostic { severity: Severity, code: Option, message: String, labels: Vec, notes: Vec, } impl Into> for Diagnostic { fn into(mut self) -> RDiagnostic { let mut s = RDiagnostic::new(self.severity.into()); s.code = self.code; s.message = self.message; let labels = std::mem::take(&mut self.labels) .into_iter() .map(|l| l.into()) .collect::>>(); s.labels = labels; s.notes = self.notes; s } } #[napi] impl Diagnostic { #[napi(factory)] pub fn error() -> Self { Self { severity: Severity::Error, code: None, message: "".to_string(), labels: vec![], notes: vec![], } } #[napi(factory)] pub fn bug() -> Self { Self { severity: Severity::Bug, code: None, message: "".to_string(), labels: vec![], notes: vec![], } } #[napi(factory)] pub fn warning() -> Self { Self { severity: Severity::Warning, code: None, message: "".to_string(), labels: vec![], notes: vec![], } } #[napi(factory)] pub fn help() -> Self { Self { severity: Severity::Help, code: None, message: "".to_string(), labels: vec![], notes: vec![], } } #[napi(factory)] pub fn note() -> Self { Self { severity: Severity::Note, code: None, message: "".to_string(), labels: vec![], notes: vec![], } } #[napi] pub fn with_message(&mut self, message: String) { self.message = message; } #[napi] pub fn with_code(&mut self, code: String) { self.code = Some(code); } #[napi] pub fn with_labels(&mut self, labels: Vec) { self.labels = labels; } #[napi] pub fn with_notes(&mut self, notes: Vec) { self.notes = notes; } #[napi] pub fn emit_std(&mut self, file_map: &FileMap) { let writer = StandardStream::stderr(ColorChoice::Always); let config = codespan_reporting::term::Config::default(); let severity = self.severity; let mut diagnostic: RDiagnostic = self.clone().into(); // if let Some(code) = self.code.clone() { // diagnostic = diagnostic.with_code(code); // } // println!("{:?}", diagnostic); term::emit(&mut writer.lock(), &config, &file_map.files, &diagnostic).unwrap(); } #[napi] pub fn emit_svg(&mut self, file_map: &FileMap, path: String) { let severity = self.severity; let mut diagnostic: RDiagnostic = self.clone().into(); let svg = emit_svg(&file_map.files, &diagnostic); let mut file = File::create(path).unwrap(); file.write(svg.as_bytes()).unwrap(); } } #[napi] pub fn emit_error( file_name: String, source_file: String, labels: Vec, error_message: Option, ) { let mut files = SimpleFiles::new(); let error_message = error_message.unwrap_or("Error occurred".to_string()); let file_id = files.add(file_name, source_file); let diagnostic = RDiagnostic::error() .with_message(error_message) // .with_code("E0308") .with_labels( labels .into_iter() .map(|l| { Label::primary(file_id, l.start as usize..l.end as usize) .with_message(l.message) }) .collect(), ); // We now set up the writer and configuration, and then finally render the // diagnostic to standard error. let writer = StandardStream::stderr(ColorChoice::Always); let config = codespan_reporting::term::Config::default(); term::emit(&mut writer.lock(), &config, &files, &diagnostic).unwrap(); } #[napi] /// convert zero based line column position into zero based offset pub fn position_to_offset(source: String, line: u32, column: u32) -> Option { let rope: Rope = ropey::Rope::from_str(&source); let b = rope.try_line_to_byte(line as usize).ok()?; Some(b as u32 + column) }