1use crate::span::{LineIndex, Span};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum Severity {
5 Error,
6 Warning,
7}
8
9#[derive(Debug, Clone)]
10pub struct Label {
11 pub span: Span,
12 pub message: String,
13}
14
15#[derive(Debug, Clone)]
16pub struct Diagnostic {
17 pub severity: Severity,
18 pub message: String,
19 pub span: Span,
20 pub labels: Vec<Label>,
22}
23
24impl Diagnostic {
25 pub fn error(span: Span, message: impl Into<String>) -> Self {
26 Self {
27 severity: Severity::Error,
28 message: message.into(),
29 span,
30 labels: Vec::new(),
31 }
32 }
33
34 pub fn warning(span: Span, message: impl Into<String>) -> Self {
35 Self {
36 severity: Severity::Warning,
37 message: message.into(),
38 span,
39 labels: Vec::new(),
40 }
41 }
42
43 pub fn with_label(mut self, span: Span, message: impl Into<String>) -> Self {
44 self.labels.push(Label {
45 span,
46 message: message.into(),
47 });
48 self
49 }
50}
51
52pub fn render(src: &str, path: &str, diags: &[Diagnostic]) -> String {
54 let index = LineIndex::new(src);
55 let mut out = String::new();
56 for d in diags {
57 let pos = index.line_col(d.span.start);
58 let tag = match d.severity {
59 Severity::Error => "error",
60 Severity::Warning => "warning",
61 };
62 out.push_str(&format!(
63 "{tag}: {msg}\n --> {path}:{line}:{col}\n",
64 msg = d.message,
65 line = pos.line,
66 col = pos.col
67 ));
68 out.push_str(&render_snippet(src, &index, d.span));
69 for label in &d.labels {
70 let lpos = index.line_col(label.span.start);
71 out.push_str(&format!(
72 " note: {} ({path}:{}:{})\n",
73 label.message, lpos.line, lpos.col
74 ));
75 out.push_str(&render_snippet(src, &index, label.span));
76 }
77 out.push('\n');
78 }
79 out
80}
81
82fn render_snippet(src: &str, index: &LineIndex, span: Span) -> String {
83 let pos = index.line_col(span.start);
84 let text = index.line_text(src, pos.line);
85 let width = span.end.saturating_sub(span.start).max(1);
86 let indent = " ".repeat(pos.col - 1);
87 let carets = "^".repeat(width);
88 format!("{:>4} | {text}\n | {indent}{carets}\n", pos.line)
89}