use anyhow::{anyhow, Context, Result}; use clang::{ source::{SourceLocation as ClangSourceLocation, SourceRange as ClangSourceRange}, Entity as ClangEntity, }; use codemap::File; use codemap_diagnostic::{Diagnostic, Level, SpanLabel}; use lazy_static::lazy_static; use std::collections::{hash_map, HashMap}; use std::path::PathBuf; use std::sync::{Arc, RwLock}; lazy_static! { pub(super) static ref CODEMAP: RwLock = RwLock::new(CodeMap::new()); } #[derive(Default)] pub(super) struct CodeMap { files: HashMap>, raw_codemap: codemap::CodeMap, } impl CodeMap { pub fn new() -> Self { Default::default() } pub fn span_from_entity(&mut self, entity: ClangEntity) -> Result { self.span_from_libclang_range(entity.get_range().ok_or_else(|| { anyhow!("Trying to build a diagnostic from an entity that doesn't have a range") })?) } pub fn span_from_libclang_range(&mut self, range: ClangSourceRange) -> Result { let start_file_location = range.get_start().get_file_location(); let file_name = start_file_location .file .ok_or_else(|| { anyhow!("Trying to build a diagnostic from an entity that isn't located in a file") })? .get_path(); let file = self.get_file(file_name)?; let begin = start_file_location.offset as u64; let end = range.get_end().get_file_location().offset as u64; Ok(file.span.subspan(begin, end)) } pub fn span_from_libclang_location( &mut self, location: ClangSourceLocation, ) -> Result { let file_location = location.get_file_location(); let file_name = file_location .file .ok_or_else(|| { anyhow!("Trying to build a diagnostic from an entity that isn't located in a file") })? .get_path(); let file = self.get_file(file_name)?; let offset = file_location.offset as u64; Ok(file.span.subspan(offset, offset)) } pub fn get_file(&mut self, file_name: PathBuf) -> Result<&mut Arc> { Ok(match self.files.entry(file_name.clone()) { hash_map::Entry::Occupied(file) => file.into_mut(), hash_map::Entry::Vacant(entry) => { let file = self.raw_codemap.add_file( file_name .to_str() .context("Filename is not valid UTF-8")? .to_owned(), std::fs::read_to_string(&file_name) .with_context(|| format!("Cannot readfile: {:?}", file_name))?, ); entry.insert(file) } }) } pub fn emitter(&self) -> codemap_diagnostic::Emitter { use codemap_diagnostic::{ColorConfig, Emitter}; Emitter::stderr(ColorConfig::Auto, Some(&self.raw_codemap)) } } #[derive(Debug, Clone)] pub(super) struct DiagnosticBuilder { level: Level, message: String, code: Option, spans: Vec, } impl DiagnosticBuilder { pub fn new(level: Level, message: String) -> Self { DiagnosticBuilder { level, message, code: None, spans: vec![], } } /* pub fn error(message: String) -> Self { Self::new(Level::Error, message) } pub fn warning(message: String) -> Self { Self::new(Level::Warning, message) } pub fn note(message: String) -> Self { Self::new(Level::Note, message) } pub fn help(message: String) -> Self { Self::new(Level::Help, message) } pub fn bug(message: String) -> Self { Self::new(Level::Bug, message) } pub fn level(&mut self, level: Level) -> &mut Self { self.level = level; self } pub fn message(&mut self, message: String) -> &mut Self { self.message = message; self } pub fn code(&mut self, code: String) -> &mut Self { self.code = Some(code); self } */ pub fn span(&mut self, span: SpanLabel) -> &mut Self { self.spans.push(span); self } pub fn build(self) -> Diagnostic { Diagnostic { level: self.level, code: self.code, message: self.message, spans: self.spans, } } } fn clang_fixit_to_label( fixit: clang::diagnostic::FixIt, ) -> Result { use clang::diagnostic::FixIt; use codemap_diagnostic::SpanStyle; let mut codemap = CODEMAP .write() .expect("Failed to lock the codemap for writing"); Ok(match fixit { FixIt::Deletion(range) => { let span = codemap.span_from_libclang_range(range)?; SpanLabel { span, label: Some(String::from("help: try deleting this")), style: SpanStyle::Secondary, } } FixIt::Insertion(location, text) => { let span = codemap.span_from_libclang_location(location)?; SpanLabel { span, label: Some(format!("help: try inserting {:?}", text)), style: SpanStyle::Secondary, } } FixIt::Replacement(range, text) => { let span = codemap.span_from_libclang_range(range)?; SpanLabel { span, label: Some(format!("help: try replacing this with {:?}", text)), style: SpanStyle::Secondary, } } }) } pub(super) fn clang_diagnostic_to_diagnostics( diagnostic: clang::diagnostic::Diagnostic, ) -> Vec { use clang::diagnostic::Severity; use codemap_diagnostic::SpanStyle; let level = match diagnostic.get_severity() { Severity::Error | Severity::Fatal => Level::Error, Severity::Warning => Level::Warning, Severity::Note => Level::Note, Severity::Ignored => return vec![], }; let mut diag = DiagnosticBuilder::new(level, diagnostic.get_text()); { let mut codemap = CODEMAP .write() .expect("Failed to lock the codemap for writing"); if let Ok(span) = codemap.span_from_libclang_location(diagnostic.get_location()) { diag.span(SpanLabel { span, label: None, style: SpanStyle::Primary, }); }; } for range in diagnostic.get_ranges() { let mut codemap = CODEMAP .write() .expect("Failed to lock the codemap for writing"); let span = match codemap.span_from_libclang_range(range) { Ok(span) => span, Err(_) => continue, }; diag.span(SpanLabel { span, label: None, style: SpanStyle::Primary, }); } let mut child_diags = diagnostic .get_children() .into_iter() .flat_map(|diag| clang_diagnostic_to_diagnostics(diag)) .collect(); for fixit_label in diagnostic .get_fix_its() .into_iter() .filter_map(|fixit| clang_fixit_to_label(fixit).ok()) { diag.span(fixit_label); } let mut diags = vec![diag.build()]; diags.append(&mut child_diags); diags } pub(super) fn emit(diagnostics: &[Diagnostic]) { CODEMAP .read() .expect("Failed to lock the codemap for reading") .emitter() .emit(diagnostics) } pub(super) fn log(level: Level, message: String, entity: ClangEntity) { use codemap_diagnostic::SpanStyle; let mut diag = DiagnosticBuilder::new(level, message); if let Ok(span) = CODEMAP .write() .expect("Failed to lock the codemap for writing") .span_from_entity(entity) { diag.span(SpanLabel { span, label: None, style: SpanStyle::Primary, }); }; emit(&[diag.build()]); } pub(super) fn error(message: String, entity: ClangEntity) { log(Level::Error, message, entity) } /* pub(super) fn warn(message: String, entity: ClangEntity) { log(Level::Warning, message, entity) } pub(super) fn note(message: String, entity: ClangEntity) { log(Level::Note, message, entity) } pub(super) fn help(message: String, entity: ClangEntity) { log(Level::Help, message, entity) } pub(super) fn bug(message: String, entity: ClangEntity) { log(Level::Bug, message, entity) } */