1 // Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! A JSON emitter for errors.
13 //! This works by converting errors to a simplified structural format (see the
14 //! structs at the start of the file) and then serializing them. These should
15 //! contain as much information about the error as possible.
17 //! The format of the JSON output should be considered *unstable*. For now the
18 //! structs at the end of this file (Diagnostic*) specify the error format.
20 // FIXME spec the JSON output properly.
22 use codemap
::{CodeMap, FilePathMapping}
;
23 use syntax_pos
::{self, MacroBacktrace, Span, SpanLabel, MultiSpan}
;
24 use errors
::registry
::Registry
;
25 use errors
::{DiagnosticBuilder, SubDiagnostic, CodeSuggestion, CodeMapper}
;
26 use errors
::DiagnosticId
;
27 use errors
::emitter
::Emitter
;
30 use std
::io
::{self, Write}
;
33 use rustc_serialize
::json
::{as_json, as_pretty_json}
;
35 pub struct JsonEmitter
{
36 dst
: Box
<Write
+ Send
>,
37 registry
: Option
<Registry
>,
38 cm
: Rc
<CodeMapper
+ '
static>,
43 pub fn stderr(registry
: Option
<Registry
>,
44 code_map
: Rc
<CodeMap
>,
45 pretty
: bool
) -> JsonEmitter
{
47 dst
: Box
::new(io
::stderr()),
54 pub fn basic(pretty
: bool
) -> JsonEmitter
{
55 let file_path_mapping
= FilePathMapping
::empty();
56 JsonEmitter
::stderr(None
, Rc
::new(CodeMap
::new(file_path_mapping
)), pretty
)
59 pub fn new(dst
: Box
<Write
+ Send
>,
60 registry
: Option
<Registry
>,
61 code_map
: Rc
<CodeMap
>,
62 pretty
: bool
) -> JsonEmitter
{
72 impl Emitter
for JsonEmitter
{
73 fn emit(&mut self, db
: &DiagnosticBuilder
) {
74 let data
= Diagnostic
::from_diagnostic_builder(db
, self);
75 let result
= if self.pretty
{
76 writeln
!(&mut self.dst
, "{}", as_pretty_json(&data
))
78 writeln
!(&mut self.dst
, "{}", as_json(&data
))
80 if let Err(e
) = result
{
81 panic
!("failed to print diagnostics: {:?}", e
);
86 // The following data types are provided just for serialisation.
88 #[derive(RustcEncodable)]
90 /// The primary error message.
92 code
: Option
<DiagnosticCode
>,
93 /// "error: internal compiler error", "error", "warning", "note", "help".
95 spans
: Vec
<DiagnosticSpan
>,
96 /// Associated diagnostic messages.
97 children
: Vec
<Diagnostic
>,
98 /// The message as rustc would render it. Currently this is always `None`
99 rendered
: Option
<String
>,
102 #[derive(RustcEncodable)]
103 struct DiagnosticSpan
{
110 /// 1-based, character offset.
113 /// Is this a "primary" span -- meaning the point, or one of the points,
114 /// where the error occurred?
116 /// Source text from the start of line_start to the end of line_end.
117 text
: Vec
<DiagnosticSpanLine
>,
118 /// Label that should be placed at this location (if any)
119 label
: Option
<String
>,
120 /// If we are suggesting a replacement, this will contain text
121 /// that should be sliced in atop this span.
122 suggested_replacement
: Option
<String
>,
123 /// Macro invocations that created the code at this span, if any.
124 expansion
: Option
<Box
<DiagnosticSpanMacroExpansion
>>,
127 #[derive(RustcEncodable)]
128 struct DiagnosticSpanLine
{
131 /// 1-based, character offset in self.text.
132 highlight_start
: usize,
134 highlight_end
: usize,
137 #[derive(RustcEncodable)]
138 struct DiagnosticSpanMacroExpansion
{
139 /// span where macro was applied to generate this code; note that
140 /// this may itself derive from a macro (if
141 /// `span.expansion.is_some()`)
142 span
: DiagnosticSpan
,
144 /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
145 macro_decl_name
: String
,
147 /// span where macro was defined (if known)
148 def_site_span
: Option
<DiagnosticSpan
>,
151 #[derive(RustcEncodable)]
152 struct DiagnosticCode
{
155 /// An explanation for the code.
156 explanation
: Option
<&'
static str>,
160 fn from_diagnostic_builder(db
: &DiagnosticBuilder
,
163 let sugg
= db
.suggestions
.iter().map(|sugg
| {
165 message
: sugg
.msg
.clone(),
168 spans
: DiagnosticSpan
::from_suggestion(sugg
, je
),
174 message
: db
.message(),
175 code
: DiagnosticCode
::map_opt_string(db
.code
.clone(), je
),
176 level
: db
.level
.to_str(),
177 spans
: DiagnosticSpan
::from_multispan(&db
.span
, je
),
178 children
: db
.children
.iter().map(|c
| {
179 Diagnostic
::from_sub_diagnostic(c
, je
)
180 }).chain(sugg
).collect(),
185 fn from_sub_diagnostic(db
: &SubDiagnostic
, je
: &JsonEmitter
) -> Diagnostic
{
187 message
: db
.message(),
189 level
: db
.level
.to_str(),
190 spans
: db
.render_span
.as_ref()
191 .map(|sp
| DiagnosticSpan
::from_multispan(sp
, je
))
192 .unwrap_or_else(|| DiagnosticSpan
::from_multispan(&db
.span
, je
)),
199 impl DiagnosticSpan
{
200 fn from_span_label(span
: SpanLabel
,
201 suggestion
: Option
<&String
>,
204 Self::from_span_etc(span
.span
,
211 fn from_span_etc(span
: Span
,
213 label
: Option
<String
>,
214 suggestion
: Option
<&String
>,
217 // obtain the full backtrace from the `macro_backtrace`
218 // helper; in some ways, it'd be better to expand the
219 // backtrace ourselves, but the `macro_backtrace` helper makes
220 // some decision, such as dropping some frames, and I don't
221 // want to duplicate that logic here.
222 let backtrace
= span
.macro_backtrace().into_iter();
223 DiagnosticSpan
::from_span_full(span
,
231 fn from_span_full(span
: Span
,
233 label
: Option
<String
>,
234 suggestion
: Option
<&String
>,
235 mut backtrace
: vec
::IntoIter
<MacroBacktrace
>,
238 let start
= je
.cm
.lookup_char_pos(span
.lo());
239 let end
= je
.cm
.lookup_char_pos(span
.hi());
240 let backtrace_step
= backtrace
.next().map(|bt
| {
242 Self::from_span_full(bt
.call_site
,
248 let def_site_span
= bt
.def_site_span
.map(|sp
| {
249 Self::from_span_full(sp
,
256 Box
::new(DiagnosticSpanMacroExpansion
{
258 macro_decl_name
: bt
.macro_decl_name
,
263 file_name
: start
.file
.name
.clone(),
264 byte_start
: span
.lo().0 - start
.file
.start_pos
.0,
265 byte_end
: span
.hi().0 - start
.file
.start_pos
.0,
266 line_start
: start
.line
,
268 column_start
: start
.col
.0 + 1,
269 column_end
: end
.col
.0 + 1,
271 text
: DiagnosticSpanLine
::from_span(span
, je
),
272 suggested_replacement
: suggestion
.cloned(),
273 expansion
: backtrace_step
,
278 fn from_multispan(msp
: &MultiSpan
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpan
> {
281 .map(|span_str
| Self::from_span_label(span_str
, None
, je
))
285 fn from_suggestion(suggestion
: &CodeSuggestion
, je
: &JsonEmitter
)
286 -> Vec
<DiagnosticSpan
> {
287 suggestion
.substitutions
289 .flat_map(|substitution
| {
290 substitution
.parts
.iter().map(move |suggestion
| {
291 let span_label
= SpanLabel
{
292 span
: suggestion
.span
,
296 DiagnosticSpan
::from_span_label(span_label
,
297 Some(&suggestion
.snippet
),
305 impl DiagnosticSpanLine
{
306 fn line_from_filemap(fm
: &syntax_pos
::FileMap
,
310 -> DiagnosticSpanLine
{
312 text
: fm
.get_line(index
).map_or(String
::new(), |l
| l
.into_owned()),
313 highlight_start
: h_start
,
314 highlight_end
: h_end
,
318 /// Create a list of DiagnosticSpanLines from span - each line with any part
319 /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
320 /// `span` within the line.
321 fn from_span(span
: Span
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpanLine
> {
322 je
.cm
.span_to_lines(span
)
324 let fm
= &*lines
.file
;
328 DiagnosticSpanLine
::line_from_filemap(fm
,
330 line
.start_col
.0 + 1,
335 .unwrap_or_else(|_
| vec
![])
339 impl DiagnosticCode
{
340 fn map_opt_string(s
: Option
<DiagnosticId
>, je
: &JsonEmitter
) -> Option
<DiagnosticCode
> {
343 DiagnosticId
::Error(s
) => s
,
344 DiagnosticId
::Lint(s
) => s
,
346 let explanation
= je
.registry
348 .and_then(|registry
| registry
.find_description(&s
));