1use crate::suggestion::SuggestionChange;
2use crate::{
3 file::{FileId, FileSpan, Span},
4 Applicability, CodeSuggestion, DiagnosticTag, Severity, SuggestionStyle,
5};
6use rslint_text_edit::*;
7
8#[derive(Debug, Clone, PartialEq, Hash)]
11pub struct Diagnostic {
12 pub file_id: FileId,
13
14 pub severity: Severity,
15 pub code: Option<String>,
16 pub title: String,
17 pub tag: Option<DiagnosticTag>,
18
19 pub primary: Option<SubDiagnostic>,
20 pub children: Vec<SubDiagnostic>,
21 pub suggestions: Vec<CodeSuggestion>,
22 pub footers: Vec<Footer>,
23}
24
25impl Diagnostic {
26 pub fn error(file_id: FileId, code: impl Into<String>, title: impl Into<String>) -> Self {
28 Self::new_with_code(file_id, Severity::Error, title, Some(code.into()))
29 }
30
31 pub fn warning(file_id: FileId, code: impl Into<String>, title: impl Into<String>) -> Self {
33 Self::new_with_code(file_id, Severity::Warning, title, Some(code.into()))
34 }
35
36 pub fn help(file_id: FileId, code: impl Into<String>, title: impl Into<String>) -> Self {
38 Self::new_with_code(file_id, Severity::Help, title, Some(code.into()))
39 }
40
41 pub fn note(file_id: FileId, code: impl Into<String>, title: impl Into<String>) -> Self {
43 Self::new_with_code(file_id, Severity::Note, title, Some(code.into()))
44 }
45
46 pub fn new(file_id: FileId, severity: Severity, title: impl Into<String>) -> Self {
49 Self::new_with_code(file_id, severity, title, None)
50 }
51
52 pub fn new_with_code(
55 file_id: FileId,
56 severity: Severity,
57 title: impl Into<String>,
58 code: Option<String>,
59 ) -> Self {
60 Self {
61 file_id,
62 code,
63 severity,
64 title: title.into(),
65 primary: None,
66 tag: None,
67 children: vec![],
68 suggestions: vec![],
69 footers: vec![],
70 }
71 }
72
73 pub fn severity(mut self, severity: Severity) -> Self {
75 self.severity = severity;
76 self
77 }
78
79 pub fn deprecated(mut self) -> Self {
84 self.tag = if matches!(self.tag, Some(DiagnosticTag::Unnecessary)) {
85 Some(DiagnosticTag::Both)
86 } else {
87 Some(DiagnosticTag::Deprecated)
88 };
89 self
90 }
91
92 pub fn unnecessary(mut self) -> Self {
97 self.tag = if matches!(self.tag, Some(DiagnosticTag::Deprecated)) {
98 Some(DiagnosticTag::Both)
99 } else {
100 Some(DiagnosticTag::Unnecessary)
101 };
102 self
103 }
104
105 pub fn label_in_file(mut self, severity: Severity, span: FileSpan, msg: String) -> Self {
108 self.children.push(SubDiagnostic {
109 severity,
110 msg,
111 span,
112 });
113 self
114 }
115
116 pub fn label(mut self, severity: Severity, span: impl Span, msg: impl Into<String>) -> Self {
120 self.children.push(SubDiagnostic {
121 severity,
122 msg: msg.into(),
123 span: FileSpan::new(self.file_id, span),
124 });
125 self
126 }
127
128 pub fn primary(mut self, span: impl Span, msg: impl Into<String>) -> Self {
130 self.primary = Some(SubDiagnostic {
131 severity: self.severity,
132 msg: msg.into(),
133 span: FileSpan::new(self.file_id, span),
134 });
135 self
136 }
137
138 pub fn secondary(self, span: impl Span, msg: impl Into<String>) -> Self {
140 self.label(Severity::Note, span, msg)
141 }
142
143 pub fn suggestion_in_file(
159 self,
160 span: impl Span,
161 msg: &str,
162 suggestion: impl Into<String>,
163 applicability: Applicability,
164 file: FileId,
165 ) -> Self {
166 self.suggestion_inner(span, msg, suggestion, applicability, None, file)
167 }
168
169 fn auto_suggestion_style(span: &impl Span, msg: &str) -> SuggestionStyle {
170 if span.as_range().len() + msg.len() > 25 {
171 SuggestionStyle::Full
172 } else {
173 SuggestionStyle::Inline
174 }
175 }
176
177 pub fn suggestion(
192 self,
193 span: impl Span,
194 msg: &str,
195 suggestion: impl Into<String>,
196 applicability: Applicability,
197 ) -> Self {
198 let file = self.file_id;
199 self.suggestion_inner(span, msg, suggestion, applicability, None, file)
200 }
201
202 pub fn suggestion_full(
204 self,
205 span: impl Span,
206 msg: &str,
207 suggestion: impl Into<String>,
208 applicability: Applicability,
209 ) -> Self {
210 let file = self.file_id;
211 self.suggestion_inner(
212 span,
213 msg,
214 suggestion,
215 applicability,
216 SuggestionStyle::Full,
217 file,
218 )
219 }
220
221 pub fn suggestion_inline(
223 self,
224 span: impl Span,
225 msg: &str,
226 suggestion: impl Into<String>,
227 applicability: Applicability,
228 ) -> Self {
229 let file = self.file_id;
230 self.suggestion_inner(
231 span,
232 msg,
233 suggestion,
234 applicability,
235 SuggestionStyle::Inline,
236 file,
237 )
238 }
239
240 pub fn suggestion_no_code(
242 self,
243 span: impl Span,
244 msg: &str,
245 applicability: Applicability,
246 ) -> Self {
247 let file = self.file_id;
248 self.suggestion_inner(
249 span,
250 msg,
251 "",
252 applicability,
253 SuggestionStyle::HideCode,
254 file,
255 )
256 }
257
258 pub fn indel_suggestion(
259 mut self,
260 indels: impl IntoIterator<Item = Indel>,
261 span: impl Span,
262 msg: &str,
263 applicability: Applicability,
264 ) -> Self {
265 let span = FileSpan {
266 file: self.file_id,
267 range: span.as_range(),
268 };
269 let indels = indels.into_iter().collect::<Vec<_>>();
270 let labels = indels
271 .iter()
272 .filter(|x| !x.insert.is_empty())
273 .map(|x| x.delete.as_range().start..x.delete.as_range().start + x.insert.len())
274 .collect();
275
276 let suggestion = CodeSuggestion {
277 substitution: SuggestionChange::Indels(indels),
278 applicability,
279 msg: msg.to_string(),
280 labels,
281 span,
282 style: SuggestionStyle::Full,
283 };
284 self.suggestions.push(suggestion);
285 self
286 }
287
288 pub fn suggestion_with_labels(
292 mut self,
293 span: impl Span,
294 msg: &str,
295 suggestion: impl Into<String>,
296 applicability: Applicability,
297 labels: impl IntoIterator<Item = impl Span>,
298 ) -> Self {
299 let span = FileSpan {
300 file: self.file_id,
301 range: span.as_range(),
302 };
303
304 let labels = labels
305 .into_iter()
306 .map(|x| {
307 let range = x.as_range();
308 span.range.start + range.start..span.range.start + range.end
309 })
310 .collect::<Vec<_>>();
311 let suggestion = CodeSuggestion {
312 substitution: SuggestionChange::String(suggestion.into()),
313 applicability,
314 msg: msg.to_string(),
315 labels,
316 span,
317 style: SuggestionStyle::Full,
318 };
319 self.suggestions.push(suggestion);
320 self
321 }
322
323 pub fn suggestion_with_src_labels(
327 mut self,
328 span: impl Span,
329 msg: &str,
330 suggestion: impl Into<String>,
331 applicability: Applicability,
332 labels: impl IntoIterator<Item = impl Span>,
333 ) -> Self {
334 let span = FileSpan {
335 file: self.file_id,
336 range: span.as_range(),
337 };
338
339 let labels = labels.into_iter().map(|x| x.as_range()).collect::<Vec<_>>();
340 let suggestion = CodeSuggestion {
341 substitution: SuggestionChange::String(suggestion.into()),
342 applicability,
343 msg: msg.to_string(),
344 labels,
345 span,
346 style: SuggestionStyle::Full,
347 };
348 self.suggestions.push(suggestion);
349 self
350 }
351
352 fn suggestion_inner(
353 mut self,
354 span: impl Span,
355 msg: &str,
356 suggestion: impl Into<String>,
357 applicability: Applicability,
358 style: impl Into<Option<SuggestionStyle>>,
359 file: FileId,
360 ) -> Self {
361 let style = style
362 .into()
363 .unwrap_or_else(|| Self::auto_suggestion_style(&span, msg));
364 let span = FileSpan {
365 file,
366 range: span.as_range(),
367 };
368 let suggestion = CodeSuggestion {
369 substitution: SuggestionChange::String(suggestion.into()),
370 applicability,
371 msg: msg.to_string(),
372 labels: vec![],
373 span,
374 style,
375 };
376 self.suggestions.push(suggestion);
377 self
378 }
379
380 pub fn footer(mut self, severity: Severity, msg: impl Into<String>) -> Self {
382 self.footers.push(Footer {
383 msg: msg.into(),
384 severity,
385 });
386 self
387 }
388
389 pub fn footer_help(self, msg: impl Into<String>) -> Self {
391 self.footer(Severity::Help, msg)
392 }
393
394 pub fn footer_note(self, msg: impl Into<String>) -> Self {
396 self.footer(Severity::Note, msg)
397 }
398}
399
400#[derive(Debug, Clone, PartialEq, Hash)]
403pub struct SubDiagnostic {
404 pub severity: Severity,
405 pub msg: String,
406 pub span: FileSpan,
407}
408
409#[derive(Debug, Clone, PartialEq, Hash)]
411pub struct Footer {
412 pub msg: String,
413 pub severity: Severity,
414}