Skip to main content

ravenrs_lint/
report.rs

1//! Diagnostics written the way the compiler writes them, or as JSON for tools.
2
3use std::collections::HashSet;
4use std::fmt::Write as _;
5
6use crate::{Diagnostic, Level, Lint};
7
8/// The diagnostics as the compiler would print them, followed by a summary line when `summary`.
9pub fn render(diagnostics: &[Diagnostic], summary: bool) -> String {
10    let mut out = String::new();
11    let mut explained: HashSet<Lint> = HashSet::new();
12    for d in diagnostics {
13        let gutter = d.line.to_string().len();
14        let pad = " ".repeat(gutter);
15        let _ = writeln!(out, "{}[ravenrs::{}]: {}", d.level.label(), d.lint.name(), d.message);
16        let _ = writeln!(out, "{pad}--> {}:{}:{}", d.file.display(), d.line, d.column);
17        let _ = writeln!(out, "{pad} |");
18        let _ = writeln!(out, "{} | {}", d.line, d.snippet);
19        let marks = "^".repeat(d.end_column.saturating_sub(d.column).max(1));
20        let _ = writeln!(out, "{pad} | {}{marks}", " ".repeat(d.column.saturating_sub(1)));
21        let _ = writeln!(out, "{pad} |");
22        let _ = writeln!(out, "{pad} = help: {}", d.help);
23        if explained.insert(d.lint) {
24            let _ = writeln!(
25                out,
26                "{pad} = note: `ravenrs::{}` ({}) is {}; fix the code rather than silencing it",
27                d.lint.name(),
28                d.lint.summary(),
29                match d.level {
30                    Level::Deny => "denied",
31                    _ => "a warning",
32                },
33            );
34        }
35        out.push('\n');
36    }
37    if summary && !diagnostics.is_empty() {
38        let errors = diagnostics.iter().filter(|d| d.level == Level::Deny).count();
39        let warnings = diagnostics.len() - errors;
40        let plural = |n: usize, word: &str| format!("{n} {word}{}", if n == 1 { "" } else { "s" });
41        let _ = writeln!(
42            out,
43            "error: ravenrs-lint found {} and {}. Fix each one as its help says, then run it again.",
44            plural(errors, "error"),
45            plural(warnings, "warning"),
46        );
47    }
48    out
49}
50
51/// One JSON object per line, for editors and agents.
52pub fn render_json(diagnostics: &[Diagnostic]) -> String {
53    let mut out = String::new();
54    for d in diagnostics {
55        let value = serde_json::json!({
56            "lint": format!("ravenrs::{}", d.lint.name()),
57            "level": d.level.label(),
58            "file": d.file.display().to_string(),
59            "line": d.line,
60            "column": d.column,
61            "end_column": d.end_column,
62            "message": d.message,
63            "help": d.help,
64        });
65        out.push_str(&value.to_string());
66        out.push('\n');
67    }
68    out
69}