1use crate::diagnostics::color::UseColor;
6use crate::diagnostics::{Diagnostic, ErrorCode, SeverityFilter};
7use crate::errors::Error;
8use crate::SourceFiles;
9use codespan_reporting::{
10 diagnostic::Severity,
11 term::{
12 emit,
13 termcolor::{ColorChoice, StandardStream, WriteColor},
14 Chars, Config,
15 },
16};
17use std::cell::RefCell;
18use std::fmt::Debug;
19use std::io::Write;
20use std::ops::{Add, AddAssign};
21use tracing::{error, info, warn};
22
23pub trait Reporter: Debug {
31 fn emit(&self, diagnostic: &Diagnostic, sources: &SourceFiles) -> Result<(), Error>;
35
36 fn emit_without_source(&self, diagnostic: &Diagnostic) -> Result<(), Error> {
37 self.emit(diagnostic, &SourceFiles::new())
38 }
39
40 fn counters(&self) -> ReportCounters;
41
42 fn done(&self, module_name: Option<String>) -> Result<ReportCounters, Error>;
43
44 fn log(&self, diagnostic: &Diagnostic) {
45 match diagnostic.severity {
46 Severity::Bug | Severity::Error => error!(
47 "[{}] {}",
48 diagnostic.code.as_ref().unwrap(),
49 diagnostic.message
50 ),
51 Severity::Warning => warn!(
52 "[{}] {}",
53 diagnostic.code.as_ref().unwrap(),
54 diagnostic.message
55 ),
56 Severity::Note | Severity::Help => info!(
57 "[{}] {}",
58 diagnostic.code.as_ref().unwrap(),
59 diagnostic.message
60 ),
61 }
62 }
63
64 fn severity_filter(&self) -> SeverityFilter;
65
66 fn set_severity_filter(&mut self, filter: SeverityFilter);
67
68 fn is_enabled(&self, level: Severity) -> bool {
69 match self.severity_filter() {
70 SeverityFilter::Bug => level >= Severity::Bug,
71 SeverityFilter::Error => level >= Severity::Error,
72 SeverityFilter::Warning => level >= Severity::Warning,
73 SeverityFilter::Note => level >= Severity::Note,
74 SeverityFilter::Help => level >= Severity::Help,
75 SeverityFilter::None => false,
76 }
77 }
78}
79
80#[derive(Clone, Copy, Debug, Default)]
81pub struct ReportCounters {
82 bugs: u32,
83 errors: u32,
84 warnings: u32,
85 info: u32,
86}
87
88#[derive(Debug)]
89pub struct StandardStreamReporter {
90 stream: StandardStream,
91 filter: SeverityFilter,
92 counters: RefCell<ReportCounters>,
93 config: Config,
94}
95
96#[derive(Debug)]
97pub struct CompactStreamReporter {
98 stream: StandardStream,
99 filter: SeverityFilter,
100 counters: RefCell<ReportCounters>,
101}
102
103#[derive(Debug, Default)]
104pub struct BailoutReporter {
105 filter: SeverityFilter,
106 counters: RefCell<ReportCounters>,
107}
108
109impl PartialEq<Severity> for SeverityFilter {
114 fn eq(&self, other: &Severity) -> bool {
115 matches!(
116 (self, other),
117 (SeverityFilter::Bug, Severity::Bug)
118 | (SeverityFilter::Error, Severity::Error)
119 | (SeverityFilter::Warning, Severity::Warning)
120 | (SeverityFilter::Note, Severity::Note)
121 | (SeverityFilter::Help, Severity::Help)
122 )
123 }
124}
125
126impl From<ErrorCode> for Diagnostic {
129 fn from(code: ErrorCode) -> Self {
130 Self::new(code.severity())
131 .with_code(code.to_string())
132 .with_message(code.message().to_string())
133 }
134}
135
136impl Add for ReportCounters {
139 type Output = ReportCounters;
140
141 fn add(self, rhs: Self) -> Self::Output {
142 Self {
143 bugs: self.bugs + rhs.bugs,
144 errors: self.errors + rhs.errors,
145 warnings: self.warnings + rhs.warnings,
146 info: self.info,
147 }
148 }
149}
150
151impl AddAssign for ReportCounters {
152 fn add_assign(&mut self, rhs: Self) {
153 self.bugs += rhs.bugs;
154 self.errors += rhs.errors;
155 self.warnings += rhs.warnings;
156 self.info += rhs.info;
157 }
158}
159
160impl ReportCounters {
161 #[inline(always)]
162 fn report(&mut self, severity: Severity) {
163 match severity {
164 Severity::Bug => self.bugs += 1,
165 Severity::Error => self.errors += 1,
166 Severity::Warning => self.warnings += 1,
167 Severity::Note => self.info += 1,
168 Severity::Help => self.info += 1,
169 }
170 }
171
172 #[inline(always)]
173 pub fn bugs(&self) -> u32 {
174 self.bugs
175 }
176
177 #[inline(always)]
178 pub fn errors(&self) -> u32 {
179 self.errors
180 }
181
182 #[inline(always)]
183 pub fn warnings(&self) -> u32 {
184 self.warnings
185 }
186
187 #[inline(always)]
188 pub fn info(&self) -> u32 {
189 self.info
190 }
191
192 #[inline(always)]
193 pub fn total_without_info(&self) -> u64 {
194 (self.bugs + self.errors + self.warnings) as u64
195 }
196
197 #[inline(always)]
198 pub fn total(&self) -> u64 {
199 (self.bugs + self.errors + self.warnings + self.info) as u64
200 }
201}
202
203impl Default for StandardStreamReporter {
206 fn default() -> Self {
207 Self::stderr(UseColor::from_env().into())
208 }
209}
210
211impl Reporter for StandardStreamReporter {
212 fn emit(&self, diagnostic: &Diagnostic, sources: &SourceFiles) -> Result<(), Error> {
213 if self.is_enabled(diagnostic.severity) {
214 self.log(diagnostic);
215 let mut counters = self.counters.borrow_mut();
216 counters.report(diagnostic.severity);
217 Ok(emit(
218 &mut self.stream.lock(),
219 &self.config,
220 sources,
221 diagnostic,
222 )?)
223 } else {
224 Ok(())
225 }
226 }
227
228 fn counters(&self) -> ReportCounters {
229 *self.counters.borrow()
230 }
231
232 fn done(&self, module_name: Option<String>) -> Result<ReportCounters, Error> {
233 self.done_stats(module_name)?;
234 let old_counters = self.counters.replace(ReportCounters::default());
235 Ok(old_counters)
236 }
237
238 fn severity_filter(&self) -> SeverityFilter {
239 self.filter
240 }
241
242 fn set_severity_filter(&mut self, filter: SeverityFilter) {
243 self.filter = filter;
244 }
245}
246
247impl StandardStreamReporter {
248 pub fn stderr(color_choice: ColorChoice) -> Self {
249 Self {
250 stream: StandardStream::stderr(color_choice),
251 filter: Default::default(),
252 config: Self::default_config(),
253 counters: Default::default(),
254 }
255 }
256
257 pub fn stdout(color_choice: ColorChoice) -> Self {
258 Self {
259 stream: StandardStream::stdout(color_choice),
260 filter: Default::default(),
261 config: Self::default_config(),
262 counters: Default::default(),
263 }
264 }
265
266 pub fn with_severity_filter(self, filter: SeverityFilter) -> Self {
267 Self { filter, ..self }
268 }
269
270 fn default_config() -> Config {
271 Config {
272 chars: Chars::box_drawing(),
273 ..Default::default()
274 }
275 }
276
277 fn done_stats(&self, module_name: Option<String>) -> Result<(), Error> {
278 let counters = self.counters.borrow();
279 if counters.total() > 0 {
280 let severity = if counters.bugs > 0 {
281 Severity::Bug
282 } else if counters.errors > 0 {
283 Severity::Error
284 } else if counters.warnings > 0 {
285 Severity::Warning
286 } else if counters.info > 0 {
287 Severity::Note
288 } else {
289 unreachable!();
290 };
291
292 let mut writer = self.stream.lock();
293
294 writer.set_color(self.config.styles.header(severity))?;
295 writer.write_all(
296 match severity {
297 Severity::Bug => i18n!("word_bug"),
298 Severity::Error => i18n!("word_error"),
299 Severity::Warning => i18n!("word_warning"),
300 Severity::Note => i18n!("word_note"),
301 Severity::Help => i18n!("word_help"),
302 }
303 .as_bytes(),
304 )?;
305 writer.reset()?;
306 writer.write_all(b": ")?;
307 writer.write_all(
308 format!(
309 "{} ",
310 if let Some(name) = module_name {
311 i18n!("lbl_module_name_short", name = name)
312 } else {
313 i18n!("lbl_parser")
314 }
315 )
316 .as_bytes(),
317 )?;
318 let mut count_strings: Vec<String> = Default::default();
319 if counters.bugs > 0 {
320 count_strings.push(i18n!("count_of_bugs", count = counters.bugs));
321 }
322 if counters.errors > 0 {
323 count_strings.push(i18n!("count_of_errors", count = counters.errors));
324 }
325 if counters.warnings > 0 {
326 count_strings.push(i18n!("count_of_warnings", count = counters.warnings));
327 }
328 if counters.info > 0 {
329 count_strings.push(i18n!("count_of_informational", count = counters.info));
330 }
331 writer.write_all(
332 i18n!(
333 "counts_generated_summary",
334 counts = count_strings.join(", ")
335 )
336 .as_bytes(),
337 )?;
338 writer.write_all(b"\n")?;
339 }
340 Ok(())
341 }
342}
343
344impl Default for CompactStreamReporter {
347 fn default() -> Self {
348 Self {
349 stream: StandardStream::stdout(UseColor::from_env().into()),
350 filter: Default::default(),
351 counters: Default::default(),
352 }
353 }
354}
355
356impl Reporter for CompactStreamReporter {
357 fn emit(&self, diagnostic: &Diagnostic, sources: &SourceFiles) -> Result<(), Error> {
358 use codespan_reporting::files::Files;
359 if self.is_enabled(diagnostic.severity) {
360 self.log(diagnostic);
361 let mut counters = self.counters.borrow_mut();
362 counters.report(diagnostic.severity);
363 let mut stream = self.stream.lock();
364 let (file_name, start, end) = if let Some(label) = diagnostic.labels.first() {
365 let file_id = label.file_id;
366 let start = sources.location(file_id, label.range.start)?;
367 let end = sources.location(file_id, label.range.end)?;
368 (
369 sources.name(file_id)?,
370 (start.line_number, start.column_number),
371 (end.line_number, end.column_number),
372 )
373 } else {
374 (String::new(), (0, 0), (0, 0))
375 };
376 stream.write_all(
377 format!(
378 "{},{},{},{},{},{},{},{}\n",
379 match diagnostic.severity {
380 Severity::Bug => i18n!("word_bug"),
381 Severity::Error => i18n!("word_error"),
382 Severity::Warning => i18n!("word_warning"),
383 Severity::Note => i18n!("word_note"),
384 Severity::Help => i18n!("word_help"),
385 },
386 file_name,
387 start.0,
388 start.1,
389 end.0,
390 end.1,
391 diagnostic.code.as_ref().unwrap(),
392 diagnostic.message
393 )
394 .as_bytes(),
395 )?;
396 }
397 Ok(())
398 }
399
400 fn counters(&self) -> ReportCounters {
401 *self.counters.borrow()
402 }
403
404 fn done(&self, _: Option<String>) -> Result<ReportCounters, Error> {
405 let old_counters = self.counters.replace(ReportCounters::default());
406 Ok(old_counters)
407 }
408
409 fn severity_filter(&self) -> SeverityFilter {
410 self.filter
411 }
412
413 fn set_severity_filter(&mut self, filter: SeverityFilter) {
414 self.filter = filter;
415 }
416}
417
418impl CompactStreamReporter {
419 pub fn with_severity_filter(self, filter: SeverityFilter) -> Self {
420 Self { filter, ..self }
421 }
422}
423
424impl Reporter for BailoutReporter {
427 fn emit(&self, diagnostic: &Diagnostic, _: &SourceFiles) -> Result<(), Error> {
428 if self.is_enabled(diagnostic.severity) {
429 self.log(diagnostic);
430 let mut counters = self.counters.borrow_mut();
431 counters.report(diagnostic.severity);
432 Err(diagnostic.clone().into())
433 } else {
434 Ok(())
435 }
436 }
437
438 fn counters(&self) -> ReportCounters {
439 *self.counters.borrow()
440 }
441
442 fn done(&self, _: Option<String>) -> Result<ReportCounters, Error> {
443 let old_counters = self.counters.replace(ReportCounters::default());
444 Ok(old_counters)
445 }
446
447 fn severity_filter(&self) -> SeverityFilter {
448 self.filter
449 }
450
451 fn set_severity_filter(&mut self, filter: SeverityFilter) {
452 self.filter = filter;
453 }
454}