1 //! A JSON emitter for errors.
3 //! This works by converting errors to a simplified structural format (see the
4 //! structs at the start of the file) and then serializing them. These should
5 //! contain as much information about the error as possible.
7 //! The format of the JSON output should be considered *unstable*. For now the
8 //! structs at the end of this file (Diagnostic*) specify the error format.
10 // FIXME: spec the JSON output properly.
12 use rustc_span::source_map::{FilePathMapping, SourceMap};
14 use crate::emitter::{Emitter, HumanReadableErrorType};
15 use crate::registry::Registry;
16 use crate::DiagnosticId;
17 use crate::ToolMetadata;
19 CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, SubDiagnostic,
21 use rustc_lint_defs::Applicability;
23 use rustc_data_structures::sync::Lrc;
24 use rustc_error_messages::FluentArgs;
25 use rustc_span::hygiene::ExpnData;
27 use std::io::{self, Write};
29 use std::sync::{Arc, Mutex};
32 use rustc_serialize::json::{as_json, as_pretty_json};
33 use rustc_serialize::{Encodable, Encoder};
38 pub struct JsonEmitter {
39 dst: Box<dyn Write + Send>,
40 registry: Option<Registry>,
42 fluent_bundle: Option<Lrc<FluentBundle>>,
43 fallback_bundle: LazyFallbackBundle,
46 json_rendered: HumanReadableErrorType,
47 terminal_width: Option<usize>,
48 macro_backtrace: bool,
53 registry: Option<Registry>,
54 source_map: Lrc<SourceMap>,
55 fluent_bundle: Option<Lrc<FluentBundle>>,
56 fallback_bundle: LazyFallbackBundle,
58 json_rendered: HumanReadableErrorType,
59 terminal_width: Option<usize>,
60 macro_backtrace: bool,
63 dst: Box::new(io::BufWriter::new(io::stderr())),
78 json_rendered: HumanReadableErrorType,
79 fluent_bundle: Option<Lrc<FluentBundle>>,
80 fallback_bundle: LazyFallbackBundle,
81 terminal_width: Option<usize>,
82 macro_backtrace: bool,
84 let file_path_mapping = FilePathMapping::empty();
87 Lrc::new(SourceMap::new(file_path_mapping)),
98 dst: Box<dyn Write + Send>,
99 registry: Option<Registry>,
100 source_map: Lrc<SourceMap>,
101 fluent_bundle: Option<Lrc<FluentBundle>>,
102 fallback_bundle: LazyFallbackBundle,
104 json_rendered: HumanReadableErrorType,
105 terminal_width: Option<usize>,
106 macro_backtrace: bool,
122 pub fn ui_testing(self, ui_testing: bool) -> Self {
123 Self { ui_testing, ..self }
127 impl Emitter for JsonEmitter {
128 fn emit_diagnostic(&mut self, diag: &crate::Diagnostic) {
129 let data = Diagnostic::from_errors_diagnostic(diag, self);
130 let result = if self.pretty {
131 writeln!(&mut self.dst, "{}", as_pretty_json(&data))
133 writeln!(&mut self.dst, "{}", as_json(&data))
135 .and_then(|_| self.dst.flush());
136 if let Err(e) = result {
137 panic!("failed to print diagnostics: {:?}", e);
141 fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) {
142 let data = ArtifactNotification { artifact: path, emit: artifact_type };
143 let result = if self.pretty {
144 writeln!(&mut self.dst, "{}", as_pretty_json(&data))
146 writeln!(&mut self.dst, "{}", as_json(&data))
148 .and_then(|_| self.dst.flush());
149 if let Err(e) = result {
150 panic!("failed to print notification: {:?}", e);
154 fn emit_future_breakage_report(&mut self, diags: Vec<crate::Diagnostic>) {
155 let data: Vec<FutureBreakageItem> = diags
158 if diag.level == crate::Level::Allow {
159 diag.level = crate::Level::Warning;
161 FutureBreakageItem { diagnostic: Diagnostic::from_errors_diagnostic(&diag, self) }
164 let report = FutureIncompatReport { future_incompat_report: data };
165 let result = if self.pretty {
166 writeln!(&mut self.dst, "{}", as_pretty_json(&report))
168 writeln!(&mut self.dst, "{}", as_json(&report))
170 .and_then(|_| self.dst.flush());
171 if let Err(e) = result {
172 panic!("failed to print future breakage report: {:?}", e);
176 fn emit_unused_externs(&mut self, lint_level: &str, unused_externs: &[&str]) {
177 let data = UnusedExterns { lint_level, unused_extern_names: unused_externs };
178 let result = if self.pretty {
179 writeln!(&mut self.dst, "{}", as_pretty_json(&data))
181 writeln!(&mut self.dst, "{}", as_json(&data))
183 .and_then(|_| self.dst.flush());
184 if let Err(e) = result {
185 panic!("failed to print unused externs: {:?}", e);
189 fn source_map(&self) -> Option<&Lrc<SourceMap>> {
193 fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
194 self.fluent_bundle.as_ref()
197 fn fallback_fluent_bundle(&self) -> &FluentBundle {
198 &**self.fallback_bundle
201 fn should_show_explain(&self) -> bool {
202 !matches!(self.json_rendered, HumanReadableErrorType::Short(_))
206 // The following data types are provided just for serialisation.
208 // NOTE: this has a manual implementation of Encodable which needs to be updated in
211 /// The primary error message.
213 code: Option<DiagnosticCode>,
214 /// "error: internal compiler error", "error", "warning", "note", "help".
216 spans: Vec<DiagnosticSpan>,
217 /// Associated diagnostic messages.
218 children: Vec<Diagnostic>,
219 /// The message as rustc would render it.
220 rendered: Option<String>,
221 /// Extra tool metadata
222 tool_metadata: ToolMetadata,
225 macro_rules! encode_fields {
227 $enc:expr, // encoder
228 $idx:expr, // starting field index
229 $struct:expr, // struct we're serializing
230 $struct_name:ident, // struct name
231 [ $($name:ident),+$(,)? ], // fields to encode
232 [ $($ignore:ident),+$(,)? ] // fields we're skipping
235 // Pattern match to make sure all fields are accounted for
236 let $struct_name { $($name,)+ $($ignore: _,)+ } = $struct;
239 $enc.emit_struct_field(
242 |enc| $name.encode(enc),
251 // Special-case encoder to skip tool_metadata if not set
252 impl<E: Encoder> Encodable<E> for Diagnostic {
253 fn encode(&self, s: &mut E) -> Result<(), E::Error> {
254 s.emit_struct(false, |s| {
257 idx = encode_fields!(
262 [message, code, level, spans, children, rendered],
265 if self.tool_metadata.is_set() {
266 idx = encode_fields!(
272 [message, code, level, spans, children, rendered]
283 struct DiagnosticSpan {
290 /// 1-based, character offset.
293 /// Is this a "primary" span -- meaning the point, or one of the points,
294 /// where the error occurred?
296 /// Source text from the start of line_start to the end of line_end.
297 text: Vec<DiagnosticSpanLine>,
298 /// Label that should be placed at this location (if any)
299 label: Option<String>,
300 /// If we are suggesting a replacement, this will contain text
301 /// that should be sliced in atop this span.
302 suggested_replacement: Option<String>,
303 /// If the suggestion is approximate
304 suggestion_applicability: Option<Applicability>,
305 /// Macro invocations that created the code at this span, if any.
306 expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
310 struct DiagnosticSpanLine {
313 /// 1-based, character offset in self.text.
314 highlight_start: usize,
316 highlight_end: usize,
320 struct DiagnosticSpanMacroExpansion {
321 /// span where macro was applied to generate this code; note that
322 /// this may itself derive from a macro (if
323 /// `span.expansion.is_some()`)
324 span: DiagnosticSpan,
326 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
327 macro_decl_name: String,
329 /// span where macro was defined (if known)
330 def_site_span: DiagnosticSpan,
334 struct DiagnosticCode {
337 /// An explanation for the code.
338 explanation: Option<&'static str>,
342 struct ArtifactNotification<'a> {
343 /// The path of the artifact.
345 /// What kind of artifact we're emitting.
350 struct FutureBreakageItem {
351 diagnostic: Diagnostic,
355 struct FutureIncompatReport {
356 future_incompat_report: Vec<FutureBreakageItem>,
359 // NOTE: Keep this in sync with the equivalent structs in rustdoc's
360 // doctest component (as well as cargo).
361 // We could unify this struct the one in rustdoc but they have different
362 // ownership semantics, so doing so would create wasteful allocations.
364 struct UnusedExterns<'a, 'b, 'c> {
365 /// The severity level of the unused dependencies lint
367 /// List of unused externs by their names.
368 unused_extern_names: &'b [&'c str],
372 fn from_errors_diagnostic(diag: &crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
373 let args = je.to_fluent_args(diag.args());
374 let sugg = diag.suggestions.iter().flatten().map(|sugg| {
375 let translated_message = je.translate_message(&sugg.msg, &args);
377 message: translated_message.to_string(),
380 spans: DiagnosticSpan::from_suggestion(sugg, &args, je),
383 tool_metadata: sugg.tool_metadata.clone(),
387 // generate regular command line output and store it in the json
389 // A threadsafe buffer for writing.
390 #[derive(Default, Clone)]
391 struct BufWriter(Arc<Mutex<Vec<u8>>>);
393 impl Write for BufWriter {
394 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
395 self.0.lock().unwrap().write(buf)
397 fn flush(&mut self) -> io::Result<()> {
398 self.0.lock().unwrap().flush()
401 let buf = BufWriter::default();
402 let output = buf.clone();
407 je.fluent_bundle.clone(),
408 je.fallback_bundle.clone(),
413 .ui_testing(je.ui_testing)
414 .emit_diagnostic(diag);
415 let output = Arc::try_unwrap(output.0).unwrap().into_inner().unwrap();
416 let output = String::from_utf8(output).unwrap();
418 let translated_message = je.translate_messages(&diag.message, &args);
420 message: translated_message.to_string(),
421 code: DiagnosticCode::map_opt_string(diag.code.clone(), je),
422 level: diag.level.to_str(),
423 spans: DiagnosticSpan::from_multispan(&diag.span, &args, je),
427 .map(|c| Diagnostic::from_sub_diagnostic(c, &args, je))
430 rendered: Some(output),
431 tool_metadata: ToolMetadata::default(),
435 fn from_sub_diagnostic(
436 diag: &SubDiagnostic,
437 args: &FluentArgs<'_>,
440 let translated_message = je.translate_messages(&diag.message, args);
442 message: translated_message.to_string(),
444 level: diag.level.to_str(),
448 .map(|sp| DiagnosticSpan::from_multispan(sp, args, je))
449 .unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, args, je)),
452 tool_metadata: ToolMetadata::default(),
457 impl DiagnosticSpan {
460 suggestion: Option<(&String, Applicability)>,
461 args: &FluentArgs<'_>,
463 ) -> DiagnosticSpan {
467 span.label.as_ref().map(|m| je.translate_message(m, args)).map(|m| m.to_string()),
476 label: Option<String>,
477 suggestion: Option<(&String, Applicability)>,
479 ) -> DiagnosticSpan {
480 // obtain the full backtrace from the `macro_backtrace`
481 // helper; in some ways, it'd be better to expand the
482 // backtrace ourselves, but the `macro_backtrace` helper makes
483 // some decision, such as dropping some frames, and I don't
484 // want to duplicate that logic here.
485 let backtrace = span.macro_backtrace();
486 DiagnosticSpan::from_span_full(span, is_primary, label, suggestion, backtrace, je)
492 label: Option<String>,
493 suggestion: Option<(&String, Applicability)>,
494 mut backtrace: impl Iterator<Item = ExpnData>,
496 ) -> DiagnosticSpan {
497 let start = je.sm.lookup_char_pos(span.lo());
498 let end = je.sm.lookup_char_pos(span.hi());
499 let backtrace_step = backtrace.next().map(|bt| {
500 let call_site = Self::from_span_full(bt.call_site, false, None, None, backtrace, je);
501 let def_site_span = Self::from_span_full(
502 je.sm.guess_head_span(bt.def_site),
509 Box::new(DiagnosticSpanMacroExpansion {
511 macro_decl_name: bt.kind.descr(),
517 file_name: je.sm.filename_for_diagnostics(&start.file.name).to_string(),
518 byte_start: start.file.original_relative_byte_pos(span.lo()).0,
519 byte_end: start.file.original_relative_byte_pos(span.hi()).0,
520 line_start: start.line,
522 column_start: start.col.0 + 1,
523 column_end: end.col.0 + 1,
525 text: DiagnosticSpanLine::from_span(span, je),
526 suggested_replacement: suggestion.map(|x| x.0.clone()),
527 suggestion_applicability: suggestion.map(|x| x.1),
528 expansion: backtrace_step,
535 args: &FluentArgs<'_>,
537 ) -> Vec<DiagnosticSpan> {
540 .map(|span_str| Self::from_span_label(span_str, None, args, je))
545 suggestion: &CodeSuggestion,
546 args: &FluentArgs<'_>,
548 ) -> Vec<DiagnosticSpan> {
552 .flat_map(|substitution| {
553 substitution.parts.iter().map(move |suggestion_inner| {
555 SpanLabel { span: suggestion_inner.span, is_primary: true, label: None };
556 DiagnosticSpan::from_span_label(
558 Some((&suggestion_inner.snippet, suggestion.applicability)),
568 impl DiagnosticSpanLine {
569 fn line_from_source_file(
570 sf: &rustc_span::SourceFile,
574 ) -> DiagnosticSpanLine {
576 text: sf.get_line(index).map_or_else(String::new, |l| l.into_owned()),
577 highlight_start: h_start,
578 highlight_end: h_end,
582 /// Creates a list of DiagnosticSpanLines from span - each line with any part
583 /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
584 /// `span` within the line.
585 fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
589 // We can't get any lines if the source is unavailable.
590 if !je.sm.ensure_source_file_source_present(lines.file.clone()) {
594 let sf = &*lines.file;
599 DiagnosticSpanLine::line_from_source_file(
602 line.start_col.0 + 1,
608 .unwrap_or_else(|_| vec![])
612 impl DiagnosticCode {
613 fn map_opt_string(s: Option<DiagnosticId>, je: &JsonEmitter) -> Option<DiagnosticCode> {
616 DiagnosticId::Error(s) => s,
617 DiagnosticId::Lint { name, .. } => name,
620 je.registry.as_ref().map(|registry| registry.try_find_description(&s)).unwrap();
622 DiagnosticCode { code: s, explanation: je_result.unwrap_or(None) }