1 // Copyright 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 serialising 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.
23 use codemap
::{self, Span, MultiSpan, CodeMap}
;
24 use diagnostics
::registry
::Registry
;
25 use errors
::{Level, DiagnosticBuilder, SubDiagnostic, RenderSpan, CodeSuggestion}
;
26 use errors
::emitter
::Emitter
;
29 use std
::io
::{self, Write}
;
31 use rustc_serialize
::json
::as_json
;
33 pub struct JsonEmitter
{
34 dst
: Box
<Write
+ Send
>,
35 registry
: Option
<Registry
>,
40 pub fn basic() -> JsonEmitter
{
41 JsonEmitter
::stderr(None
, Rc
::new(CodeMap
::new()))
44 pub fn stderr(registry
: Option
<Registry
>,
45 code_map
: Rc
<CodeMap
>) -> JsonEmitter
{
47 dst
: Box
::new(io
::stderr()),
54 impl Emitter
for JsonEmitter
{
55 fn emit(&mut self, span
: Option
<&MultiSpan
>, msg
: &str, code
: Option
<&str>, level
: Level
) {
56 let data
= Diagnostic
::new(span
, msg
, code
, level
, self);
57 if let Err(e
) = writeln
!(&mut self.dst
, "{}", as_json(&data
)) {
58 panic
!("failed to print diagnostics: {:?}", e
);
62 fn custom_emit(&mut self, sp
: &RenderSpan
, msg
: &str, level
: Level
) {
63 let data
= Diagnostic
::from_render_span(sp
, msg
, level
, self);
64 if let Err(e
) = writeln
!(&mut self.dst
, "{}", as_json(&data
)) {
65 panic
!("failed to print diagnostics: {:?}", e
);
69 fn emit_struct(&mut self, db
: &DiagnosticBuilder
) {
70 let data
= Diagnostic
::from_diagnostic_builder(db
, self);
71 if let Err(e
) = writeln
!(&mut self.dst
, "{}", as_json(&data
)) {
72 panic
!("failed to print diagnostics: {:?}", e
);
77 // The following data types are provided just for serialisation.
79 #[derive(RustcEncodable)]
80 struct Diagnostic
<'a
> {
81 /// The primary error message.
83 code
: Option
<DiagnosticCode
>,
84 /// "error: internal compiler error", "error", "warning", "note", "help".
86 spans
: Vec
<DiagnosticSpan
>,
87 /// Assocaited diagnostic messages.
88 children
: Vec
<Diagnostic
<'a
>>,
91 #[derive(RustcEncodable)]
92 struct DiagnosticSpan
{
99 /// 1-based, character offset.
102 /// Source text from the start of line_start to the end of line_end.
103 text
: Vec
<DiagnosticSpanLine
>,
106 #[derive(RustcEncodable)]
107 struct DiagnosticSpanLine
{
109 /// 1-based, character offset in self.text.
110 highlight_start
: usize,
111 highlight_end
: usize,
114 #[derive(RustcEncodable)]
115 struct DiagnosticCode
{
118 /// An explanation for the code.
119 explanation
: Option
<&'
static str>,
122 impl<'a
> Diagnostic
<'a
> {
123 fn new(msp
: Option
<&MultiSpan
>,
131 code
: DiagnosticCode
::map_opt_string(code
.map(|c
| c
.to_owned()), je
),
132 level
: level
.to_str(),
133 spans
: msp
.map_or(vec
![], |msp
| DiagnosticSpan
::from_multispan(msp
, je
)),
138 fn from_render_span(span
: &RenderSpan
,
146 level
: level
.to_str(),
147 spans
: DiagnosticSpan
::from_render_span(span
, je
),
152 fn from_diagnostic_builder
<'c
>(db
: &'c DiagnosticBuilder
,
156 message
: &db
.message
,
157 code
: DiagnosticCode
::map_opt_string(db
.code
.clone(), je
),
158 level
: db
.level
.to_str(),
159 spans
: db
.span
.as_ref().map_or(vec
![], |sp
| DiagnosticSpan
::from_multispan(sp
, je
)),
160 children
: db
.children
.iter().map(|c
| {
161 Diagnostic
::from_sub_diagnostic(c
, je
)
166 fn from_sub_diagnostic
<'c
>(db
: &'c SubDiagnostic
, je
: &JsonEmitter
) -> Diagnostic
<'c
> {
168 message
: &db
.message
,
170 level
: db
.level
.to_str(),
171 spans
: db
.render_span
.as_ref()
172 .map(|sp
| DiagnosticSpan
::from_render_span(sp
, je
))
173 .or_else(|| db
.span
.as_ref().map(|s
| DiagnosticSpan
::from_multispan(s
, je
)))
180 impl DiagnosticSpan
{
181 fn from_multispan(msp
: &MultiSpan
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpan
> {
182 msp
.spans
.iter().map(|span
| {
183 let start
= je
.cm
.lookup_char_pos(span
.lo
);
184 let end
= je
.cm
.lookup_char_pos(span
.hi
);
186 file_name
: start
.file
.name
.clone(),
187 byte_start
: span
.lo
.0,
189 line_start
: start
.line
,
191 column_start
: start
.col
.0 + 1,
192 column_end
: end
.col
.0 + 1,
193 text
: DiagnosticSpanLine
::from_span(span
, je
),
198 fn from_render_span(rsp
: &RenderSpan
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpan
> {
200 RenderSpan
::FullSpan(ref msp
) |
201 // FIXME(#30701) handle Suggestion properly
202 RenderSpan
::Suggestion(CodeSuggestion { ref msp, .. }
) => {
203 DiagnosticSpan
::from_multispan(msp
, je
)
205 RenderSpan
::EndSpan(ref msp
) => {
206 msp
.spans
.iter().map(|span
| {
207 let end
= je
.cm
.lookup_char_pos(span
.hi
);
209 file_name
: end
.file
.name
.clone(),
210 byte_start
: span
.hi
.0,
212 line_start
: end
.line
,
214 column_start
: end
.col
.0 + 1,
215 column_end
: end
.col
.0 + 1,
216 text
: DiagnosticSpanLine
::from_span_end(span
, je
),
220 RenderSpan
::FileLine(ref msp
) => {
221 msp
.spans
.iter().map(|span
| {
222 let start
= je
.cm
.lookup_char_pos(span
.lo
);
223 let end
= je
.cm
.lookup_char_pos(span
.hi
);
225 file_name
: start
.file
.name
.clone(),
226 byte_start
: span
.lo
.0,
228 line_start
: start
.line
,
232 text
: DiagnosticSpanLine
::from_span(span
, je
),
240 macro_rules
! get_lines_for_span
{
241 ($span
: ident
, $je
: ident
) => {
242 match $je
.cm
.span_to_lines(*$span
) {
245 debug
!("unprintable span");
252 impl DiagnosticSpanLine
{
253 fn line_from_filemap(fm
: &codemap
::FileMap
,
257 -> DiagnosticSpanLine
{
259 text
: fm
.get_line(index
).unwrap().to_owned(),
260 highlight_start
: h_start
,
261 highlight_end
: h_end
,
265 /// Create a list of DiagnosticSpanLines from span - each line with any part
266 /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
267 /// `span` within the line.
268 fn from_span(span
: &Span
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpanLine
> {
269 let lines
= get_lines_for_span
!(span
, je
);
271 let mut result
= Vec
::new();
272 let fm
= &*lines
.file
;
274 for line
in &lines
.lines
{
275 result
.push(DiagnosticSpanLine
::line_from_filemap(fm
,
277 line
.start_col
.0 + 1,
278 line
.end_col
.0 + 1));
284 /// Create a list of DiagnosticSpanLines from span - the result covers all
285 /// of `span`, but the highlight is zero-length and at the end of `span`.
286 fn from_span_end(span
: &Span
, je
: &JsonEmitter
) -> Vec
<DiagnosticSpanLine
> {
287 let lines
= get_lines_for_span
!(span
, je
);
289 let mut result
= Vec
::new();
290 let fm
= &*lines
.file
;
292 for (i
, line
) in lines
.lines
.iter().enumerate() {
293 // Invariant - CodeMap::span_to_lines will not return extra context
294 // lines - the last line returned is the last line of `span`.
295 let highlight
= if i
== lines
.lines
.len() - 1 {
296 (line
.end_col
.0 + 1, line
.end_col
.0 + 1)
300 result
.push(DiagnosticSpanLine
::line_from_filemap(fm
,
310 impl DiagnosticCode
{
311 fn map_opt_string(s
: Option
<String
>, je
: &JsonEmitter
) -> Option
<DiagnosticCode
> {
314 let explanation
= je
.registry
316 .and_then(|registry
| registry
.find_description(&s
));
320 explanation
: explanation
,