1use std::io::IsTerminal;
2
3use crate::{Location, Span};
4use codespan_reporting::diagnostic::{Diagnostic, Label};
5use codespan_reporting::files::Files;
6use codespan_reporting::term;
7use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct CustomDiagnostic {
11 pub file: fm::FileId,
12 pub message: String,
13 pub secondaries: Vec<CustomLabel>,
14 pub notes: Vec<String>,
15 pub kind: DiagnosticKind,
16 pub deprecated: bool,
17 pub unnecessary: bool,
18
19 pub call_stack: Vec<Location>,
22}
23
24#[derive(Debug, Copy, Clone, PartialEq, Eq)]
25pub enum DiagnosticKind {
26 Error,
27 Bug,
28 Warning,
29 Info,
30}
31
32#[derive(Debug, Copy, Clone)]
34pub struct ReportedErrors {
35 pub error_count: u32,
36}
37
38impl CustomDiagnostic {
39 pub fn from_message(msg: &str, file: fm::FileId) -> CustomDiagnostic {
40 Self {
41 file,
42 message: msg.to_owned(),
43 secondaries: Vec::new(),
44 notes: Vec::new(),
45 kind: DiagnosticKind::Error,
46 deprecated: false,
47 unnecessary: false,
48 call_stack: Default::default(),
49 }
50 }
51
52 fn simple_with_kind(
53 primary_message: String,
54 secondary_message: String,
55 secondary_location: Location,
56 kind: DiagnosticKind,
57 ) -> CustomDiagnostic {
58 CustomDiagnostic {
59 file: secondary_location.file,
60 message: primary_message,
61 secondaries: vec![CustomLabel::new(secondary_message, secondary_location)],
62 notes: Vec::new(),
63 kind,
64 deprecated: false,
65 unnecessary: false,
66 call_stack: Default::default(),
67 }
68 }
69
70 pub fn simple_error(
71 primary_message: String,
72 secondary_message: String,
73 secondary_location: Location,
74 ) -> CustomDiagnostic {
75 Self::simple_with_kind(
76 primary_message,
77 secondary_message,
78 secondary_location,
79 DiagnosticKind::Error,
80 )
81 }
82
83 pub fn simple_warning(
84 primary_message: String,
85 secondary_message: String,
86 secondary_location: Location,
87 ) -> CustomDiagnostic {
88 Self::simple_with_kind(
89 primary_message,
90 secondary_message,
91 secondary_location,
92 DiagnosticKind::Warning,
93 )
94 }
95
96 pub fn simple_info(
97 primary_message: String,
98 secondary_message: String,
99 secondary_location: Location,
100 ) -> CustomDiagnostic {
101 Self::simple_with_kind(
102 primary_message,
103 secondary_message,
104 secondary_location,
105 DiagnosticKind::Info,
106 )
107 }
108
109 pub fn simple_bug(
110 primary_message: String,
111 secondary_message: String,
112 secondary_location: Location,
113 ) -> CustomDiagnostic {
114 CustomDiagnostic {
115 file: secondary_location.file,
116 message: primary_message,
117 secondaries: vec![CustomLabel::new(secondary_message, secondary_location)],
118 notes: Vec::new(),
119 kind: DiagnosticKind::Bug,
120 deprecated: false,
121 unnecessary: false,
122 call_stack: Default::default(),
123 }
124 }
125
126 pub fn with_call_stack(mut self, call_stack: Vec<Location>) -> Self {
127 self.call_stack = call_stack;
128 self
129 }
130
131 pub fn add_note(&mut self, message: String) {
132 self.notes.push(message);
133 }
134
135 pub fn add_secondary(&mut self, message: String, location: Location) {
136 self.secondaries.push(CustomLabel::new(message, location));
137 }
138
139 pub fn is_error(&self) -> bool {
140 matches!(self.kind, DiagnosticKind::Error)
141 }
142
143 pub fn is_warning(&self) -> bool {
144 matches!(self.kind, DiagnosticKind::Warning)
145 }
146
147 pub fn is_info(&self) -> bool {
148 matches!(self.kind, DiagnosticKind::Info)
149 }
150
151 pub fn is_bug(&self) -> bool {
152 matches!(self.kind, DiagnosticKind::Bug)
153 }
154}
155
156impl std::fmt::Display for CustomDiagnostic {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 write!(f, "{}", self.message)?;
159
160 for secondary in &self.secondaries {
161 write!(f, "\nsecondary: {}", secondary.message)?;
162 }
163
164 for note in &self.notes {
165 write!(f, "\nnote: {note}")?;
166 }
167
168 Ok(())
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct CustomLabel {
174 pub message: String,
175 pub location: Location,
176}
177
178impl CustomLabel {
179 fn new(message: String, location: Location) -> CustomLabel {
180 CustomLabel { message, location }
181 }
182}
183
184pub fn report_all<'files>(
187 files: &'files impl Files<'files, FileId = fm::FileId>,
188 diagnostics: &[CustomDiagnostic],
189 deny_warnings: bool,
190 silence_warnings: bool,
191) -> ReportedErrors {
192 let (warnings_and_bugs, mut errors): (Vec<_>, _) =
194 diagnostics.iter().partition(|item| !item.is_error());
195
196 let (warnings, mut bugs): (Vec<_>, _) =
197 warnings_and_bugs.iter().partition(|item| item.is_warning());
198 let mut diagnostics = if silence_warnings { Vec::new() } else { warnings };
199 diagnostics.append(&mut bugs);
200 diagnostics.append(&mut errors);
201
202 let error_count =
203 diagnostics.iter().map(|error| error.report(files, deny_warnings) as u32).sum();
204
205 ReportedErrors { error_count }
206}
207
208impl CustomDiagnostic {
209 pub fn report<'files>(
211 &self,
212 files: &'files impl Files<'files, FileId = fm::FileId>,
213 deny_warnings: bool,
214 ) -> bool {
215 report(files, self, deny_warnings)
216 }
217}
218
219pub fn report<'files>(
221 files: &'files impl Files<'files, FileId = fm::FileId>,
222 custom_diagnostic: &CustomDiagnostic,
223 deny_warnings: bool,
224) -> bool {
225 let color_choice =
226 if std::io::stderr().is_terminal() { ColorChoice::Auto } else { ColorChoice::Never };
227 let writer = StandardStream::stderr(color_choice);
228 let config = term::Config::default();
229
230 let stack_trace = stack_trace(files, &custom_diagnostic.call_stack);
231 let diagnostic = convert_diagnostic(custom_diagnostic, stack_trace, deny_warnings);
232 term::emit(&mut writer.lock(), &config, files, &diagnostic).unwrap();
233
234 deny_warnings || custom_diagnostic.is_error()
235}
236
237fn convert_diagnostic(
238 cd: &CustomDiagnostic,
239 stack_trace: String,
240 deny_warnings: bool,
241) -> Diagnostic<fm::FileId> {
242 let diagnostic = match (cd.kind, deny_warnings) {
243 (DiagnosticKind::Warning, false) => Diagnostic::warning(),
244 (DiagnosticKind::Info, _) => Diagnostic::note(),
245 (DiagnosticKind::Bug, ..) => Diagnostic::bug(),
246 _ => Diagnostic::error(),
247 };
248
249 let secondary_labels = cd
250 .secondaries
251 .iter()
252 .map(|custom_label| {
253 let location = custom_label.location;
254 let span = location.span;
255 let start_span = span.start() as usize;
256 let end_span = span.end() as usize;
257 let file = location.file;
258 Label::secondary(file, start_span..end_span).with_message(&custom_label.message)
259 })
260 .collect();
261
262 let mut notes = cd.notes.clone();
263 notes.push(stack_trace);
264
265 diagnostic.with_message(&cd.message).with_labels(secondary_labels).with_notes(notes)
266}
267
268pub fn stack_trace<'files>(
269 files: &'files impl Files<'files, FileId = fm::FileId>,
270 call_stack: &[Location],
271) -> String {
272 if call_stack.is_empty() {
273 return String::new();
274 }
275
276 let mut result = "Call stack:\n".to_string();
277
278 for (i, call_item) in call_stack.iter().enumerate() {
279 let path = files.name(call_item.file).expect("should get file path");
280 let source = files.source(call_item.file).expect("should get file source");
281
282 let (line, column) = line_and_column_from_span(source.as_ref(), &call_item.span);
283 result += &format!("{}. {}:{}:{}\n", i + 1, path, line, column);
284 }
285
286 result
287}
288
289pub fn line_and_column_from_span(source: &str, span: &Span) -> (u32, u32) {
290 let mut line = 1;
291 let mut column = 0;
292
293 for (i, char) in source.chars().enumerate() {
294 column += 1;
295
296 if char == '\n' {
297 line += 1;
298 column = 0;
299 }
300
301 if span.start() <= i as u32 {
302 break;
303 }
304 }
305
306 (line, column)
307}