1use std::path::Path;
19
20use ariadne::Source;
21use bynk_syntax::error::Severity;
22use bynk_syntax::{CompileError, span};
23
24pub fn render_errors(errors: &[CompileError], source: &str, filename: &str) -> String {
27 let mut out = Vec::new();
28 let mut cache = (filename, Source::from(source));
29 for err in errors {
30 err.report_for(filename, source.len())
31 .write(&mut cache, &mut out)
32 .expect("write to Vec<u8> cannot fail");
33 }
34 String::from_utf8_lossy(&out).into_owned()
35}
36
37pub fn render_errors_plain(errors: &[CompileError], source: &str, filename: &str) -> String {
42 let mut out = Vec::new();
43 let mut cache = (filename, Source::from(source));
44 for err in errors {
45 err.report_plain_for(filename, source.len())
46 .write(&mut cache, &mut out)
47 .expect("write to Vec<u8> cannot fail");
48 }
49 String::from_utf8_lossy(&out).into_owned()
50}
51
52pub fn print_errors(errors: &[CompileError], source: &str, filename: &str) {
54 let mut cache = (filename, Source::from(source));
55 for err in errors {
56 let _ = err.report_for(filename, source.len()).eprint(&mut cache);
57 }
58}
59
60pub fn print_project_errors(root: &Path, errors: &[CompileError]) {
64 let _ = root;
65 for err in errors {
66 eprintln!("[{}] {}", err.category, err.message);
67 for note in &err.notes {
68 eprintln!(" note: {note}");
69 }
70 }
71}
72
73pub fn print_errors_short(errors: &[CompileError], source: &str, filename: &str) {
79 eprint!("{}", render_errors_short(errors, source, filename));
80}
81
82pub fn render_errors_short(errors: &[CompileError], source: &str, filename: &str) -> String {
86 let mut out = String::new();
87 for err in errors {
88 out.push_str(&short_line(filename, source, err));
89 out.push('\n');
90 }
91 out
92}
93
94pub fn short_line(filename: &str, source: &str, err: &CompileError) -> String {
98 let (line, col) = span::line_col(source, err.span.start);
99 format!(
100 "{filename}:{line}:{col}: {}[{}]: {}",
101 severity_word(err),
102 err.category,
103 err.message
104 )
105}
106
107pub fn severity_word(err: &CompileError) -> &'static str {
109 match Severity::for_error(err) {
110 Severity::Error => "error",
111 Severity::Warning => "warning",
112 }
113}
114
115pub fn render_project_errors(errors: &[CompileError]) -> String {
118 let mut out = String::new();
119 for err in errors {
120 out.push_str(&format!("[{}] {}\n", err.category, err.message));
121 for note in &err.notes {
122 out.push_str(&format!(" note: {note}\n"));
123 }
124 }
125 out
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use bynk_syntax::span::Span;
132
133 #[test]
138 fn underline_is_byte_indexed_on_non_ascii_lines() {
139 let source = "-- caféxyz bad\n";
142 let start = source.find("bad").unwrap();
143 let err = CompileError::new(
144 "bynk.test.example",
145 Span::new(start, start + 3),
146 "bad thing",
147 );
148 let rendered = render_errors_plain(&[err], source, "probe.bynk");
149 let source_line = rendered
150 .lines()
151 .find(|l| l.contains("caféxyz"))
152 .expect("snippet line present");
153 let marker_line = rendered
154 .lines()
155 .find(|l| l.contains('┬'))
156 .expect("marker line present");
157 let col_of = |line: &str, target: char| line.chars().take_while(|&c| c != target).count();
158 let b_col = col_of(source_line, 'b');
161 let caret_col = col_of(marker_line, '┬');
162 assert!(
163 (b_col..b_col + 3).contains(&caret_col),
164 "caret at display column {caret_col}, expected within `bad` at {b_col}..{}:\n{rendered}",
165 b_col + 3
166 );
167 }
168
169 #[test]
173 fn out_of_bounds_label_demotes_to_note() {
174 let source = "commons demo\n";
175 let err = CompileError::new("bynk.test.example", Span::new(0, 7), "problem here")
176 .with_label(
177 Span::new(5_000, 5_010),
178 "parameter declared here (in another file)",
179 );
180 let rendered = render_errors_plain(&[err], source, "probe.bynk");
181 assert!(
182 rendered.contains("parameter declared here"),
183 "label text survives as a note:\n{rendered}"
184 );
185 }
186}