Skip to main content

hermes_support/
diag.rs

1//! Diagnostic types, handler trait, collecting handler, output options, and
2//! warning categories. Port of `SourceErrorManager` diagnostic infrastructure.
3
4use crate::location::SourceCoords;
5
6/// Kind of diagnostic. Port of `SourceErrorManager::DiagKind`.
7#[derive(Copy, Clone, PartialEq, Eq, Debug)]
8pub enum DiagKind {
9    Error,
10    Warning,
11    Note,
12}
13
14/// Subsystem that produced a message. Port of `Subsystem`.
15#[derive(Copy, Clone, PartialEq, Eq, Debug)]
16pub enum Subsystem {
17    /// No specific system provided.
18    Unspecified,
19    /// e.g. JSLexer or something with similar functionality.
20    Lexer,
21    /// e.g. JSParser, JSONParser or something with similar functionality.
22    Parser,
23}
24
25/// Options for outputting errors. Port of `SourceErrorOutputOptions`.
26#[derive(Copy, Clone, Debug)]
27pub struct OutputOptions {
28    /// Determine whether errors should be colorized.
29    pub show_colors: bool,
30    /// Soft limit on how wide errors should be (None = unlimited).
31    pub preferred_max_error_width: Option<usize>,
32}
33
34impl OutputOptions {
35    /// Width of a tab.
36    pub const TAB_STOP: usize = 8;
37    /// Minimum context (in source characters) around a highlighted range.
38    pub const MINIMUM_SOURCE_CONTEXT: usize = 16;
39}
40
41impl Default for OutputOptions {
42    fn default() -> Self {
43        OutputOptions {
44            show_colors: true,
45            preferred_max_error_width: None,
46        }
47    }
48}
49
50/// A fully resolved diagnostic handed to a `DiagHandler`. All buffer lookups
51/// have already happened, so handlers are free of the source manager.
52#[derive(Clone, Debug)]
53pub struct ResolvedDiagnostic {
54    pub kind: DiagKind,
55    pub file_name: String,
56    /// 1-based line/col.
57    pub line: u32,
58    pub col: u32,
59    pub message: String,
60    /// The source line text (without buffer access), if available.
61    pub source_line: Option<String>,
62    /// 0-based byte `[start, end)` columns of the highlighted range within the
63    /// source line, if a range was provided and the location is known.
64    /// `None` when no range is present or no source line is available.
65    pub range_cols: Option<(u32, u32)>,
66}
67
68/// Sink for resolved diagnostics. Default impls print; the collecting impl
69/// captures for tests. Replaces the hardcoded-stderr model.
70pub trait DiagHandler {
71    fn handle(&mut self, diag: &ResolvedDiagnostic);
72    /// Return `self` as `&dyn Any` to allow downcasting to a concrete type.
73    fn as_any(&self) -> &dyn std::any::Any;
74}
75
76/// Hook to translate coordinates (e.g. via a source map) before display.
77/// Port of `ICoordTranslator`.
78pub trait CoordTranslator {
79    fn translate(&self, coords: &mut SourceCoords);
80}
81
82/// A `DiagHandler` that records diagnostics in memory for tests.
83pub struct CollectingHandler {
84    messages: Vec<ResolvedDiagnostic>,
85}
86
87impl CollectingHandler {
88    pub fn new() -> CollectingHandler {
89        CollectingHandler {
90            messages: Vec::new(),
91        }
92    }
93
94    pub fn messages(&self) -> &[ResolvedDiagnostic] {
95        &self.messages
96    }
97}
98
99impl Default for CollectingHandler {
100    fn default() -> Self {
101        Self::new()
102    }
103}
104
105impl DiagHandler for CollectingHandler {
106    fn handle(&mut self, diag: &ResolvedDiagnostic) {
107        self.messages.push(diag.clone());
108    }
109
110    fn as_any(&self) -> &dyn std::any::Any {
111        self
112    }
113}
114
115/// A warning category. Ported verbatim from `hermes/Support/Warnings.def`.
116/// `NoWarning` is special and must remain the first variant.
117#[derive(Copy, Clone, PartialEq, Eq, Debug)]
118pub enum Warning {
119    /// All warnings. Special; its description is only used in the context of
120    /// -Werror and -Wno-error.
121    NoWarning,
122    /// Warning when an undefined variable is referenced. (-Wundefined-variable)
123    UndefinedVariable,
124    /// Warning when attempting a direct (local) eval. (-Wdirect-eval)
125    DirectEval,
126    /// Warning if invoking eval() when it is disabled. (-Weval-disabled)
127    EvalDisabled,
128    /// Warning when require calls cannot be resolved statically.
129    /// (-Wunresolved-static-require)
130    UnresolvedStaticRequire,
131    /// Miscellaneous warnings. (hidden: -Wmisc)
132    Misc,
133}
134
135impl Warning {
136    /// Number of warning categories (the C++ `_NumWarnings` sentinel).
137    pub const COUNT: usize = 6;
138
139    /// The dense 0-based index of this category, for indexing status bitsets.
140    pub fn index(self) -> usize {
141        self as usize
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn collecting_handler_records() {
151        let mut h = CollectingHandler::new();
152        h.handle(&ResolvedDiagnostic {
153            kind: DiagKind::Error,
154            line: 3,
155            col: 5,
156            file_name: "a.js".into(),
157            message: "boom".into(),
158            source_line: Some("  let x".into()),
159            range_cols: None,
160        });
161        assert_eq!(h.messages().len(), 1);
162        assert_eq!(h.messages()[0].kind, DiagKind::Error);
163        assert_eq!((h.messages()[0].line, h.messages()[0].col), (3, 5));
164    }
165
166    #[test]
167    fn output_options_defaults() {
168        let o = OutputOptions::default();
169        assert!(o.show_colors);
170        assert_eq!(OutputOptions::TAB_STOP, 8);
171    }
172
173    #[test]
174    fn warning_index_within_count() {
175        assert_eq!(Warning::NoWarning.index(), 0);
176        assert!(Warning::Misc.index() < Warning::COUNT);
177        assert_eq!(Warning::COUNT, 6);
178    }
179}