nova_diagnostics/
render.rs1use 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
15pub 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 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
71pub 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
131fn 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}