rajac_diagnostics/
render_diagnostic.rs1use crate::diagnostic::Diagnostic;
2use crate::severity::Severity;
3use rajac_base::shared_string::SharedString;
4
5#[allow(unused_imports)]
6use crate::source_chunk::SourceChunk;
7
8pub fn render_diagnostic(diagnostic: &Diagnostic) -> SharedString {
9 render_diagnostics(std::iter::once(diagnostic))
10}
11
12pub fn render_diagnostics<'a>(
13 diagnostics: impl IntoIterator<Item = &'a Diagnostic>,
14) -> SharedString {
15 use annotate_snippets::{
16 AnnotationKind, Group, Level, Renderer, Snippet, renderer::DecorStyle,
17 };
18
19 let mut groups = Vec::new();
20
21 for diagnostic in diagnostics {
22 let level = match diagnostic.severity {
23 Severity::Error => Level::ERROR,
24 Severity::Warning => Level::WARNING,
25 Severity::Note => Level::NOTE,
26 Severity::Help => Level::HELP,
27 };
28
29 let title = level.primary_title(&*diagnostic.message);
30
31 let mut group = Group::with_title(title);
32
33 for chunk in &diagnostic.chunks {
34 let path = chunk.path.as_str().to_string();
35 let mut snippet: Snippet<'static, annotate_snippets::Annotation<'static>> =
36 Snippet::source(chunk.fragment.as_str().to_string())
37 .line_start(chunk.line)
38 .path(path);
39
40 for annotation in &chunk.annotations {
41 snippet = snippet.annotation(
42 AnnotationKind::Primary
43 .span(annotation.span.0.clone())
44 .label(annotation.message.as_str().to_string()),
45 );
46 }
47
48 group = group.element(snippet);
49 }
50
51 groups.push(group);
52 }
53
54 let renderer = Renderer::styled().decor_style(DecorStyle::Unicode);
55 SharedString::from(renderer.render(&groups).to_string())
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use crate::annotation::Annotation;
62 use crate::span::Span;
63 use expect_test::expect;
64 use rajac_base::file_path::FilePath;
65 use strip_ansi::strip_ansi;
66
67 #[test]
68 fn test_render_error() {
70 let diagnostic = Diagnostic {
71 severity: Severity::Error,
72 message: "expected type, found `i32`".into(),
73 chunks: vec![SourceChunk {
74 path: FilePath::new("test.java"),
75 fragment: "let x: String = 42;".into(),
76 offset: 0,
77 line: 1,
78 annotations: vec![],
79 }],
80 };
81
82 let output = render_diagnostic(&diagnostic);
83 let stripped = strip_ansi(&output);
84
85 let expected = expect![[r#"
86 error: expected type, found `i32`
87 ╭▸ test.java
88 │"#]];
89 expected.assert_eq(&stripped);
90 }
91
92 #[test]
93 fn test_render_warning() {
95 let diagnostic = Diagnostic {
96 severity: Severity::Warning,
97 message: "unused variable `x`".into(),
98 chunks: vec![SourceChunk {
99 path: FilePath::new("test.java"),
100 fragment: "let x = 42;".into(),
101 offset: 0,
102 line: 5,
103 annotations: vec![],
104 }],
105 };
106
107 let output = render_diagnostic(&diagnostic);
108 let stripped = strip_ansi(&output);
109
110 let expected = expect![[r#"
111 warning: unused variable `x`
112 ╭▸ test.java
113 │"#]];
114 expected.assert_eq(&stripped);
115 }
116
117 #[test]
118 fn test_render_with_annotation() {
120 let diagnostic = Diagnostic {
121 severity: Severity::Error,
122 message: "mismatched types".into(),
123 chunks: vec![SourceChunk {
124 path: FilePath::new("test.java"),
125 fragment: "let x: String = 42;".into(),
126 offset: 0,
127 line: 1,
128 annotations: vec![Annotation {
129 span: Span(9..15),
130 message: "expected `String` but found `i32`".into(),
131 }],
132 }],
133 };
134
135 let output = render_diagnostic(&diagnostic);
136 let stripped = strip_ansi(&output);
137
138 let expected = expect![[r#"
139 error: mismatched types
140 ╭▸ test.java:1:10
141 │
142 1 │ let x: String = 42;
143 ╰╴ ━━━━━━ expected `String` but found `i32`"#]];
144 expected.assert_eq(&stripped);
145 }
146
147 #[test]
148 fn test_render_multiple_chunks() {
150 let diagnostic = Diagnostic {
151 severity: Severity::Error,
152 message: "undefined variable".into(),
153 chunks: vec![
154 SourceChunk {
155 path: FilePath::new("main.java"),
156 fragment: "fn main() {}".into(),
157 offset: 0,
158 line: 1,
159 annotations: vec![],
160 },
161 SourceChunk {
162 path: FilePath::new("main.java"),
163 fragment: " x;".into(),
164 offset: 12,
165 line: 2,
166 annotations: vec![],
167 },
168 ],
169 };
170
171 let output = render_diagnostic(&diagnostic);
172 let stripped = strip_ansi(&output);
173
174 let expected = expect![[r#"
175 error: undefined variable
176 ╭▸ main.java
177 │
178 │
179 ⸬ main.java
180 │"#]];
181 expected.assert_eq(&stripped);
182 }
183}