1use std::collections::BTreeMap;
4use std::fmt::{self, Write};
5use std::sync::Arc;
6
7#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct Span {
10 pub file: Arc<str>,
11 pub line: u32,
13}
14
15impl Span {
16 pub fn new(file: &str, line: u32) -> Self {
17 Span { file: file.into(), line }
18 }
19}
20
21impl fmt::Display for Span {
22 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23 write!(f, "{}:{}", self.file, self.line)
24 }
25}
26
27#[derive(Clone, Debug, Default)]
29pub struct Sources(BTreeMap<Arc<str>, String>);
30
31impl Sources {
32 pub fn insert(&mut self, file: Arc<str>, text: String) {
33 self.0.insert(file, text);
34 }
35
36 pub fn line(&self, span: &Span) -> Option<&str> {
37 self.0.get(&span.file)?.lines().nth(span.line.checked_sub(1)? as usize)
38 }
39
40 pub fn files(&self) -> impl Iterator<Item = &str> {
41 self.0.keys().map(|k| &**k)
42 }
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
46pub enum Severity {
47 Error,
48 Warning,
49 Note,
50}
51
52#[derive(Clone, Debug)]
53pub struct Label {
54 pub span: Span,
55 pub message: String,
56}
57
58#[derive(Clone, Debug)]
59pub struct Diagnostic {
60 pub severity: Severity,
61 pub code: &'static str,
63 pub message: String,
64 pub labels: Vec<Label>,
65 pub notes: Vec<String>,
66 pub help: Option<String>,
67}
68
69impl Diagnostic {
70 pub fn new(severity: Severity, code: &'static str, message: impl Into<String>) -> Self {
71 Diagnostic { severity, code, message: message.into(), labels: vec![], notes: vec![], help: None }
72 }
73
74 pub fn error(code: &'static str, message: impl Into<String>) -> Self {
75 Self::new(Severity::Error, code, message)
76 }
77
78 pub fn warning(code: &'static str, message: impl Into<String>) -> Self {
79 Self::new(Severity::Warning, code, message)
80 }
81
82 pub fn note(code: &'static str, message: impl Into<String>) -> Self {
83 Self::new(Severity::Note, code, message)
84 }
85
86 pub fn at(mut self, span: &Span, message: impl Into<String>) -> Self {
87 self.labels.push(Label { span: span.clone(), message: message.into() });
88 self
89 }
90
91 pub fn note_that(mut self, note: impl Into<String>) -> Self {
92 self.notes.push(note.into());
93 self
94 }
95
96 pub fn help(mut self, help: impl Into<String>) -> Self {
97 self.help = Some(help.into());
98 self
99 }
100
101 pub fn is_error(&self) -> bool {
102 self.severity == Severity::Error
103 }
104
105 pub fn sort_key(&self) -> (Option<&Span>, Severity) {
107 (self.labels.first().map(|l| &l.span), self.severity)
108 }
109
110 pub fn render(&self, sources: &Sources) -> String {
112 let mut s = String::new();
113 let level = match self.severity {
114 Severity::Error => "error",
115 Severity::Warning => "warning",
116 Severity::Note => "note",
117 };
118 let _ = writeln!(s, "{level}[{}]: {}", self.code, self.message);
119 for label in &self.labels {
120 let _ = writeln!(s, " --> {}", label.span);
121 if let Some(line) = sources.line(&label.span) {
122 let _ = writeln!(s, " | {}", line.trim_end());
123 }
124 if !label.message.is_empty() {
125 let _ = writeln!(s, " = {}", label.message);
126 }
127 }
128 for note in &self.notes {
129 let _ = writeln!(s, " = note: {note}");
130 }
131 if let Some(help) = &self.help {
132 let _ = writeln!(s, " = help: {help}");
133 }
134 s
135 }
136}
137
138pub fn has_errors(diagnostics: &[Diagnostic]) -> bool {
139 diagnostics.iter().any(Diagnostic::is_error)
140}
141
142pub fn render_all(diagnostics: &[Diagnostic], sources: &Sources) -> String {
144 let mut sorted: Vec<&Diagnostic> = diagnostics.iter().collect();
145 sorted.sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
146 let mut s = String::new();
147 for d in &sorted {
148 s.push_str(&d.render(sources));
149 s.push('\n');
150 }
151 let errors = diagnostics.iter().filter(|d| d.severity == Severity::Error).count();
152 let warnings = diagnostics.iter().filter(|d| d.severity == Severity::Warning).count();
153 let plural = |n: usize, w: &str| if n == 1 { format!("1 {w}") } else { format!("{n} {w}s") };
154 let _ = writeln!(s, "{}, {}", plural(errors, "error"), plural(warnings, "warning"));
155 s
156}