]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/json.rs
cf11ac550b76388c1a89e618d224f556f8047cec
[rust.git] / src / libsyntax / json.rs
1 //! A JSON emitter for errors.
2 //!
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.
6 //!
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.
9
10 // FIXME: spec the JSON output properly.
11
12 use source_map::{SourceMap, FilePathMapping};
13 use syntax_pos::{self, MacroBacktrace, Span, SpanLabel, MultiSpan};
14 use errors::registry::Registry;
15 use errors::{DiagnosticBuilder, SubDiagnostic, CodeSuggestion, SourceMapper};
16 use errors::{DiagnosticId, Applicability};
17 use errors::emitter::{Emitter, EmitterWriter};
18
19 use rustc_data_structures::sync::{self, Lrc};
20 use std::io::{self, Write};
21 use std::vec;
22 use std::sync::{Arc, Mutex};
23
24 use rustc_serialize::json::{as_json, as_pretty_json};
25
26 pub struct JsonEmitter {
27     dst: Box<dyn Write + Send>,
28     registry: Option<Registry>,
29     sm: Lrc<dyn SourceMapper + sync::Send + sync::Sync>,
30     pretty: bool,
31     ui_testing: bool,
32 }
33
34 impl JsonEmitter {
35     pub fn stderr(registry: Option<Registry>,
36                   source_map: Lrc<SourceMap>,
37                   pretty: bool) -> JsonEmitter {
38         JsonEmitter {
39             dst: Box::new(io::stderr()),
40             registry,
41             sm: source_map,
42             pretty,
43             ui_testing: false,
44         }
45     }
46
47     pub fn basic(pretty: bool) -> JsonEmitter {
48         let file_path_mapping = FilePathMapping::empty();
49         JsonEmitter::stderr(None, Lrc::new(SourceMap::new(file_path_mapping)),
50                             pretty)
51     }
52
53     pub fn new(dst: Box<dyn Write + Send>,
54                registry: Option<Registry>,
55                source_map: Lrc<SourceMap>,
56                pretty: bool) -> JsonEmitter {
57         JsonEmitter {
58             dst,
59             registry,
60             sm: source_map,
61             pretty,
62             ui_testing: false,
63         }
64     }
65
66     pub fn ui_testing(self, ui_testing: bool) -> Self {
67         Self { ui_testing, ..self }
68     }
69 }
70
71 impl Emitter for JsonEmitter {
72     fn emit(&mut self, db: &DiagnosticBuilder) {
73         let data = Diagnostic::from_diagnostic_builder(db, self);
74         let result = if self.pretty {
75             writeln!(&mut self.dst, "{}", as_pretty_json(&data))
76         } else {
77             writeln!(&mut self.dst, "{}", as_json(&data))
78         };
79         if let Err(e) = result {
80             panic!("failed to print diagnostics: {:?}", e);
81         }
82     }
83 }
84
85 // The following data types are provided just for serialisation.
86
87 #[derive(RustcEncodable)]
88 struct Diagnostic {
89     /// The primary error message.
90     message: String,
91     code: Option<DiagnosticCode>,
92     /// "error: internal compiler error", "error", "warning", "note", "help".
93     level: &'static str,
94     spans: Vec<DiagnosticSpan>,
95     /// Associated diagnostic messages.
96     children: Vec<Diagnostic>,
97     /// The message as rustc would render it.
98     rendered: Option<String>,
99 }
100
101 #[derive(RustcEncodable)]
102 #[allow(unused_attributes)]
103 struct DiagnosticSpan {
104     file_name: String,
105     byte_start: u32,
106     byte_end: u32,
107     /// 1-based.
108     line_start: usize,
109     line_end: usize,
110     /// 1-based, character offset.
111     column_start: usize,
112     column_end: usize,
113     /// Is this a "primary" span -- meaning the point, or one of the points,
114     /// where the error occurred?
115     is_primary: bool,
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     /// If the suggestion is approximate
124     suggestion_applicability: Option<Applicability>,
125     /// Macro invocations that created the code at this span, if any.
126     expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
127 }
128
129 #[derive(RustcEncodable)]
130 struct DiagnosticSpanLine {
131     text: String,
132
133     /// 1-based, character offset in self.text.
134     highlight_start: usize,
135
136     highlight_end: usize,
137 }
138
139 #[derive(RustcEncodable)]
140 struct DiagnosticSpanMacroExpansion {
141     /// span where macro was applied to generate this code; note that
142     /// this may itself derive from a macro (if
143     /// `span.expansion.is_some()`)
144     span: DiagnosticSpan,
145
146     /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
147     macro_decl_name: String,
148
149     /// span where macro was defined (if known)
150     def_site_span: Option<DiagnosticSpan>,
151 }
152
153 #[derive(RustcEncodable)]
154 struct DiagnosticCode {
155     /// The code itself.
156     code: String,
157     /// An explanation for the code.
158     explanation: Option<&'static str>,
159 }
160
161 impl Diagnostic {
162     fn from_diagnostic_builder(db: &DiagnosticBuilder,
163                                je: &JsonEmitter)
164                                -> Diagnostic {
165         let sugg = db.suggestions.iter().map(|sugg| {
166             Diagnostic {
167                 message: sugg.msg.clone(),
168                 code: None,
169                 level: "help",
170                 spans: DiagnosticSpan::from_suggestion(sugg, je),
171                 children: vec![],
172                 rendered: None,
173             }
174         });
175
176         // generate regular command line output and store it in the json
177
178         // A threadsafe buffer for writing.
179         #[derive(Default, Clone)]
180         struct BufWriter(Arc<Mutex<Vec<u8>>>);
181
182         impl Write for BufWriter {
183             fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
184                 self.0.lock().unwrap().write(buf)
185             }
186             fn flush(&mut self) -> io::Result<()> {
187                 self.0.lock().unwrap().flush()
188             }
189         }
190         let buf = BufWriter::default();
191         let output = buf.clone();
192         EmitterWriter::new(Box::new(buf), Some(je.sm.clone()), false, false)
193             .ui_testing(je.ui_testing).emit(db);
194         let output = Arc::try_unwrap(output.0).unwrap().into_inner().unwrap();
195         let output = String::from_utf8(output).unwrap();
196
197         Diagnostic {
198             message: db.message(),
199             code: DiagnosticCode::map_opt_string(db.code.clone(), je),
200             level: db.level.to_str(),
201             spans: DiagnosticSpan::from_multispan(&db.span, je),
202             children: db.children.iter().map(|c| {
203                 Diagnostic::from_sub_diagnostic(c, je)
204             }).chain(sugg).collect(),
205             rendered: Some(output),
206         }
207     }
208
209     fn from_sub_diagnostic(db: &SubDiagnostic, je: &JsonEmitter) -> Diagnostic {
210         Diagnostic {
211             message: db.message(),
212             code: None,
213             level: db.level.to_str(),
214             spans: db.render_span.as_ref()
215                      .map(|sp| DiagnosticSpan::from_multispan(sp, je))
216                      .unwrap_or_else(|| DiagnosticSpan::from_multispan(&db.span, je)),
217             children: vec![],
218             rendered: None,
219         }
220     }
221 }
222
223 impl DiagnosticSpan {
224     fn from_span_label(span: SpanLabel,
225                        suggestion: Option<(&String, Applicability)>,
226                        je: &JsonEmitter)
227                        -> DiagnosticSpan {
228         Self::from_span_etc(span.span,
229                             span.is_primary,
230                             span.label,
231                             suggestion,
232                             je)
233     }
234
235     fn from_span_etc(span: Span,
236                      is_primary: bool,
237                      label: Option<String>,
238                      suggestion: Option<(&String, Applicability)>,
239                      je: &JsonEmitter)
240                      -> DiagnosticSpan {
241         // obtain the full backtrace from the `macro_backtrace`
242         // helper; in some ways, it'd be better to expand the
243         // backtrace ourselves, but the `macro_backtrace` helper makes
244         // some decision, such as dropping some frames, and I don't
245         // want to duplicate that logic here.
246         let backtrace = span.macro_backtrace().into_iter();
247         DiagnosticSpan::from_span_full(span,
248                                        is_primary,
249                                        label,
250                                        suggestion,
251                                        backtrace,
252                                        je)
253     }
254
255     fn from_span_full(span: Span,
256                       is_primary: bool,
257                       label: Option<String>,
258                       suggestion: Option<(&String, Applicability)>,
259                       mut backtrace: vec::IntoIter<MacroBacktrace>,
260                       je: &JsonEmitter)
261                       -> DiagnosticSpan {
262         let start = je.sm.lookup_char_pos(span.lo());
263         let end = je.sm.lookup_char_pos(span.hi());
264         let backtrace_step = backtrace.next().map(|bt| {
265             let call_site =
266                 Self::from_span_full(bt.call_site,
267                                      false,
268                                      None,
269                                      None,
270                                      backtrace,
271                                      je);
272             let def_site_span = bt.def_site_span.map(|sp| {
273                 Self::from_span_full(sp,
274                                      false,
275                                      None,
276                                      None,
277                                      vec![].into_iter(),
278                                      je)
279             });
280             Box::new(DiagnosticSpanMacroExpansion {
281                 span: call_site,
282                 macro_decl_name: bt.macro_decl_name,
283                 def_site_span,
284             })
285         });
286
287         DiagnosticSpan {
288             file_name: start.file.name.to_string(),
289             byte_start: span.lo().0 - start.file.start_pos.0,
290             byte_end: span.hi().0 - start.file.start_pos.0,
291             line_start: start.line,
292             line_end: end.line,
293             column_start: start.col.0 + 1,
294             column_end: end.col.0 + 1,
295             is_primary,
296             text: DiagnosticSpanLine::from_span(span, je),
297             suggested_replacement: suggestion.map(|x| x.0.clone()),
298             suggestion_applicability: suggestion.map(|x| x.1),
299             expansion: backtrace_step,
300             label,
301         }
302     }
303
304     fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
305         msp.span_labels()
306            .into_iter()
307            .map(|span_str| Self::from_span_label(span_str, None, je))
308            .collect()
309     }
310
311     fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter)
312                        -> Vec<DiagnosticSpan> {
313         suggestion.substitutions
314                       .iter()
315                       .flat_map(|substitution| {
316                           substitution.parts.iter().map(move |suggestion_inner| {
317                               let span_label = SpanLabel {
318                                   span: suggestion_inner.span,
319                                   is_primary: true,
320                                   label: None,
321                               };
322                               DiagnosticSpan::from_span_label(span_label,
323                                                               Some((&suggestion_inner.snippet,
324                                                                    suggestion.applicability)),
325                                                               je)
326                           })
327                       })
328                       .collect()
329     }
330 }
331
332 impl DiagnosticSpanLine {
333     fn line_from_source_file(fm: &syntax_pos::SourceFile,
334                          index: usize,
335                          h_start: usize,
336                          h_end: usize)
337                          -> DiagnosticSpanLine {
338         DiagnosticSpanLine {
339             text: fm.get_line(index).map_or(String::new(), |l| l.into_owned()),
340             highlight_start: h_start,
341             highlight_end: h_end,
342         }
343     }
344
345     /// Create a list of DiagnosticSpanLines from span - each line with any part
346     /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
347     /// `span` within the line.
348     fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
349         je.sm.span_to_lines(span)
350              .map(|lines| {
351                  let fm = &*lines.file;
352                  lines.lines
353                       .iter()
354                       .map(|line| {
355                           DiagnosticSpanLine::line_from_source_file(fm,
356                                                                 line.line_index,
357                                                                 line.start_col.0 + 1,
358                                                                 line.end_col.0 + 1)
359                       })
360                      .collect()
361              })
362             .unwrap_or_else(|_| vec![])
363     }
364 }
365
366 impl DiagnosticCode {
367     fn map_opt_string(s: Option<DiagnosticId>, je: &JsonEmitter) -> Option<DiagnosticCode> {
368         s.map(|s| {
369             let s = match s {
370                 DiagnosticId::Error(s) => s,
371                 DiagnosticId::Lint(s) => s,
372             };
373             let explanation = je.registry
374                                 .as_ref()
375                                 .and_then(|registry| registry.find_description(&s));
376
377             DiagnosticCode {
378                 code: s,
379                 explanation,
380             }
381         })
382     }
383 }