Skip to main content

plc_diagnostics/
diagnostician.rs

1use rustc_hash::FxHashMap;
2
3use crate::{
4    diagnostics::{
5        diagnostics_registry::{DiagnosticsConfiguration, DiagnosticsRegistry},
6        Diagnostic, Severity,
7    },
8    reporter::{
9        clang::ClangFormatDiagnosticReporter, codespan::CodeSpanDiagnosticReporter,
10        null::NullDiagnosticReporter, DiagnosticReporter, ResolvedDiagnostics, ResolvedLocation,
11    },
12};
13
14/// the Diagnostician handle's Diangostics with the help of a
15/// assessor and a reporter
16pub struct Diagnostician {
17    reporter: Box<dyn DiagnosticReporter>,
18    assessor: Box<dyn DiagnosticAssessor>,
19    filename_fileid_mapping: FxHashMap<String, usize>,
20}
21
22impl Diagnostician {
23    /// registers the given source-code at the diagnostician, so it can
24    /// preview errors in the source
25    /// returns the id to use to reference the given file
26    pub fn register_file(&mut self, id: String, src: String) -> usize {
27        let handle = self.reporter.register(id.clone(), src);
28        self.filename_fileid_mapping.insert(id, handle);
29        handle
30    }
31
32    fn get_file_handle(&self, file_name: Option<&str>) -> Option<usize> {
33        file_name.and_then(|it| self.filename_fileid_mapping.get(it).cloned())
34    }
35
36    /// Assess and reports the given diagnostics.
37    pub fn handle(&mut self, diagnostics: &[Diagnostic]) -> Severity {
38        let resolved_diagnostics = diagnostics
39            .iter()
40            .fold(vec![], |mut acc, d| {
41                acc.push(d);
42                acc.extend(d.get_sub_diagnostics());
43                acc
44            })
45            .iter()
46            .map(|d| ResolvedDiagnostics {
47                code: d.get_error_code().to_string(),
48                message: d.get_message().to_string(),
49                severity: self.assess(d),
50                main_location: ResolvedLocation {
51                    file_handle: self
52                        .get_file_handle(d.get_location().get_file_name().or(Some("<internal>")))
53                        .unwrap_or(usize::MAX),
54                    span: d.get_location().get_span().clone(),
55                },
56                additional_locations: d.get_secondary_locations().map(|it| {
57                    it.iter()
58                        .map(|l| ResolvedLocation {
59                            file_handle: self
60                                .get_file_handle(l.get_file_name().or(Some("<internal>")))
61                                .unwrap_or(usize::MAX),
62                            span: l.get_span().clone(),
63                        })
64                        .collect()
65                }),
66            })
67            .collect::<Vec<_>>();
68
69        self.report(resolved_diagnostics.as_slice());
70
71        resolved_diagnostics.iter().map(|it| it.severity).max().unwrap_or_default()
72    }
73
74    /// Creates a null-diagnostician that does not report diagnostics
75    pub fn null_diagnostician() -> Diagnostician {
76        Diagnostician {
77            assessor: Box::<DiagnosticsRegistry>::default(),
78            reporter: Box::<NullDiagnosticReporter>::default(),
79            filename_fileid_mapping: FxHashMap::default(),
80        }
81    }
82
83    /// Creates a buffered-diagnostician that saves its reports in a buffer
84    pub fn buffered() -> Diagnostician {
85        Diagnostician {
86            assessor: Box::<DiagnosticsRegistry>::default(),
87            reporter: Box::new(CodeSpanDiagnosticReporter::buffered()),
88            filename_fileid_mapping: FxHashMap::default(),
89        }
90    }
91
92    /// Creates a clang-format-diagnostician that reports diagnostics in clang format
93    pub fn clang_format_diagnostician() -> Diagnostician {
94        Diagnostician {
95            reporter: Box::<ClangFormatDiagnosticReporter>::default(),
96            assessor: Box::<DiagnosticsRegistry>::default(),
97            filename_fileid_mapping: FxHashMap::default(),
98        }
99    }
100
101    pub fn with_configuration(self, configuration: DiagnosticsConfiguration) -> Self {
102        let mut res = self;
103        let registry = DiagnosticsRegistry::default().with_configuration(configuration);
104        res.assessor = Box::new(registry);
105        res
106    }
107
108    /// Explain the error with the given code by consulting the diagnostics registry
109    pub fn explain(&self, error: &str) -> String {
110        self.assessor.explain(error)
111    }
112    pub fn get_diagnostic_configuration(&self) -> String {
113        self.assessor.get_diagnostic_configuration()
114    }
115}
116
117impl DiagnosticReporter for Diagnostician {
118    fn report(&mut self, diagnostics: &[ResolvedDiagnostics]) {
119        //delegate to reporter
120        self.reporter.report(diagnostics);
121    }
122
123    fn register(&mut self, path: String, src: String) -> usize {
124        //delegate to reporter
125        self.reporter.register(path, src)
126    }
127
128    fn buffer(&self) -> Option<String> {
129        self.reporter.buffer()
130    }
131}
132
133impl DiagnosticAssessor for Diagnostician {
134    fn assess(&self, d: &Diagnostic) -> Severity {
135        //delegate to assesor
136        self.assessor.assess(d)
137    }
138
139    fn explain(&self, _error: &str) -> String {
140        unimplemented!("Error explanation is not supported on this diagnostian")
141    }
142
143    fn get_diagnostic_configuration(&self) -> String {
144        unimplemented!("Diagnostic configuration is not supported on this diagnostian")
145    }
146}
147
148//This clippy lint is wrong her because the trait is expecting dyn
149#[allow(clippy::derivable_impls)]
150impl Default for Diagnostician {
151    fn default() -> Self {
152        Self {
153            reporter: Box::<CodeSpanDiagnosticReporter>::default(),
154            assessor: Box::<DiagnosticsRegistry>::default(),
155            filename_fileid_mapping: FxHashMap::default(),
156        }
157    }
158}
159
160/// the assessor determins the severity of a diagnostic
161/// this trait allows for different implementations for different usecases
162/// (e.g. default, compiler-settings, tests)
163pub trait DiagnosticAssessor {
164    /// determines the severity of the given diagnostic
165    fn assess(&self, d: &Diagnostic) -> Severity;
166    //TODO should these be results
167    /// Explains the given error based on the diagnostician information
168    fn explain(&self, error: &str) -> String;
169    /// Returns a serialized version of the diagnostics configuration this diagnostician will use
170    /// to assess errors
171    fn get_diagnostic_configuration(&self) -> String;
172}
173
174impl std::fmt::Display for Severity {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        let severity = match self {
177            Severity::Error => "error",
178            Severity::Warning => "warning",
179            Severity::Info => "info",
180            Severity::Ignore => "ignore",
181        };
182        write!(f, "{severity}")
183    }
184}