Skip to main content

nova_diagnostics/
render.rs

1//! Renders `Diagnostic` values to stderr using `codespan-reporting`.
2
3use codespan_reporting::{
4    diagnostic::{Diagnostic as CsDiag, Label as CsLabel, Severity as CsSeverity},
5    files::SimpleFiles,
6    term::{
7        self,
8        termcolor::{ColorChoice, StandardStream},
9        Config,
10    },
11};
12
13use crate::{Diagnostic, FileDb, Severity};
14
15/// Render a slice of diagnostics to stderr.
16pub fn emit_all(db: &FileDb, diagnostics: &[Diagnostic]) {
17    let mut files: SimpleFiles<&str, &str> = SimpleFiles::new();
18    let mut id_map = std::collections::HashMap::new();
19
20    // Build codespan file db from our FileDb.
21    for (raw_id, diag) in diagnostics.iter().enumerate() {
22        let _ = raw_id;
23        for label in &diag.labels {
24            let fid = label.span.file;
25            if let std::collections::hash_map::Entry::Vacant(e) = id_map.entry(fid) {
26                if let (Some(name), Some(src)) = (db.get_name(fid), db.get_source(fid)) {
27                    let cs_id = files.add(name, src);
28                    e.insert(cs_id);
29                }
30            }
31        }
32    }
33
34    let writer = StandardStream::stderr(ColorChoice::Auto);
35    let config = Config::default();
36
37    for diag in diagnostics {
38        let cs_severity = match diag.severity {
39            Severity::Error => CsSeverity::Error,
40            Severity::Warning => CsSeverity::Warning,
41            Severity::Note => CsSeverity::Note,
42            Severity::Help => CsSeverity::Help,
43        };
44
45        let labels: Vec<_> = diag
46            .labels
47            .iter()
48            .filter_map(|l| {
49                let cs_file = *id_map.get(&l.span.file)?;
50                let range = l.span.as_range();
51                let cs_label = if l.primary {
52                    CsLabel::primary(cs_file, range)
53                } else {
54                    CsLabel::secondary(cs_file, range)
55                };
56                Some(cs_label.with_message(l.message.clone()))
57            })
58            .collect();
59
60        let cs_diag = CsDiag::new(cs_severity)
61            .with_code(&diag.code)
62            .with_message(&diag.message)
63            .with_labels(labels)
64            .with_notes(notes(diag));
65
66        term::emit(&mut writer.lock(), &config, &files, &cs_diag)
67            .expect("failed to write diagnostic");
68    }
69}
70
71/// Render diagnostics to a `String` (useful for tests and snapshot testing).
72pub fn render_to_string(db: &FileDb, diagnostics: &[Diagnostic]) -> String {
73    use codespan_reporting::term::termcolor::NoColor;
74    use std::io::Write;
75
76    let mut files: SimpleFiles<&str, &str> = SimpleFiles::new();
77    let mut id_map = std::collections::HashMap::new();
78
79    for diag in diagnostics {
80        for label in &diag.labels {
81            let fid = label.span.file;
82            if let std::collections::hash_map::Entry::Vacant(e) = id_map.entry(fid) {
83                if let (Some(name), Some(src)) = (db.get_name(fid), db.get_source(fid)) {
84                    let cs_id = files.add(name, src);
85                    e.insert(cs_id);
86                }
87            }
88        }
89    }
90
91    let mut buf = Vec::new();
92    let mut writer = NoColor::new(&mut buf);
93    let config = Config::default();
94
95    for diag in diagnostics {
96        let cs_severity = match diag.severity {
97            Severity::Error => CsSeverity::Error,
98            Severity::Warning => CsSeverity::Warning,
99            Severity::Note => CsSeverity::Note,
100            Severity::Help => CsSeverity::Help,
101        };
102
103        let labels: Vec<_> = diag
104            .labels
105            .iter()
106            .filter_map(|l| {
107                let cs_file = *id_map.get(&l.span.file)?;
108                let range = l.span.as_range();
109                let cs_label = if l.primary {
110                    CsLabel::primary(cs_file, range)
111                } else {
112                    CsLabel::secondary(cs_file, range)
113                };
114                Some(cs_label.with_message(l.message.clone()))
115            })
116            .collect();
117
118        let cs_diag = CsDiag::new(cs_severity)
119            .with_code(&diag.code)
120            .with_message(&diag.message)
121            .with_labels(labels)
122            .with_notes(notes(diag));
123
124        term::emit(&mut writer, &config, &files, &cs_diag).expect("failed to render diagnostic");
125    }
126
127    let _ = writer.flush();
128    String::from_utf8(buf).expect("diagnostic output is not valid UTF-8")
129}
130
131/// The notes, then each fix's title as `help: …` (spec 3.4b §3.3).
132fn notes(diag: &Diagnostic) -> Vec<String> {
133    diag.notes
134        .iter()
135        .cloned()
136        .chain(diag.fixes.iter().map(|f| format!("help: {}", f.title)))
137        .collect()
138}