use crate::diagnostic::{DiagnosticSeverity, SourceSpan};
use crate::error::Error;
use ariadne::{Color, Label, Report, ReportKind};
#[must_use]
pub fn error_to_ariadne_report<'a>(
err: &Error,
filename: &'a str,
source: &str,
) -> Report<'a, (&'a str, core::ops::Range<usize>)> {
let diagnostic = err.diagnostic();
let kind = match diagnostic.severity() {
DiagnosticSeverity::Error => ReportKind::Error,
DiagnosticSeverity::Warning => ReportKind::Warning,
DiagnosticSeverity::Information | DiagnosticSeverity::Hint => ReportKind::Advice,
};
let mut builder = Report::build(kind, (filename, 0..source.len()))
.with_code(diagnostic.code())
.with_message(diagnostic.message());
for diagnostic_label in diagnostic.labels() {
let span = label_span(diagnostic_label.span(), source);
let color = if diagnostic_label.is_primary() {
Color::Red
} else {
Color::Yellow
};
builder = builder.with_label(
Label::new((filename, span))
.with_message(diagnostic_label.message())
.with_color(color),
);
}
builder.finish()
}
fn label_span(span: SourceSpan, source: &str) -> core::ops::Range<usize> {
let mut start = span.offset().min(source.len());
while start > 0 && !source.is_char_boundary(start) {
start -= 1;
}
if start >= source.len() {
return start..start;
}
let mut end = span.end().min(source.len());
if end <= start {
end = source[start..]
.chars()
.next()
.map_or(start + 1, |character| start + character.len_utf8());
}
while end < source.len() && !source.is_char_boundary(end) {
end += 1;
}
start..end
}