codespan_reporting/
diagnostic.rs

1//! Diagnostic data structures.
2
3use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5
6use core::fmt::Display;
7use core::ops::Range;
8
9#[cfg(feature = "serialization")]
10use serde::{Deserialize, Serialize};
11
12/// A severity level for diagnostic messages.
13///
14/// These are ordered in the following way:
15///
16/// ```rust
17/// use codespan_reporting::diagnostic::Severity;
18///
19/// assert!(Severity::Bug > Severity::Error);
20/// assert!(Severity::Error > Severity::Warning);
21/// assert!(Severity::Warning > Severity::Note);
22/// assert!(Severity::Note > Severity::Help);
23/// ```
24#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)]
25#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
26pub enum Severity {
27    /// A help message.
28    Help,
29    /// A note.
30    Note,
31    /// A warning.
32    Warning,
33    /// An error.
34    Error,
35    /// An unexpected bug.
36    Bug,
37}
38
39#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd)]
40#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
41pub enum LabelStyle {
42    /// Labels that describe the primary cause of a diagnostic.
43    Primary,
44    /// Labels that provide additional context for a diagnostic.
45    Secondary,
46}
47
48/// A label describing an underlined region of code associated with a diagnostic.
49#[derive(Clone, Debug, PartialEq, Eq)]
50#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
51pub struct Label<FileId> {
52    /// The style of the label.
53    pub style: LabelStyle,
54    /// The file that we are labelling.
55    pub file_id: FileId,
56    /// The range in bytes we are going to include in the final snippet.
57    pub range: Range<usize>,
58    /// An optional message to provide some additional information for the
59    /// underlined code. These should not include line breaks.
60    pub message: String,
61}
62
63impl<FileId> Label<FileId> {
64    /// Create a new label.
65    pub fn new(
66        style: LabelStyle,
67        file_id: FileId,
68        range: impl Into<Range<usize>>,
69    ) -> Label<FileId> {
70        Label {
71            style,
72            file_id,
73            range: range.into(),
74            message: String::new(),
75        }
76    }
77
78    /// Create a new label with a style of [`LabelStyle::Primary`].
79    ///
80    /// [`LabelStyle::Primary`]: LabelStyle::Primary
81    pub fn primary(file_id: FileId, range: impl Into<Range<usize>>) -> Label<FileId> {
82        Label::new(LabelStyle::Primary, file_id, range)
83    }
84
85    /// Create a new label with a style of [`LabelStyle::Secondary`].
86    ///
87    /// [`LabelStyle::Secondary`]: LabelStyle::Secondary
88    pub fn secondary(file_id: FileId, range: impl Into<Range<usize>>) -> Label<FileId> {
89        Label::new(LabelStyle::Secondary, file_id, range)
90    }
91
92    /// Add a message to the diagnostic.
93    #[must_use]
94    pub fn with_message(mut self, message: impl Display) -> Label<FileId> {
95        self.message = message.to_string();
96        self
97    }
98}
99
100/// Represents a diagnostic message that can provide information like errors and
101/// warnings to the user.
102///
103/// The position of a Diagnostic is considered to be the position of the [`Label`] that has the earliest starting position and has the highest style which appears in all the labels of the diagnostic.
104#[derive(Clone, Debug, PartialEq, Eq)]
105#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
106pub struct Diagnostic<FileId> {
107    /// The overall severity of the diagnostic
108    pub severity: Severity,
109    /// An optional code that identifies this diagnostic.
110    pub code: Option<String>,
111    /// The main message associated with this diagnostic.
112    ///
113    /// These should not include line breaks, and in order support the 'short'
114    /// diagnostic display mod, the message should be specific enough to make
115    /// sense on its own, without additional context provided by labels and notes.
116    pub message: String,
117    /// Source labels that describe the cause of the diagnostic.
118    /// The order of the labels inside the vector does not have any meaning.
119    /// The labels are always arranged in the order they appear in the source code.
120    pub labels: Vec<Label<FileId>>,
121    /// Notes that are associated with the primary cause of the diagnostic.
122    /// These can include line breaks for improved formatting.
123    pub notes: Vec<String>,
124}
125
126impl<FileId> Diagnostic<FileId> {
127    /// Create a new diagnostic.
128    pub fn new(severity: Severity) -> Diagnostic<FileId> {
129        Diagnostic {
130            severity,
131            code: None,
132            message: String::new(),
133            labels: Vec::new(),
134            notes: Vec::new(),
135        }
136    }
137
138    /// Create a new diagnostic with a severity of [`Severity::Bug`].
139    ///
140    /// [`Severity::Bug`]: Severity::Bug
141    #[must_use]
142    pub fn bug() -> Diagnostic<FileId> {
143        Diagnostic::new(Severity::Bug)
144    }
145
146    /// Create a new diagnostic with a severity of [`Severity::Error`].
147    ///
148    /// [`Severity::Error`]: Severity::Error
149    #[must_use]
150    pub fn error() -> Diagnostic<FileId> {
151        Diagnostic::new(Severity::Error)
152    }
153
154    /// Create a new diagnostic with a severity of [`Severity::Warning`].
155    ///
156    /// [`Severity::Warning`]: Severity::Warning
157    #[must_use]
158    pub fn warning() -> Diagnostic<FileId> {
159        Diagnostic::new(Severity::Warning)
160    }
161
162    /// Create a new diagnostic with a severity of [`Severity::Note`].
163    ///
164    /// [`Severity::Note`]: Severity::Note
165    #[must_use]
166    pub fn note() -> Diagnostic<FileId> {
167        Diagnostic::new(Severity::Note)
168    }
169
170    /// Create a new diagnostic with a severity of [`Severity::Help`].
171    ///
172    /// [`Severity::Help`]: Severity::Help
173    #[must_use]
174    pub fn help() -> Diagnostic<FileId> {
175        Diagnostic::new(Severity::Help)
176    }
177
178    /// Set the error code of the diagnostic.
179    #[must_use]
180    pub fn with_code(mut self, code: impl Display) -> Diagnostic<FileId> {
181        self.code = Some(code.to_string());
182        self
183    }
184
185    /// Set the message of the diagnostic.
186    #[must_use]
187    pub fn with_message(mut self, message: impl Display) -> Diagnostic<FileId> {
188        self.message = message.to_string();
189        self
190    }
191
192    /// Add a label to the diagnostic.
193    #[must_use]
194    pub fn with_label(mut self, label: Label<FileId>) -> Diagnostic<FileId> {
195        self.labels.push(label);
196        self
197    }
198
199    /// Add some labels to the diagnostic.
200    #[must_use]
201    pub fn with_labels(mut self, mut labels: Vec<Label<FileId>>) -> Diagnostic<FileId> {
202        self.labels.append(&mut labels);
203        self
204    }
205
206    /// Add some labels to the diagnostic.
207    #[must_use]
208    pub fn with_labels_iter(
209        mut self,
210        labels: impl IntoIterator<Item = Label<FileId>>,
211    ) -> Diagnostic<FileId> {
212        self.labels.extend(labels);
213        self
214    }
215
216    /// Add a note to the diagnostic.
217    #[allow(clippy::needless_pass_by_value)]
218    #[must_use]
219    pub fn with_note(mut self, note: impl ToString) -> Diagnostic<FileId> {
220        self.notes.push(note.to_string());
221        self
222    }
223
224    /// Add some notes to the diagnostic.
225    #[must_use]
226    pub fn with_notes(mut self, mut notes: Vec<String>) -> Diagnostic<FileId> {
227        self.notes.append(&mut notes);
228        self
229    }
230
231    /// Add some notes to the diagnostic.
232    #[must_use]
233    pub fn with_notes_iter(
234        mut self,
235        notes: impl IntoIterator<Item = String>,
236    ) -> Diagnostic<FileId> {
237        self.notes.extend(notes);
238        self
239    }
240}