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}