From ab5a6da519f91faa1ba5aa281112e547fcd08b88 Mon Sep 17 00:00:00 2001 From: Minijackson Date: Thu, 23 Jan 2020 20:58:59 +0100 Subject: clang-parser: centralize diagnostic building/reporting --- src/parser/clang/diagnostics.rs | 313 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 src/parser/clang/diagnostics.rs (limited to 'src/parser/clang/diagnostics.rs') diff --git a/src/parser/clang/diagnostics.rs b/src/parser/clang/diagnostics.rs new file mode 100644 index 0000000..7aa43be --- /dev/null +++ b/src/parser/clang/diagnostics.rs @@ -0,0 +1,313 @@ +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) +} +*/ -- cgit v1.2.3