use crate::diagnostics::diagnostic::Diagnostic;
pub fn render_report(source_name: &str, source: &str, diagnostics: &[Diagnostic]) -> String {
let mut out = String::new();
let lines: Vec<&str> = source.lines().collect();
for (i, diag) in diagnostics.iter().enumerate() {
if i > 0 {
out.push('\n');
}
if let Some(code) = diag.code {
out.push_str(&format!("{}[{}]: {}\n", diag.severity, code, diag.message));
} else {
out.push_str(&format!("{}: {}\n", diag.severity, diag.message));
}
if let Some(label) = &diag.primary_label {
let line_idx = label.span.start.line as usize;
let col_idx = label.span.start.column.max(1) as usize;
let pad = line_idx.to_string().len().max(2);
out.push_str(&format!(
"{:pad$}--> {}:{}:{}\n",
"",
source_name,
line_idx,
col_idx,
pad = pad
));
out.push_str(&format!("{:pad$} |\n", "", pad = pad));
if line_idx >= 1 && line_idx <= lines.len() {
let line_content = lines[line_idx - 1];
out.push_str(&format!(
"{:pad$} | {}\n",
line_idx,
line_content,
pad = pad
));
let start_col = col_idx.saturating_sub(1);
let end_col = if label.span.end.line == label.span.start.line {
(label.span.end.column as usize).max(col_idx)
} else {
col_idx
};
let width = (end_col - col_idx).max(1);
let spaces = " ".repeat(start_col);
let carets = "^".repeat(width);
if !label.message.is_empty() {
out.push_str(&format!(
"{:pad$} | {}{} {}\n",
"",
spaces,
carets,
label.message,
pad = pad
));
} else {
out.push_str(&format!("{:pad$} | {}{}\n", "", spaces, carets, pad = pad));
}
}
out.push_str(&format!("{:pad$} |\n", "", pad = pad));
}
for sec in &diag.secondary_labels {
let line_idx = sec.span.start.line as usize;
if line_idx >= 1 && line_idx <= lines.len() {
let pad = line_idx.to_string().len();
let line_content = lines[line_idx - 1];
out.push_str(&format!(
"{:pad$} | {}\n",
line_idx,
line_content,
pad = pad
));
let col = sec.span.start.column.max(1) as usize - 1;
let spaces = " ".repeat(col);
out.push_str(&format!(
"{:pad$} | {}--- {}\n",
"",
spaces,
sec.message,
pad = pad
));
}
}
let pad = diag
.primary_label
.as_ref()
.map(|l| (l.span.start.line as usize).to_string().len().max(2))
.unwrap_or(2);
for note in &diag.notes {
out.push_str(&format!("{:pad$} = note: {}\n", "", note, pad = pad));
}
if let Some(help) = &diag.help {
out.push_str(&format!("{:pad$} = help: {}\n", "", help, pad = pad));
}
}
out
}