1use std::error::Report;
13use std::io::{self, Write};
14use std::path::Path;
15use std::sync::{Arc, Mutex};
16use std::vec;
17
18use derive_setters::Setters;
19use rustc_data_structures::sync::IntoDynSyncSend;
20use rustc_error_messages::FluentArgs;
21use rustc_lint_defs::Applicability;
22use rustc_span::Span;
23use rustc_span::hygiene::ExpnData;
24use rustc_span::source_map::{FilePathMapping, SourceMap};
25use serde::Serialize;
26use termcolor::{ColorSpec, WriteColor};
27
28use crate::diagnostic::IsLint;
29use crate::emitter::{
30 ColorConfig, Destination, Emitter, HumanEmitter, HumanReadableErrorType, OutputTheme,
31 TimingEvent, should_show_source_code,
32};
33use crate::registry::Registry;
34use crate::timings::{TimingRecord, TimingSection};
35use crate::translation::{Translate, to_fluent_args};
36use crate::{
37 CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, Subdiag, Suggestions,
38 TerminalUrl,
39};
40
41#[cfg(test)]
42mod tests;
43
44#[derive(Setters)]
45pub struct JsonEmitter {
46 #[setters(skip)]
47 dst: IntoDynSyncSend<Box<dyn Write + Send>>,
48 #[setters(skip)]
49 sm: Option<Arc<SourceMap>>,
50 fluent_bundle: Option<Arc<FluentBundle>>,
51 #[setters(skip)]
52 fallback_bundle: LazyFallbackBundle,
53 #[setters(skip)]
54 pretty: bool,
55 ui_testing: bool,
56 ignored_directories_in_source_blocks: Vec<String>,
57 #[setters(skip)]
58 json_rendered: HumanReadableErrorType,
59 color_config: ColorConfig,
60 diagnostic_width: Option<usize>,
61 macro_backtrace: bool,
62 track_diagnostics: bool,
63 terminal_url: TerminalUrl,
64}
65
66impl JsonEmitter {
67 pub fn new(
68 dst: Box<dyn Write + Send>,
69 sm: Option<Arc<SourceMap>>,
70 fallback_bundle: LazyFallbackBundle,
71 pretty: bool,
72 json_rendered: HumanReadableErrorType,
73 color_config: ColorConfig,
74 ) -> JsonEmitter {
75 JsonEmitter {
76 dst: IntoDynSyncSend(dst),
77 sm,
78 fluent_bundle: None,
79 fallback_bundle,
80 pretty,
81 ui_testing: false,
82 ignored_directories_in_source_blocks: Vec::new(),
83 json_rendered,
84 color_config,
85 diagnostic_width: None,
86 macro_backtrace: false,
87 track_diagnostics: false,
88 terminal_url: TerminalUrl::No,
89 }
90 }
91
92 fn emit(&mut self, val: EmitTyped<'_>) -> io::Result<()> {
93 if self.pretty {
94 serde_json::to_writer_pretty(&mut *self.dst, &val)?
95 } else {
96 serde_json::to_writer(&mut *self.dst, &val)?
97 };
98 self.dst.write_all(b"\n")?;
99 self.dst.flush()
100 }
101}
102
103#[derive(Serialize)]
104#[serde(tag = "$message_type", rename_all = "snake_case")]
105enum EmitTyped<'a> {
106 Diagnostic(Diagnostic),
107 Artifact(ArtifactNotification<'a>),
108 SectionTiming(SectionTimestamp<'a>),
109 FutureIncompat(FutureIncompatReport<'a>),
110 UnusedExtern(UnusedExterns<'a>),
111}
112
113impl Translate for JsonEmitter {
114 fn fluent_bundle(&self) -> Option<&FluentBundle> {
115 self.fluent_bundle.as_deref()
116 }
117
118 fn fallback_fluent_bundle(&self) -> &FluentBundle {
119 &self.fallback_bundle
120 }
121}
122
123impl Emitter for JsonEmitter {
124 fn emit_diagnostic(&mut self, diag: crate::DiagInner, registry: &Registry) {
125 let data = Diagnostic::from_errors_diagnostic(diag, self, registry);
126 let result = self.emit(EmitTyped::Diagnostic(data));
127 if let Err(e) = result {
128 panic!("failed to print diagnostics: {e:?}");
129 }
130 }
131
132 fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) {
133 let data = ArtifactNotification { artifact: path, emit: artifact_type };
134 let result = self.emit(EmitTyped::Artifact(data));
135 if let Err(e) = result {
136 panic!("failed to print notification: {e:?}");
137 }
138 }
139
140 fn emit_timing_section(&mut self, record: TimingRecord, event: TimingEvent) {
141 let event = match event {
142 TimingEvent::Start => "start",
143 TimingEvent::End => "end",
144 };
145 let name = match record.section {
146 TimingSection::Linking => "link",
147 };
148 let data = SectionTimestamp { name, event, timestamp: record.timestamp };
149 let result = self.emit(EmitTyped::SectionTiming(data));
150 if let Err(e) = result {
151 panic!("failed to print timing section: {e:?}");
152 }
153 }
154
155 fn emit_future_breakage_report(&mut self, diags: Vec<crate::DiagInner>, registry: &Registry) {
156 let data: Vec<FutureBreakageItem<'_>> = diags
157 .into_iter()
158 .map(|mut diag| {
159 if matches!(diag.level, crate::Level::Allow | crate::Level::Expect) {
165 diag.level = crate::Level::Warning;
166 }
167 FutureBreakageItem {
168 diagnostic: EmitTyped::Diagnostic(Diagnostic::from_errors_diagnostic(
169 diag, self, registry,
170 )),
171 }
172 })
173 .collect();
174 let report = FutureIncompatReport { future_incompat_report: data };
175 let result = self.emit(EmitTyped::FutureIncompat(report));
176 if let Err(e) = result {
177 panic!("failed to print future breakage report: {e:?}");
178 }
179 }
180
181 fn emit_unused_externs(&mut self, lint_level: rustc_lint_defs::Level, unused_externs: &[&str]) {
182 let lint_level = lint_level.as_str();
183 let data = UnusedExterns { lint_level, unused_extern_names: unused_externs };
184 let result = self.emit(EmitTyped::UnusedExtern(data));
185 if let Err(e) = result {
186 panic!("failed to print unused externs: {e:?}");
187 }
188 }
189
190 fn source_map(&self) -> Option<&SourceMap> {
191 self.sm.as_deref()
192 }
193
194 fn should_show_explain(&self) -> bool {
195 !self.json_rendered.short()
196 }
197}
198
199#[derive(Serialize)]
202struct Diagnostic {
203 message: String,
205 code: Option<DiagnosticCode>,
206 level: &'static str,
208 spans: Vec<DiagnosticSpan>,
209 children: Vec<Diagnostic>,
211 rendered: Option<String>,
213}
214
215#[derive(Serialize)]
216struct DiagnosticSpan {
217 file_name: String,
218 byte_start: u32,
219 byte_end: u32,
220 line_start: usize,
222 line_end: usize,
223 column_start: usize,
225 column_end: usize,
226 is_primary: bool,
229 text: Vec<DiagnosticSpanLine>,
231 label: Option<String>,
233 suggested_replacement: Option<String>,
236 suggestion_applicability: Option<Applicability>,
238 expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
240}
241
242#[derive(Serialize)]
243struct DiagnosticSpanLine {
244 text: String,
245
246 highlight_start: usize,
248
249 highlight_end: usize,
250}
251
252#[derive(Serialize)]
253struct DiagnosticSpanMacroExpansion {
254 span: DiagnosticSpan,
258
259 macro_decl_name: String,
261
262 def_site_span: DiagnosticSpan,
264}
265
266#[derive(Serialize)]
267struct DiagnosticCode {
268 code: String,
271 explanation: Option<&'static str>,
273}
274
275#[derive(Serialize)]
276struct ArtifactNotification<'a> {
277 artifact: &'a Path,
279 emit: &'a str,
281}
282
283#[derive(Serialize)]
284struct SectionTimestamp<'a> {
285 name: &'a str,
287 event: &'a str,
289 timestamp: u128,
291}
292
293#[derive(Serialize)]
294struct FutureBreakageItem<'a> {
295 diagnostic: EmitTyped<'a>,
298}
299
300#[derive(Serialize)]
301struct FutureIncompatReport<'a> {
302 future_incompat_report: Vec<FutureBreakageItem<'a>>,
303}
304
305#[derive(Serialize)]
310struct UnusedExterns<'a> {
311 lint_level: &'a str,
313 unused_extern_names: &'a [&'a str],
315}
316
317impl Diagnostic {
318 fn from_errors_diagnostic(
320 diag: crate::DiagInner,
321 je: &JsonEmitter,
322 registry: &Registry,
323 ) -> Diagnostic {
324 let args = to_fluent_args(diag.args.iter());
325 let sugg_to_diag = |sugg: &CodeSuggestion| {
326 let translated_message =
327 je.translate_message(&sugg.msg, &args).map_err(Report::new).unwrap();
328 Diagnostic {
329 message: translated_message.to_string(),
330 code: None,
331 level: "help",
332 spans: DiagnosticSpan::from_suggestion(sugg, &args, je),
333 children: vec![],
334 rendered: None,
335 }
336 };
337 let sugg = match &diag.suggestions {
338 Suggestions::Enabled(suggestions) => suggestions.iter().map(sugg_to_diag),
339 Suggestions::Sealed(suggestions) => suggestions.iter().map(sugg_to_diag),
340 Suggestions::Disabled => [].iter().map(sugg_to_diag),
341 };
342
343 #[derive(Default, Clone)]
347 struct BufWriter(Arc<Mutex<Vec<u8>>>);
348
349 impl Write for BufWriter {
350 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
351 self.0.lock().unwrap().write(buf)
352 }
353 fn flush(&mut self) -> io::Result<()> {
354 self.0.lock().unwrap().flush()
355 }
356 }
357 impl WriteColor for BufWriter {
358 fn supports_color(&self) -> bool {
359 false
360 }
361
362 fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> {
363 Ok(())
364 }
365
366 fn reset(&mut self) -> io::Result<()> {
367 Ok(())
368 }
369 }
370
371 let translated_message = je.translate_messages(&diag.messages, &args);
372
373 let code = if let Some(code) = diag.code {
374 Some(DiagnosticCode {
375 code: code.to_string(),
376 explanation: registry.try_find_description(code).ok(),
377 })
378 } else if let Some(IsLint { name, .. }) = &diag.is_lint {
379 Some(DiagnosticCode { code: name.to_string(), explanation: None })
380 } else {
381 None
382 };
383 let level = diag.level.to_str();
384 let spans = DiagnosticSpan::from_multispan(&diag.span, &args, je);
385 let children = diag
386 .children
387 .iter()
388 .map(|c| Diagnostic::from_sub_diagnostic(c, &args, je))
389 .chain(sugg)
390 .collect();
391
392 let buf = BufWriter::default();
393 let mut dst: Destination = Box::new(buf.clone());
394 let short = je.json_rendered.short();
395 match je.color_config {
396 ColorConfig::Always | ColorConfig::Auto => dst = Box::new(termcolor::Ansi::new(dst)),
397 ColorConfig::Never => {}
398 }
399 HumanEmitter::new(dst, Arc::clone(&je.fallback_bundle))
400 .short_message(short)
401 .sm(je.sm.clone())
402 .fluent_bundle(je.fluent_bundle.clone())
403 .diagnostic_width(je.diagnostic_width)
404 .macro_backtrace(je.macro_backtrace)
405 .track_diagnostics(je.track_diagnostics)
406 .terminal_url(je.terminal_url)
407 .ui_testing(je.ui_testing)
408 .ignored_directories_in_source_blocks(je.ignored_directories_in_source_blocks.clone())
409 .theme(if let HumanReadableErrorType::Unicode = je.json_rendered {
410 OutputTheme::Unicode
411 } else {
412 OutputTheme::Ascii
413 })
414 .emit_diagnostic(diag, registry);
415 let buf = Arc::try_unwrap(buf.0).unwrap().into_inner().unwrap();
416 let buf = String::from_utf8(buf).unwrap();
417
418 Diagnostic {
419 message: translated_message.to_string(),
420 code,
421 level,
422 spans,
423 children,
424 rendered: Some(buf),
425 }
426 }
427
428 fn from_sub_diagnostic(
429 subdiag: &Subdiag,
430 args: &FluentArgs<'_>,
431 je: &JsonEmitter,
432 ) -> Diagnostic {
433 let translated_message = je.translate_messages(&subdiag.messages, args);
434 Diagnostic {
435 message: translated_message.to_string(),
436 code: None,
437 level: subdiag.level.to_str(),
438 spans: DiagnosticSpan::from_multispan(&subdiag.span, args, je),
439 children: vec![],
440 rendered: None,
441 }
442 }
443}
444
445impl DiagnosticSpan {
446 fn from_span_label(
447 span: SpanLabel,
448 suggestion: Option<(&String, Applicability)>,
449 args: &FluentArgs<'_>,
450 je: &JsonEmitter,
451 ) -> DiagnosticSpan {
452 Self::from_span_etc(
453 span.span,
454 span.is_primary,
455 span.label
456 .as_ref()
457 .map(|m| je.translate_message(m, args).unwrap())
458 .map(|m| m.to_string()),
459 suggestion,
460 je,
461 )
462 }
463
464 fn from_span_etc(
465 span: Span,
466 is_primary: bool,
467 label: Option<String>,
468 suggestion: Option<(&String, Applicability)>,
469 je: &JsonEmitter,
470 ) -> DiagnosticSpan {
471 let backtrace = span.macro_backtrace();
477 DiagnosticSpan::from_span_full(span, is_primary, label, suggestion, backtrace, je)
478 }
479
480 fn from_span_full(
481 mut span: Span,
482 is_primary: bool,
483 label: Option<String>,
484 suggestion: Option<(&String, Applicability)>,
485 mut backtrace: impl Iterator<Item = ExpnData>,
486 je: &JsonEmitter,
487 ) -> DiagnosticSpan {
488 let empty_source_map;
489 let sm = match &je.sm {
490 Some(s) => s,
491 None => {
492 span = rustc_span::DUMMY_SP;
493 empty_source_map = Arc::new(SourceMap::new(FilePathMapping::empty()));
494 empty_source_map
495 .new_source_file(std::path::PathBuf::from("empty.rs").into(), String::new());
496 &empty_source_map
497 }
498 };
499 let start = sm.lookup_char_pos(span.lo());
500 if start.col.0 == 0
504 && let Some((suggestion, _)) = suggestion
505 && suggestion.is_empty()
506 && let Ok(after) = sm.span_to_next_source(span)
507 && after.starts_with('\n')
508 {
509 span = span.with_hi(span.hi() + rustc_span::BytePos(1));
510 }
511 let end = sm.lookup_char_pos(span.hi());
512 let backtrace_step = backtrace.next().map(|bt| {
513 let call_site = Self::from_span_full(bt.call_site, false, None, None, backtrace, je);
514 let def_site_span = Self::from_span_full(
515 sm.guess_head_span(bt.def_site),
516 false,
517 None,
518 None,
519 [].into_iter(),
520 je,
521 );
522 Box::new(DiagnosticSpanMacroExpansion {
523 span: call_site,
524 macro_decl_name: bt.kind.descr(),
525 def_site_span,
526 })
527 });
528
529 DiagnosticSpan {
530 file_name: sm.filename_for_diagnostics(&start.file.name).to_string(),
531 byte_start: start.file.original_relative_byte_pos(span.lo()).0,
532 byte_end: start.file.original_relative_byte_pos(span.hi()).0,
533 line_start: start.line,
534 line_end: end.line,
535 column_start: start.col.0 + 1,
536 column_end: end.col.0 + 1,
537 is_primary,
538 text: DiagnosticSpanLine::from_span(span, je),
539 suggested_replacement: suggestion.map(|x| x.0.clone()),
540 suggestion_applicability: suggestion.map(|x| x.1),
541 expansion: backtrace_step,
542 label,
543 }
544 }
545
546 fn from_multispan(
547 msp: &MultiSpan,
548 args: &FluentArgs<'_>,
549 je: &JsonEmitter,
550 ) -> Vec<DiagnosticSpan> {
551 msp.span_labels()
552 .into_iter()
553 .map(|span_str| Self::from_span_label(span_str, None, args, je))
554 .collect()
555 }
556
557 fn from_suggestion(
558 suggestion: &CodeSuggestion,
559 args: &FluentArgs<'_>,
560 je: &JsonEmitter,
561 ) -> Vec<DiagnosticSpan> {
562 suggestion
563 .substitutions
564 .iter()
565 .flat_map(|substitution| {
566 substitution.parts.iter().map(move |suggestion_inner| {
567 let span_label =
568 SpanLabel { span: suggestion_inner.span, is_primary: true, label: None };
569 DiagnosticSpan::from_span_label(
570 span_label,
571 Some((&suggestion_inner.snippet, suggestion.applicability)),
572 args,
573 je,
574 )
575 })
576 })
577 .collect()
578 }
579}
580
581impl DiagnosticSpanLine {
582 fn line_from_source_file(
583 sf: &rustc_span::SourceFile,
584 index: usize,
585 h_start: usize,
586 h_end: usize,
587 ) -> DiagnosticSpanLine {
588 DiagnosticSpanLine {
589 text: sf.get_line(index).map_or_else(String::new, |l| l.into_owned()),
590 highlight_start: h_start,
591 highlight_end: h_end,
592 }
593 }
594
595 fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
599 je.sm
600 .as_ref()
601 .and_then(|sm| {
602 let lines = sm.span_to_lines(span).ok()?;
603 if !should_show_source_code(
605 &je.ignored_directories_in_source_blocks,
606 &sm,
607 &lines.file,
608 ) {
609 return None;
610 }
611
612 let sf = &*lines.file;
613 let span_lines = lines
614 .lines
615 .iter()
616 .map(|line| {
617 DiagnosticSpanLine::line_from_source_file(
618 sf,
619 line.line_index,
620 line.start_col.0 + 1,
621 line.end_col.0 + 1,
622 )
623 })
624 .collect();
625 Some(span_lines)
626 })
627 .unwrap_or_default()
628 }
629}