sdml_errors/diagnostics/
reporter.rs

1/*!
2This module contains the trait [`Reporter`] and common implementations.
3 */
4
5use crate::diagnostics::color::UseColor;
6use crate::diagnostics::{Diagnostic, ErrorCode, SeverityFilter};
7use crate::errors::Error;
8use crate::SourceFiles;
9use codespan_reporting::{
10    diagnostic::Severity,
11    term::{
12        emit,
13        termcolor::{ColorChoice, StandardStream, WriteColor},
14        Chars, Config,
15    },
16};
17use std::cell::RefCell;
18use std::fmt::Debug;
19use std::io::Write;
20use std::ops::{Add, AddAssign};
21use tracing::{error, info, warn};
22
23// ------------------------------------------------------------------------------------------------
24// Public Types
25// ------------------------------------------------------------------------------------------------
26
27///
28/// This trait describes a facility to report diagnostics.
29///
30pub trait Reporter: Debug {
31    ///
32    /// Emit a diagnostic, providing a mapping for source code.
33    ///
34    fn emit(&self, diagnostic: &Diagnostic, sources: &SourceFiles) -> Result<(), Error>;
35
36    fn emit_without_source(&self, diagnostic: &Diagnostic) -> Result<(), Error> {
37        self.emit(diagnostic, &SourceFiles::new())
38    }
39
40    fn counters(&self) -> ReportCounters;
41
42    fn done(&self, module_name: Option<String>) -> Result<ReportCounters, Error>;
43
44    fn log(&self, diagnostic: &Diagnostic) {
45        match diagnostic.severity {
46            Severity::Bug | Severity::Error => error!(
47                "[{}] {}",
48                diagnostic.code.as_ref().unwrap(),
49                diagnostic.message
50            ),
51            Severity::Warning => warn!(
52                "[{}] {}",
53                diagnostic.code.as_ref().unwrap(),
54                diagnostic.message
55            ),
56            Severity::Note | Severity::Help => info!(
57                "[{}] {}",
58                diagnostic.code.as_ref().unwrap(),
59                diagnostic.message
60            ),
61        }
62    }
63
64    fn severity_filter(&self) -> SeverityFilter;
65
66    fn set_severity_filter(&mut self, filter: SeverityFilter);
67
68    fn is_enabled(&self, level: Severity) -> bool {
69        match self.severity_filter() {
70            SeverityFilter::Bug => level >= Severity::Bug,
71            SeverityFilter::Error => level >= Severity::Error,
72            SeverityFilter::Warning => level >= Severity::Warning,
73            SeverityFilter::Note => level >= Severity::Note,
74            SeverityFilter::Help => level >= Severity::Help,
75            SeverityFilter::None => false,
76        }
77    }
78}
79
80#[derive(Clone, Copy, Debug, Default)]
81pub struct ReportCounters {
82    bugs: u32,
83    errors: u32,
84    warnings: u32,
85    info: u32,
86}
87
88#[derive(Debug)]
89pub struct StandardStreamReporter {
90    stream: StandardStream,
91    filter: SeverityFilter,
92    counters: RefCell<ReportCounters>,
93    config: Config,
94}
95
96#[derive(Debug)]
97pub struct CompactStreamReporter {
98    stream: StandardStream,
99    filter: SeverityFilter,
100    counters: RefCell<ReportCounters>,
101}
102
103#[derive(Debug, Default)]
104pub struct BailoutReporter {
105    filter: SeverityFilter,
106    counters: RefCell<ReportCounters>,
107}
108
109// ------------------------------------------------------------------------------------------------
110// Implementations
111// ------------------------------------------------------------------------------------------------
112
113impl PartialEq<Severity> for SeverityFilter {
114    fn eq(&self, other: &Severity) -> bool {
115        matches!(
116            (self, other),
117            (SeverityFilter::Bug, Severity::Bug)
118                | (SeverityFilter::Error, Severity::Error)
119                | (SeverityFilter::Warning, Severity::Warning)
120                | (SeverityFilter::Note, Severity::Note)
121                | (SeverityFilter::Help, Severity::Help)
122        )
123    }
124}
125
126// ------------------------------------------------------------------------------------------------
127
128impl From<ErrorCode> for Diagnostic {
129    fn from(code: ErrorCode) -> Self {
130        Self::new(code.severity())
131            .with_code(code.to_string())
132            .with_message(code.message().to_string())
133    }
134}
135
136// ------------------------------------------------------------------------------------------------
137
138impl Add for ReportCounters {
139    type Output = ReportCounters;
140
141    fn add(self, rhs: Self) -> Self::Output {
142        Self {
143            bugs: self.bugs + rhs.bugs,
144            errors: self.errors + rhs.errors,
145            warnings: self.warnings + rhs.warnings,
146            info: self.info,
147        }
148    }
149}
150
151impl AddAssign for ReportCounters {
152    fn add_assign(&mut self, rhs: Self) {
153        self.bugs += rhs.bugs;
154        self.errors += rhs.errors;
155        self.warnings += rhs.warnings;
156        self.info += rhs.info;
157    }
158}
159
160impl ReportCounters {
161    #[inline(always)]
162    fn report(&mut self, severity: Severity) {
163        match severity {
164            Severity::Bug => self.bugs += 1,
165            Severity::Error => self.errors += 1,
166            Severity::Warning => self.warnings += 1,
167            Severity::Note => self.info += 1,
168            Severity::Help => self.info += 1,
169        }
170    }
171
172    #[inline(always)]
173    pub fn bugs(&self) -> u32 {
174        self.bugs
175    }
176
177    #[inline(always)]
178    pub fn errors(&self) -> u32 {
179        self.errors
180    }
181
182    #[inline(always)]
183    pub fn warnings(&self) -> u32 {
184        self.warnings
185    }
186
187    #[inline(always)]
188    pub fn info(&self) -> u32 {
189        self.info
190    }
191
192    #[inline(always)]
193    pub fn total_without_info(&self) -> u64 {
194        (self.bugs + self.errors + self.warnings) as u64
195    }
196
197    #[inline(always)]
198    pub fn total(&self) -> u64 {
199        (self.bugs + self.errors + self.warnings + self.info) as u64
200    }
201}
202
203// ------------------------------------------------------------------------------------------------
204
205impl Default for StandardStreamReporter {
206    fn default() -> Self {
207        Self::stderr(UseColor::from_env().into())
208    }
209}
210
211impl Reporter for StandardStreamReporter {
212    fn emit(&self, diagnostic: &Diagnostic, sources: &SourceFiles) -> Result<(), Error> {
213        if self.is_enabled(diagnostic.severity) {
214            self.log(diagnostic);
215            let mut counters = self.counters.borrow_mut();
216            counters.report(diagnostic.severity);
217            Ok(emit(
218                &mut self.stream.lock(),
219                &self.config,
220                sources,
221                diagnostic,
222            )?)
223        } else {
224            Ok(())
225        }
226    }
227
228    fn counters(&self) -> ReportCounters {
229        *self.counters.borrow()
230    }
231
232    fn done(&self, module_name: Option<String>) -> Result<ReportCounters, Error> {
233        self.done_stats(module_name)?;
234        let old_counters = self.counters.replace(ReportCounters::default());
235        Ok(old_counters)
236    }
237
238    fn severity_filter(&self) -> SeverityFilter {
239        self.filter
240    }
241
242    fn set_severity_filter(&mut self, filter: SeverityFilter) {
243        self.filter = filter;
244    }
245}
246
247impl StandardStreamReporter {
248    pub fn stderr(color_choice: ColorChoice) -> Self {
249        Self {
250            stream: StandardStream::stderr(color_choice),
251            filter: Default::default(),
252            config: Self::default_config(),
253            counters: Default::default(),
254        }
255    }
256
257    pub fn stdout(color_choice: ColorChoice) -> Self {
258        Self {
259            stream: StandardStream::stdout(color_choice),
260            filter: Default::default(),
261            config: Self::default_config(),
262            counters: Default::default(),
263        }
264    }
265
266    pub fn with_severity_filter(self, filter: SeverityFilter) -> Self {
267        Self { filter, ..self }
268    }
269
270    fn default_config() -> Config {
271        Config {
272            chars: Chars::box_drawing(),
273            ..Default::default()
274        }
275    }
276
277    fn done_stats(&self, module_name: Option<String>) -> Result<(), Error> {
278        let counters = self.counters.borrow();
279        if counters.total() > 0 {
280            let severity = if counters.bugs > 0 {
281                Severity::Bug
282            } else if counters.errors > 0 {
283                Severity::Error
284            } else if counters.warnings > 0 {
285                Severity::Warning
286            } else if counters.info > 0 {
287                Severity::Note
288            } else {
289                unreachable!();
290            };
291
292            let mut writer = self.stream.lock();
293
294            writer.set_color(self.config.styles.header(severity))?;
295            writer.write_all(
296                match severity {
297                    Severity::Bug => i18n!("word_bug"),
298                    Severity::Error => i18n!("word_error"),
299                    Severity::Warning => i18n!("word_warning"),
300                    Severity::Note => i18n!("word_note"),
301                    Severity::Help => i18n!("word_help"),
302                }
303                .as_bytes(),
304            )?;
305            writer.reset()?;
306            writer.write_all(b": ")?;
307            writer.write_all(
308                format!(
309                    "{} ",
310                    if let Some(name) = module_name {
311                        i18n!("lbl_module_name_short", name = name)
312                    } else {
313                        i18n!("lbl_parser")
314                    }
315                )
316                .as_bytes(),
317            )?;
318            let mut count_strings: Vec<String> = Default::default();
319            if counters.bugs > 0 {
320                count_strings.push(i18n!("count_of_bugs", count = counters.bugs));
321            }
322            if counters.errors > 0 {
323                count_strings.push(i18n!("count_of_errors", count = counters.errors));
324            }
325            if counters.warnings > 0 {
326                count_strings.push(i18n!("count_of_warnings", count = counters.warnings));
327            }
328            if counters.info > 0 {
329                count_strings.push(i18n!("count_of_informational", count = counters.info));
330            }
331            writer.write_all(
332                i18n!(
333                    "counts_generated_summary",
334                    counts = count_strings.join(", ")
335                )
336                .as_bytes(),
337            )?;
338            writer.write_all(b"\n")?;
339        }
340        Ok(())
341    }
342}
343
344// ------------------------------------------------------------------------------------------------
345
346impl Default for CompactStreamReporter {
347    fn default() -> Self {
348        Self {
349            stream: StandardStream::stdout(UseColor::from_env().into()),
350            filter: Default::default(),
351            counters: Default::default(),
352        }
353    }
354}
355
356impl Reporter for CompactStreamReporter {
357    fn emit(&self, diagnostic: &Diagnostic, sources: &SourceFiles) -> Result<(), Error> {
358        use codespan_reporting::files::Files;
359        if self.is_enabled(diagnostic.severity) {
360            self.log(diagnostic);
361            let mut counters = self.counters.borrow_mut();
362            counters.report(diagnostic.severity);
363            let mut stream = self.stream.lock();
364            let (file_name, start, end) = if let Some(label) = diagnostic.labels.first() {
365                let file_id = label.file_id;
366                let start = sources.location(file_id, label.range.start)?;
367                let end = sources.location(file_id, label.range.end)?;
368                (
369                    sources.name(file_id)?,
370                    (start.line_number, start.column_number),
371                    (end.line_number, end.column_number),
372                )
373            } else {
374                (String::new(), (0, 0), (0, 0))
375            };
376            stream.write_all(
377                format!(
378                    "{},{},{},{},{},{},{},{}\n",
379                    match diagnostic.severity {
380                        Severity::Bug => i18n!("word_bug"),
381                        Severity::Error => i18n!("word_error"),
382                        Severity::Warning => i18n!("word_warning"),
383                        Severity::Note => i18n!("word_note"),
384                        Severity::Help => i18n!("word_help"),
385                    },
386                    file_name,
387                    start.0,
388                    start.1,
389                    end.0,
390                    end.1,
391                    diagnostic.code.as_ref().unwrap(),
392                    diagnostic.message
393                )
394                .as_bytes(),
395            )?;
396        }
397        Ok(())
398    }
399
400    fn counters(&self) -> ReportCounters {
401        *self.counters.borrow()
402    }
403
404    fn done(&self, _: Option<String>) -> Result<ReportCounters, Error> {
405        let old_counters = self.counters.replace(ReportCounters::default());
406        Ok(old_counters)
407    }
408
409    fn severity_filter(&self) -> SeverityFilter {
410        self.filter
411    }
412
413    fn set_severity_filter(&mut self, filter: SeverityFilter) {
414        self.filter = filter;
415    }
416}
417
418impl CompactStreamReporter {
419    pub fn with_severity_filter(self, filter: SeverityFilter) -> Self {
420        Self { filter, ..self }
421    }
422}
423
424// ------------------------------------------------------------------------------------------------
425
426impl Reporter for BailoutReporter {
427    fn emit(&self, diagnostic: &Diagnostic, _: &SourceFiles) -> Result<(), Error> {
428        if self.is_enabled(diagnostic.severity) {
429            self.log(diagnostic);
430            let mut counters = self.counters.borrow_mut();
431            counters.report(diagnostic.severity);
432            Err(diagnostic.clone().into())
433        } else {
434            Ok(())
435        }
436    }
437
438    fn counters(&self) -> ReportCounters {
439        *self.counters.borrow()
440    }
441
442    fn done(&self, _: Option<String>) -> Result<ReportCounters, Error> {
443        let old_counters = self.counters.replace(ReportCounters::default());
444        Ok(old_counters)
445    }
446
447    fn severity_filter(&self) -> SeverityFilter {
448        self.filter
449    }
450
451    fn set_severity_filter(&mut self, filter: SeverityFilter) {
452        self.filter = filter;
453    }
454}