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}