compose_codespan_reporting/
diagnostic.rs

1//! Diagnostic data structures.
2
3#[cfg(feature = "serialization")]
4use serde::{Deserialize, Serialize};
5use std::ops::Range;
6use std::string::ToString;
7
8/// A severity level for diagnostic messages.
9///
10/// These are ordered in the following way:
11///
12/// ```rust
13/// use compose_codespan_reporting::diagnostic::Severity;
14///
15/// assert!(Severity::Bug > Severity::Error);
16/// assert!(Severity::Error > Severity::Warning);
17/// assert!(Severity::Warning > Severity::Note);
18/// assert!(Severity::Note > Severity::Help);
19/// ```
20#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)]
21#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
22pub enum Severity {
23    /// A help message.
24    Help,
25    /// A note.
26    Note,
27    /// A warning.
28    Warning,
29    /// An error.
30    Error,
31    /// An unexpected bug.
32    Bug,
33}
34
35#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd)]
36#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
37pub enum LabelStyle {
38    /// Labels that describe the primary cause of a diagnostic.
39    Primary,
40    /// Labels that provide additional context for a diagnostic.
41    Secondary,
42}
43
44/// A label describing an underlined region of code associated with a diagnostic.
45#[derive(Clone, Debug, PartialEq, Eq)]
46#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
47pub struct Label<FileId> {
48    /// The style of the label.
49    pub style: LabelStyle,
50    /// The file that we are labelling.
51    pub file_id: FileId,
52    /// The range in bytes we are going to include in the final snippet.
53    pub range: Range<usize>,
54    /// An optional message to provide some additional information for the
55    /// underlined code. These should not include line breaks.
56    pub message: String,
57}
58
59impl<FileId> Label<FileId> {
60    /// Create a new label.
61    pub fn new(
62        style: LabelStyle,
63        file_id: FileId,
64        range: impl Into<Range<usize>>,
65    ) -> Label<FileId> {
66        Label {
67            style,
68            file_id,
69            range: range.into(),
70            message: String::new(),
71        }
72    }
73
74    /// Create a new label with a style of [`LabelStyle::Primary`].
75    ///
76    /// [`LabelStyle::Primary`]: LabelStyle::Primary
77    pub fn primary(file_id: FileId, range: impl Into<Range<usize>>) -> Label<FileId> {
78        Label::new(LabelStyle::Primary, file_id, range)
79    }
80
81    /// Create a new label with a style of [`LabelStyle::Secondary`].
82    ///
83    /// [`LabelStyle::Secondary`]: LabelStyle::Secondary
84    pub fn secondary(file_id: FileId, range: impl Into<Range<usize>>) -> Label<FileId> {
85        Label::new(LabelStyle::Secondary, file_id, range)
86    }
87
88    /// Add a message to the diagnostic.
89    pub fn with_message(mut self, message: impl ToString) -> Label<FileId> {
90        self.message = message.to_string();
91        self
92    }
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
96#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
97pub struct Suggestion<FileId> {
98    pub file_id: FileId,
99    pub message: String,
100    pub parts: Vec<SuggestionPart>,
101}
102
103#[derive(Clone, Debug, PartialEq, Eq)]
104#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
105pub struct SpannedNote<FileId> {
106    pub severity: Severity,
107    pub message: String,
108    pub labels: Vec<Label<FileId>>,
109}
110
111#[derive(Clone, Debug, PartialEq, Eq)]
112#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
113pub enum Subdiagnostic<FileId> {
114    Suggestion(Suggestion<FileId>),
115    SpannedNote(SpannedNote<FileId>),
116}
117
118#[derive(Clone, Debug, PartialEq, Eq)]
119#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
120pub struct SuggestionPart {
121    pub range: Range<usize>,
122    pub replacement: String,
123}
124
125/// Represents a diagnostic message that can provide information like errors and
126/// warnings to the user.
127///
128/// 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.
129#[derive(Clone, Debug, PartialEq, Eq)]
130#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
131pub struct Diagnostic<FileId> {
132    /// The overall severity of the diagnostic
133    pub severity: Severity,
134    /// An optional code that identifies this diagnostic.
135    pub code: Option<String>,
136    /// The main message associated with this diagnostic.
137    ///
138    /// These should not include line breaks, and in order support the 'short'
139    /// diagnostic display mod, the message should be specific enough to make
140    /// sense on its own, without additional context provided by labels and notes.
141    pub message: String,
142    /// Source labels that describe the cause of the diagnostic.
143    /// The order of the labels inside the vector does not have any meaning.
144    /// The labels are always arranged in the order they appear in the source code.
145    pub labels: Vec<Label<FileId>>,
146    /// Notes that are associated with the primary cause of the diagnostic.
147    /// These can include line breaks for improved formatting.
148    // FIXME: Notes could be replaced with a Subdiagnostic::Note without a Label, but that would
149    // change lots of API. Probably deprecate for one version before that.
150    pub notes: Vec<String>,
151    /// A list of diagnostics that are "attached" to the main diagnostic. They are rendered below
152    /// the main diagnostic but still as one "unit". For example, a note with one or more labels
153    /// can be attached to a warning to give additional information that doesn't make sense to
154    /// group with the labels.
155    pub subdiagnostics: Vec<Subdiagnostic<FileId>>,
156}
157
158impl<FileId> Diagnostic<FileId> {
159    /// Create a new diagnostic.
160    pub fn new(severity: Severity) -> Self {
161        Diagnostic {
162            severity,
163            code: None,
164            message: String::new(),
165            labels: Vec::new(),
166            notes: Vec::new(),
167            subdiagnostics: Vec::new(),
168        }
169    }
170
171    /// Create a new diagnostic with a severity of [`Severity::Bug`].
172    ///
173    /// [`Severity::Bug`]: Severity::Bug
174    pub fn bug() -> Self {
175        Diagnostic::new(Severity::Bug)
176    }
177
178    /// Create a new diagnostic with a severity of [`Severity::Error`].
179    ///
180    /// [`Severity::Error`]: Severity::Error
181    pub fn error() -> Self {
182        Diagnostic::new(Severity::Error)
183    }
184
185    /// Create a new diagnostic with a severity of [`Severity::Warning`].
186    ///
187    /// [`Severity::Warning`]: Severity::Warning
188    pub fn warning() -> Self {
189        Diagnostic::new(Severity::Warning)
190    }
191
192    /// Create a new diagnostic with a severity of [`Severity::Note`].
193    ///
194    /// [`Severity::Note`]: Severity::Note
195    pub fn note() -> Self {
196        Diagnostic::new(Severity::Note)
197    }
198
199    /// Create a new diagnostic with a severity of [`Severity::Help`].
200    ///
201    /// [`Severity::Help`]: Severity::Help
202    pub fn help() -> Self {
203        Diagnostic::new(Severity::Help)
204    }
205
206    /// Set the error code of the diagnostic.
207    pub fn with_code(mut self, code: impl ToString) -> Self {
208        self.code = Some(code.to_string());
209        self
210    }
211
212    /// Set the message of the diagnostic.
213    pub fn with_message(mut self, message: impl ToString) -> Self {
214        self.message = message.to_string();
215        self
216    }
217
218    /// Add a label to the diagnostic.
219    pub fn with_label(mut self, label: Label<FileId>) -> Self {
220        self.labels.push(label);
221        self
222    }
223
224    /// Add some labels to the diagnostic.
225    pub fn with_labels(mut self, mut labels: Vec<Label<FileId>>) -> Self {
226        self.labels.append(&mut labels);
227        self
228    }
229
230    /// Add some labels from an iterator to the diagnostic.
231    pub fn with_labels_iter<I>(mut self, labels: I) -> Self
232    where
233        I: IntoIterator<Item = Label<FileId>>,
234    {
235        self.labels.extend(labels);
236        self
237    }
238
239    /// Add some notes to the diagnostic.
240    pub fn with_notes(mut self, mut notes: Vec<String>) -> Self {
241        self.notes.append(&mut notes);
242        self
243    }
244
245    /// Add a note to the diagnostic.
246    pub fn with_note(mut self, note: impl Into<String>) -> Self {
247        self.notes.push(note.into());
248        self
249    }
250
251    /// Add notes from an iterator to the diagnostic.
252    pub fn with_notes_iter<I, T>(mut self, notes: I) -> Self
253    where
254        I: IntoIterator<Item = T>,
255        T: Into<String>,
256    {
257        self.notes.extend(notes.into_iter().map(Into::into));
258        self
259    }
260
261    /// Add some suggestions to the diagnostic.
262    ///
263    /// Convenience method for [Self::with_subdiagnostics]. Use that if you want to interleave
264    /// suggestions with other [Subdiagnostic]s.
265    pub fn with_suggestions(mut self, suggestions: Vec<Suggestion<FileId>>) -> Self {
266        self.subdiagnostics
267            .extend(suggestions.into_iter().map(Subdiagnostic::Suggestion));
268        self
269    }
270
271    /// Add some spanned notes to the diagnostic.
272    ///
273    /// Convenience method for [Self::with_subdiagnostics]. Use that if you want to interleave
274    /// spanned notes with other [Subdiagnostic]s.
275    pub fn with_spanned_notes(mut self, spanned_notes: Vec<SpannedNote<FileId>>) -> Self {
276        self.subdiagnostics
277            .extend(spanned_notes.into_iter().map(Subdiagnostic::SpannedNote));
278        self
279    }
280
281    /// Add some subdiagnostics to the diagnostic.
282    pub fn with_subdiagnostics(
283        mut self,
284        mut subdiagnostics: Vec<Subdiagnostic<FileId>>,
285    ) -> Diagnostic<FileId> {
286        self.subdiagnostics.append(&mut subdiagnostics);
287        self
288    }
289}