probl_syntax/
diagnostic.rs1use crate::span::{SourceFile, Span};
4use ariadne::{Config, IndexType, Label, Report, ReportKind, Source};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum Severity {
8 Error,
9 Warning,
10}
11
12#[derive(Clone, Debug)]
13pub struct Diagnostic {
14 pub severity: Severity,
15 pub message: String,
16 pub span: Span,
17 pub label: Option<String>,
19 pub notes: Vec<String>,
20 pub help: Option<String>,
21}
22
23impl Diagnostic {
24 pub fn error(span: Span, message: impl Into<String>) -> Diagnostic {
25 Diagnostic {
26 severity: Severity::Error,
27 message: message.into(),
28 span,
29 label: None,
30 notes: Vec::new(),
31 help: None,
32 }
33 }
34
35 pub fn warning(span: Span, message: impl Into<String>) -> Diagnostic {
36 Diagnostic {
37 severity: Severity::Warning,
38 ..Diagnostic::error(span, message)
39 }
40 }
41
42 pub fn with_label(mut self, label: impl Into<String>) -> Diagnostic {
43 self.label = Some(label.into());
44 self
45 }
46
47 pub fn with_note(mut self, note: impl Into<String>) -> Diagnostic {
48 self.notes.push(note.into());
49 self
50 }
51
52 pub fn with_help(mut self, help: impl Into<String>) -> Diagnostic {
53 self.help = Some(help.into());
54 self
55 }
56
57 pub fn help(&mut self, help: impl Into<String>) -> &mut Diagnostic {
59 self.help = Some(help.into());
60 self
61 }
62
63 pub fn note(&mut self, note: impl Into<String>) -> &mut Diagnostic {
65 self.notes.push(note.into());
66 self
67 }
68
69 pub fn is_error(&self) -> bool {
70 self.severity == Severity::Error
71 }
72
73 pub fn render(&self, file: &SourceFile, color: bool) -> String {
75 let name = file.name.as_str();
76 let kind = match self.severity {
77 Severity::Error => ReportKind::Error,
78 Severity::Warning => ReportKind::Warning,
79 };
80 let range = clamp(self.span, file.text.len());
81 let label = Label::new((name, range.clone())).with_message(self.label.as_deref().unwrap_or("here"));
83 let mut report = Report::build(kind, (name, range))
84 .with_config(Config::default().with_color(color).with_index_type(IndexType::Byte))
85 .with_message(&self.message)
86 .with_label(label);
87 for note in &self.notes {
88 report = report.with_note(note);
89 }
90 if let Some(help) = &self.help {
91 report = report.with_help(help);
92 }
93 let mut out = Vec::new();
94 report
95 .finish()
96 .write((name, Source::from(file.text.as_str())), &mut out)
97 .expect("writing to a Vec cannot fail");
98 String::from_utf8_lossy(&out).into_owned()
99 }
100}
101
102pub fn render_all(diagnostics: &[Diagnostic], file: &SourceFile, color: bool) -> String {
103 diagnostics.iter().map(|d| d.render(file, color)).collect()
104}
105
106fn clamp(span: Span, len: usize) -> std::ops::Range<usize> {
107 let lo = (span.lo as usize).min(len);
108 let hi = (span.hi as usize).clamp(lo, len);
109 lo..hi
110}