1use std::fmt;
8
9use ariadne::{Color, Config, IndexType, Label as ALabel, Report, ReportKind};
10
11use crate::source::{FileId, SourceMap, Span};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
14pub enum Severity {
15 Warning,
16 Error,
17}
18
19#[derive(Clone, Debug)]
20pub struct Label {
21 pub span: Span,
22 pub text: String,
23}
24
25#[derive(Clone, Debug)]
26pub struct Diagnostic {
27 pub code: &'static str,
28 pub severity: Severity,
29 pub message: String,
30 pub labels: Vec<Label>,
32 pub notes: Vec<String>,
33 pub help: Option<String>,
34}
35
36impl Diagnostic {
37 pub fn error(code: &'static str, message: impl Into<String>) -> Self {
38 Diagnostic { code, severity: Severity::Error, message: message.into(), labels: vec![], notes: vec![], help: None }
39 }
40
41 pub fn warning(code: &'static str, message: impl Into<String>) -> Self {
42 Diagnostic { code, severity: Severity::Warning, message: message.into(), labels: vec![], notes: vec![], help: None }
43 }
44
45 pub fn label(mut self, span: Span, text: impl Into<String>) -> Self {
46 self.labels.push(Label { span, text: text.into() });
47 self
48 }
49
50 pub fn note(mut self, text: impl Into<String>) -> Self {
51 self.notes.push(text.into());
52 self
53 }
54
55 pub fn help(mut self, text: impl Into<String>) -> Self {
56 self.help = Some(text.into());
57 self
58 }
59
60 pub fn primary_span(&self) -> Option<Span> {
61 self.labels.first().map(|l| l.span)
62 }
63
64 pub fn is_error(&self) -> bool {
65 self.severity == Severity::Error
66 }
67
68 pub fn render(&self, sm: &SourceMap, color: bool) -> String {
71 let kind = match self.severity {
72 Severity::Error => ReportKind::Error,
73 Severity::Warning => ReportKind::Warning,
74 };
75 let primary = self.labels.first().map(|l| l.span);
76 let anchor = primary.map(|s| (s.file, s.range())).unwrap_or((FileId(0), 0..0));
77 let config = Config::default()
78 .with_color(color)
79 .with_index_type(IndexType::Byte)
80 .with_char_set(if color { ariadne::CharSet::Unicode } else { ariadne::CharSet::Ascii });
81 let mut report = Report::build(kind, anchor).with_code(self.code).with_message(&self.message).with_config(config);
82 for (i, l) in self.labels.iter().enumerate() {
83 let mut label = ALabel::new((l.span.file, l.span.range())).with_message(&l.text).with_order(i as i32);
84 if color {
85 label = label.with_color(if i == 0 { Color::Red } else { Color::Blue });
86 }
87 report = report.with_label(label);
88 }
89 for n in &self.notes {
90 report = report.with_note(n);
91 }
92 if let Some(h) = &self.help {
93 report = report.with_help(h);
94 }
95 let mut out = Vec::new();
96 report.finish().write(Cache(sm), &mut out).expect("rendering a diagnostic");
97 String::from_utf8(out).expect("ariadne emits UTF-8")
98 }
99}
100
101impl fmt::Display for Diagnostic {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 write!(f, "[{}] {}", self.code, self.message)
104 }
105}
106
107struct Cache<'a>(&'a SourceMap);
108
109impl ariadne::Cache<FileId> for Cache<'_> {
110 type Storage = String;
111
112 fn fetch(&mut self, id: &FileId) -> Result<&ariadne::Source<String>, impl fmt::Debug> {
113 Ok::<_, std::convert::Infallible>(&self.0.file(*id).ariadne)
114 }
115
116 fn display<'b>(&self, id: &'b FileId) -> Option<impl fmt::Display + 'b> {
117 Some(self.0.file(*id).name.clone())
118 }
119}
120
121pub fn render_all(diags: &[Diagnostic], sm: &SourceMap, color: bool) -> String {
123 diags.iter().map(|d| d.render(sm, color)).collect::<Vec<_>>().join("")
124}
125
126pub fn has_errors(diags: &[Diagnostic]) -> bool {
127 diags.iter().any(Diagnostic::is_error)
128}