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}