biome_diagnostics/
diagnostic.rs

1use std::{
2    convert::Infallible,
3    fmt::{Debug, Display},
4    io,
5    ops::{BitOr, BitOrAssign},
6    str::FromStr,
7};
8
9use enumflags2::{bitflags, make_bitflags, BitFlags};
10use serde::{Deserialize, Serialize};
11
12use biome_console::fmt;
13
14use crate::{Category, Location, Visit};
15
16/// The `Diagnostic` trait defines the metadata that can be exposed by error
17/// types in order to print details diagnostics in the console of the editor
18///
19/// ## Implementation
20///
21/// Most types should not have to implement this trait manually, and should
22/// instead rely on the `Diagnostic` derive macro also provided by this crate:
23///
24/// ```
25/// # use biome_diagnostics::Diagnostic;
26/// #[derive(Debug, Diagnostic)]
27/// #[diagnostic(category = "lint/style/noShoutyConstants", tags(FIXABLE))]
28/// struct ExampleDiagnostic {
29///     #[message]
30///     #[description]
31///     message: String,
32/// }
33/// ```
34pub trait Diagnostic: Debug {
35    /// The category of a diagnostic uniquely identifying this
36    /// diagnostic type, such as `lint/correctness/noArguments`, `args/invalid`
37    /// or `format/disabled`.
38    fn category(&self) -> Option<&'static Category> {
39        None
40    }
41
42    /// The severity defines whether this diagnostic reports an error, a
43    /// warning, an information or a hint to the user.
44    fn severity(&self) -> Severity {
45        Severity::Error
46    }
47
48    /// The description is a text-only explanation of the issue this diagnostic
49    /// is reporting, intended for display contexts that do not support rich
50    /// markup such as in-editor popovers
51    ///
52    /// The description should generally be as exhaustive as possible, since
53    /// the clients that do not support rendering markup will not render the
54    /// advices for the diagnostic either.
55    fn description(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        let _ = fmt;
57        Ok(())
58    }
59
60    /// An explanation of the issue this diagnostic is reporting
61    ///
62    /// In general it's better to keep this message as short as possible, and
63    /// instead rely on advices to better convey contextual explanations to the
64    /// user.
65    fn message(&self, fmt: &mut fmt::Formatter<'_>) -> io::Result<()> {
66        let _ = fmt;
67        Ok(())
68    }
69
70    /// Advices are the main building blocks used compose rich errors. They are
71    /// implemented using a visitor pattern, where consumers of a diagnostic
72    /// can visit the object and collect the advices that make it up for the
73    /// purpose of display or introspection.
74    fn advices(&self, visitor: &mut dyn Visit) -> io::Result<()> {
75        let _ = visitor;
76        Ok(())
77    }
78
79    /// Diagnostics can defines additional advices to be printed if the user
80    /// requires more detail about the diagnostic.
81    fn verbose_advices(&self, visitor: &mut dyn Visit) -> io::Result<()> {
82        let _ = visitor;
83        Ok(())
84    }
85
86    /// A diagnostic can be tied to a specific "location": this can be a file,
87    /// memory buffer, command line argument, etc. It may also be tied to a
88    /// specific text range within the content of that location. Finally, it
89    /// may also provide the source string for that location (this is required
90    /// in order to display a code frame advice for the diagnostic).
91    fn location(&self) -> Location<'_> {
92        Location::builder().build()
93    }
94
95    /// Tags convey additional boolean metadata about the nature of a diagnostic:
96    /// - If the diagnostic can be automatically fixed
97    /// - If the diagnostic resulted from and internal error
98    /// - If the diagnostic is being emitted as part of a crash / fatal error
99    /// - If the diagnostic is a warning about a piece of unused or unnecessary code
100    /// - If the diagnostic is a warning about a piece of deprecated or obsolete code.
101    /// - If the diagnostic is meant to provide more information
102    fn tags(&self) -> DiagnosticTags {
103        DiagnosticTags::empty()
104    }
105
106    /// Similarly to the `source` method of the [std::error::Error] trait, this
107    /// returns another diagnostic that's the logical "cause" for this issue.
108    /// For instance, a "request failed" diagnostic may have been cause by a
109    /// "deserialization error". This allows low-level error to be wrapped in
110    /// higher level concepts, while retaining enough information to display
111    /// and fix the underlying issue.
112    fn source(&self) -> Option<&dyn Diagnostic> {
113        None
114    }
115}
116
117#[derive(
118    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
119)]
120#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
121#[serde(rename_all = "camelCase")]
122/// The severity to associate to a diagnostic.
123pub enum Severity {
124    /// Reports a hint.
125    Hint,
126    /// Reports an information.
127    #[default]
128    Information,
129    /// Reports a warning.
130    Warning,
131    /// Reports an error.
132    Error,
133    /// Reports a crash.
134    Fatal,
135}
136
137impl FromStr for Severity {
138    type Err = String;
139
140    fn from_str(s: &str) -> Result<Self, Self::Err> {
141        match s {
142            "hint" => Ok(Self::Information),
143            "info" => Ok(Self::Information),
144            "warn" => Ok(Self::Warning),
145            "error" => Ok(Self::Error),
146            v => Err(format!(
147                "Found unexpected value ({v}), valid values are: info, warn, error."
148            )),
149        }
150    }
151}
152
153impl Display for Severity {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        match self {
156            Self::Hint => write!(f, "info"),
157            Self::Information => write!(f, "info"),
158            Self::Warning => write!(f, "warn"),
159            Self::Error => write!(f, "error"),
160            Self::Fatal => write!(f, "fatal"),
161        }
162    }
163}
164
165/// Internal enum used to automatically generate bit offsets for [DiagnosticTags]
166/// and help with the implementation of `serde` and `schemars` for tags.
167#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
168#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
169#[serde(rename_all = "camelCase")]
170#[bitflags]
171#[repr(u8)]
172pub(super) enum DiagnosticTag {
173    Fixable = 1 << 0,
174    Internal = 1 << 1,
175    UnnecessaryCode = 1 << 2,
176    DeprecatedCode = 1 << 3,
177    Verbose = 1 << 4,
178}
179
180#[derive(Debug, Copy, Clone, PartialEq, Eq)]
181pub struct DiagnosticTags(BitFlags<DiagnosticTag>);
182impl DiagnosticTags {
183    /// This diagnostic has a fix suggestion.
184    pub const FIXABLE: Self = Self(make_bitflags!(DiagnosticTag::{Fixable}));
185    /// This diagnostic results from an internal error.
186    pub const INTERNAL: Self = Self(make_bitflags!(DiagnosticTag::{Internal}));
187    /// This diagnostic tags unused or unnecessary code, this may change
188    /// how the diagnostic is render in editors.
189    pub const UNNECESSARY_CODE: Self = Self(make_bitflags!(DiagnosticTag::{UnnecessaryCode}));
190    /// This diagnostic tags deprecated or obsolete code, this may change
191    /// how the diagnostic is render in editors.
192    pub const DEPRECATED_CODE: Self = Self(make_bitflags!(DiagnosticTag::{DeprecatedCode}));
193    /// This diagnostic is verbose and should be printed only if the `--verbose` option is provided
194    pub const VERBOSE: Self = Self(make_bitflags!(DiagnosticTag::{Verbose}));
195    pub const fn all() -> Self {
196        Self(BitFlags::ALL)
197    }
198    pub const fn empty() -> Self {
199        Self(BitFlags::EMPTY)
200    }
201    pub fn insert(&mut self, other: DiagnosticTags) {
202        self.0 |= other.0;
203    }
204    pub fn contains(self, other: impl Into<DiagnosticTags>) -> bool {
205        self.0.contains(other.into().0)
206    }
207    pub const fn union(self, other: Self) -> Self {
208        Self(self.0.union_c(other.0))
209    }
210    pub fn is_empty(self) -> bool {
211        self.0.is_empty()
212    }
213    pub fn is_verbose(&self) -> bool {
214        self.contains(DiagnosticTag::Verbose)
215    }
216}
217
218impl BitOr for DiagnosticTags {
219    type Output = Self;
220
221    fn bitor(self, rhs: Self) -> Self::Output {
222        DiagnosticTags(self.0 | rhs.0)
223    }
224}
225
226impl BitOrAssign for DiagnosticTags {
227    fn bitor_assign(&mut self, rhs: Self) {
228        self.0 |= rhs.0;
229    }
230}
231
232// Implement the `Diagnostic` on the `Infallible` error type from the standard
233// library as a utility for implementing signatures that require a diagnostic
234// type when the operation can never fail
235impl Diagnostic for Infallible {}
236
237pub(crate) mod internal {
238    //! The `AsDiagnostic` trait needs to be declared as public as its referred
239    //! to in the `where` clause of other public items, but as it's not part of
240    //! the public API it's declared in a private module so it's not accessible
241    //! outside of the crate
242
243    use std::fmt::Debug;
244
245    use crate::Diagnostic;
246
247    /// Since [Error](crate::Error) must implement `From<T: Diagnostic>` to
248    /// be used with the `?` operator, it cannot implement the [Diagnostic]
249    /// trait (as that would conflict with the implementation of `From<T> for T`
250    /// in the standard library). The [AsDiagnostic] exists as an internal
251    /// implementation detail to bridge this gap and allow various types and
252    /// functions in `biome_diagnostics` to be generic over all diagnostics +
253    /// `Error`.
254    pub trait AsDiagnostic: Debug {
255        type Diagnostic: Diagnostic + ?Sized;
256        fn as_diagnostic(&self) -> &Self::Diagnostic;
257        fn as_dyn(&self) -> &dyn Diagnostic;
258    }
259
260    impl<D: Diagnostic> AsDiagnostic for D {
261        type Diagnostic = D;
262
263        fn as_diagnostic(&self) -> &Self::Diagnostic {
264            self
265        }
266
267        fn as_dyn(&self) -> &dyn Diagnostic {
268            self
269        }
270    }
271}