Skip to main content

microcad_lang_base/diag/
diag_handler.rs

1// Copyright © 2024-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::{GetSourceStrByHash, diag::*};
5use std::io::IsTerminal;
6
7/// Handler for diagnostics.
8#[derive(Default)]
9pub struct DiagHandler {
10    /// The list of diagnostics per source file.
11    pub diag_list: DiagList,
12    /// The number of overall errors in the evaluation process.
13    error_count: u32,
14    /// The number of overall errors in the evaluation process.
15    warning_count: u32,
16    /// The maximum number of collected errors until abort
17    /// (`0` means unlimited number of errors).
18    error_limit: Option<u32>,
19    /// `true` after the first time error limit was reached
20    error_limit_reached: bool,
21    /// Treat warnings as errors if `true`.
22    warnings_as_errors: bool,
23    /// Line offset for error and warning messages.
24    line_offset: usize,
25    /// Diagnostic rendering options
26    pub render_options: DiagRenderOptions,
27}
28
29/// Options that control the rendering of diagnostics
30#[derive(Debug)]
31pub struct DiagRenderOptions {
32    /// Render diagnostic with colors
33    pub color: bool,
34    /// Render diagnostic with unicode characters
35    pub unicode: bool,
36}
37
38impl Default for DiagRenderOptions {
39    fn default() -> Self {
40        DiagRenderOptions {
41            color: std::env::var("NO_COLOR").as_deref().unwrap_or("0") == "0",
42            unicode: std::io::stdout().is_terminal() && std::io::stderr().is_terminal(),
43        }
44    }
45}
46
47impl DiagRenderOptions {
48    /// Get the miette theme for the options
49    pub fn theme(&self) -> miette::GraphicalTheme {
50        match (self.unicode, self.color) {
51            (true, true) => miette::GraphicalTheme::unicode(),
52            (true, false) => miette::GraphicalTheme::unicode_nocolor(),
53            (false, true) => miette::GraphicalTheme::ascii(),
54            (false, false) => miette::GraphicalTheme::none(),
55        }
56    }
57}
58
59/// Handler for diagnostics.
60impl DiagHandler {
61    /// Create new diag handler.
62    pub fn new(line_offset: usize) -> Self {
63        Self {
64            line_offset,
65            ..Default::default()
66        }
67    }
68
69    /// Pretty print all errors of all files.
70    pub fn pretty_print(
71        &self,
72        f: &mut dyn std::fmt::Write,
73        source_by_hash: &impl GetSourceStrByHash,
74    ) -> std::fmt::Result {
75        self.diag_list
76            .pretty_print(f, source_by_hash, self.line_offset, &self.render_options)
77    }
78
79    /// Return overall number of occurred errors.
80    pub fn warning_count(&self) -> u32 {
81        self.warning_count
82    }
83
84    /// Return overall number of occurred errors.
85    pub fn error_count(&self) -> u32 {
86        self.error_count
87    }
88
89    /// return lines with errors
90    pub fn error_lines(&self) -> std::collections::HashSet<usize> {
91        self.diag_list
92            .iter()
93            .filter_map(|d| {
94                if d.level() == Level::Error {
95                    d.line().map(|line| line + self.line_offset)
96                } else {
97                    None
98                }
99            })
100            .collect()
101    }
102
103    /// return lines with warnings
104    pub fn warning_lines(&self) -> std::collections::HashSet<usize> {
105        self.diag_list
106            .iter()
107            .filter_map(|d| {
108                if d.level() == Level::Warning {
109                    d.line().map(|line| line + self.line_offset)
110                } else {
111                    None
112                }
113            })
114            .collect()
115    }
116
117    /// Clear all errors and warnings
118    pub fn clear(&mut self) {
119        self.diag_list.clear();
120        self.error_count = 0;
121        self.warning_count = 0;
122    }
123}
124
125impl PushDiag for DiagHandler {
126    fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()> {
127        if let Some(error_limit) = self.error_limit {
128            if self.error_count >= error_limit && !self.error_limit_reached {
129                self.error(&SrcRef(None), DiagError::ErrorLimitReached(error_limit))?;
130                self.error_limit_reached = true;
131            }
132            return Err(DiagError::ErrorLimitReached(error_limit));
133        }
134
135        match &diag {
136            Diagnostic::Error(_) => {
137                self.error_count += 1;
138            }
139            Diagnostic::Warning(_) => {
140                if self.warnings_as_errors {
141                    self.error_count += 1;
142                } else {
143                    self.warning_count += 1;
144                }
145            }
146            _ => (),
147        }
148
149        self.diag_list.push_diag(diag)
150    }
151}