1use crate::source::FileId;
9use crate::span::Span;
10
11#[derive(Clone, Copy, PartialEq, Eq, Debug)]
12pub enum Severity {
13 Error,
14 Warning,
15}
16
17impl Severity {
18 pub fn as_str(self) -> &'static str {
19 match self {
20 Severity::Error => "error",
21 Severity::Warning => "warning",
22 }
23 }
24}
25
26#[derive(Clone, Debug)]
37pub struct Label {
38 pub span: Span,
39 pub file: Option<FileId>,
41 pub message: String,
42}
43
44impl Label {
45 pub fn new(span: Span, message: impl Into<String>) -> Self {
46 Self {
47 span,
48 file: None,
49 message: message.into(),
50 }
51 }
52
53 pub fn in_file(file: FileId, span: Span, message: impl Into<String>) -> Self {
55 Self {
56 span,
57 file: Some(file),
58 message: message.into(),
59 }
60 }
61
62 pub fn file_or(&self, diagnostic: FileId) -> FileId {
64 self.file.unwrap_or(diagnostic)
65 }
66}
67
68#[derive(Clone, Copy, PartialEq, Eq, Debug)]
70pub enum Applicability {
71 MachineApplicable,
73 MaybeIncorrect,
75}
76
77impl Applicability {
78 pub fn as_str(self) -> &'static str {
79 match self {
80 Applicability::MachineApplicable => "machine-applicable",
81 Applicability::MaybeIncorrect => "maybe-incorrect",
82 }
83 }
84}
85
86#[derive(Clone, Debug)]
88pub struct SuggestedEdit {
89 pub span: Span,
90 pub replacement: String,
91}
92
93#[derive(Clone, Debug)]
95pub struct Fix {
96 pub message: String,
97 pub edits: Vec<SuggestedEdit>,
98 pub applicability: Applicability,
99}
100
101#[derive(Clone, Debug)]
108pub struct Diagnostic {
109 pub code: &'static str,
111 pub severity: Severity,
112 pub message: String,
113 pub file: FileId,
114 pub primary: Label,
115 pub secondary: Vec<Label>,
116 pub notes: Vec<String>,
117 pub fix: Option<Fix>,
118}
119
120impl Diagnostic {
121 pub fn error(code: &'static str, file: FileId, span: Span, message: impl Into<String>) -> Self {
122 let message = message.into();
123 Self {
124 code,
125 severity: Severity::Error,
126 primary: Label::new(span, message.clone()),
127 message,
128 file,
129 secondary: Vec::new(),
130 notes: Vec::new(),
131 fix: None,
132 }
133 }
134
135 pub fn warning(
136 code: &'static str,
137 file: FileId,
138 span: Span,
139 message: impl Into<String>,
140 ) -> Self {
141 let mut diagnostic = Self::error(code, file, span, message);
142 diagnostic.severity = Severity::Warning;
143 diagnostic
144 }
145
146 #[must_use]
149 pub fn with_primary_label(mut self, message: impl Into<String>) -> Self {
150 self.primary.message = message.into();
151 self
152 }
153
154 #[must_use]
155 pub fn with_secondary(mut self, span: Span, message: impl Into<String>) -> Self {
156 self.secondary.push(Label::new(span, message));
157 self
158 }
159
160 #[must_use]
168 pub fn with_secondary_in(
169 mut self,
170 file: FileId,
171 span: Span,
172 message: impl Into<String>,
173 ) -> Self {
174 self.secondary.push(Label::in_file(file, span, message));
175 self
176 }
177
178 #[must_use]
179 pub fn with_note(mut self, note: impl Into<String>) -> Self {
180 self.notes.push(note.into());
181 self
182 }
183
184 #[must_use]
185 pub fn with_fix(
186 mut self,
187 message: impl Into<String>,
188 span: Span,
189 replacement: impl Into<String>,
190 applicability: Applicability,
191 ) -> Self {
192 self.fix = Some(Fix {
193 message: message.into(),
194 edits: vec![SuggestedEdit {
195 span,
196 replacement: replacement.into(),
197 }],
198 applicability,
199 });
200 self
201 }
202
203 #[must_use]
213 pub fn with_edits(
214 mut self,
215 message: impl Into<String>,
216 edits: Vec<SuggestedEdit>,
217 applicability: Applicability,
218 ) -> Self {
219 self.fix = Some(Fix {
220 message: message.into(),
221 edits,
222 applicability,
223 });
224 self
225 }
226
227 pub fn is_error(&self) -> bool {
228 self.severity == Severity::Error
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::{Applicability, Diagnostic, Severity};
235 use crate::source::SourceMap;
236 use crate::span::Span;
237
238 #[test]
239 fn primary_label_defaults_to_the_message() {
240 let mut map = SourceMap::new();
241 let file = map.add("t.deed", "let x = 1");
242 let d = Diagnostic::error("DEED0001", file, Span::new(0, 3), "something went wrong");
243 assert_eq!(d.primary.message, "something went wrong");
244 assert!(d.is_error());
245 }
246
247 #[test]
248 fn builders_compose() {
249 let mut map = SourceMap::new();
250 let file = map.add("t.deed", "let x = 1");
251 let d = Diagnostic::warning("DEED0002", file, Span::new(0, 3), "headline")
252 .with_primary_label("here")
253 .with_secondary(Span::new(4, 5), "related")
254 .with_note("a note")
255 .with_fix(
256 "try this",
257 Span::new(0, 3),
258 "val",
259 Applicability::MachineApplicable,
260 );
261
262 assert_eq!(d.severity, Severity::Warning);
263 assert_eq!(d.primary.message, "here");
264 assert_eq!(d.secondary.len(), 1);
265 assert_eq!(d.notes, vec!["a note".to_string()]);
266 assert_eq!(d.fix.unwrap().edits[0].replacement, "val");
267 }
268}