]>
Commit | Line | Data |
---|---|---|
9cc50fc6 SL |
1 | //! A JSON emitter for errors. |
2 | //! | |
3 | //! This works by converting errors to a simplified structural format (see the | |
3b2f2976 | 4 | //! structs at the start of the file) and then serializing them. These should |
9cc50fc6 SL |
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 | ||
9fa01778 XL |
10 | // FIXME: spec the JSON output properly. |
11 | ||
c620b35d FG |
12 | use crate::emitter::{ |
13 | should_show_source_code, ColorConfig, Destination, Emitter, HumanEmitter, | |
14 | HumanReadableErrorType, | |
15 | }; | |
dfeec247 | 16 | use crate::registry::Registry; |
2b03887a | 17 | use crate::translation::{to_fluent_args, Translate}; |
04454e1e | 18 | use crate::{ |
c0240ec0 | 19 | diagnostic::IsLint, CodeSuggestion, FluentBundle, LazyFallbackBundle, MultiSpan, SpanLabel, |
c620b35d | 20 | Subdiag, TerminalUrl, |
04454e1e | 21 | }; |
c620b35d | 22 | use derive_setters::Setters; |
781aab86 | 23 | use rustc_data_structures::sync::{IntoDynSyncSend, Lrc}; |
04454e1e | 24 | use rustc_error_messages::FluentArgs; |
c620b35d | 25 | use rustc_lint_defs::Applicability; |
dfeec247 | 26 | use rustc_span::hygiene::ExpnData; |
c620b35d | 27 | use rustc_span::source_map::SourceMap; |
04454e1e | 28 | use rustc_span::Span; |
c620b35d | 29 | use serde::Serialize; |
9c376795 | 30 | use std::error::Report; |
9cc50fc6 | 31 | use std::io::{self, Write}; |
48663c56 | 32 | use std::path::Path; |
ff7c6d11 | 33 | use std::sync::{Arc, Mutex}; |
dfeec247 | 34 | use std::vec; |
c620b35d | 35 | use termcolor::{ColorSpec, WriteColor}; |
9cc50fc6 | 36 | |
e74abb32 XL |
37 | #[cfg(test)] |
38 | mod tests; | |
39 | ||
c620b35d | 40 | #[derive(Setters)] |
9cc50fc6 | 41 | pub struct JsonEmitter { |
c620b35d | 42 | #[setters(skip)] |
781aab86 | 43 | dst: IntoDynSyncSend<Box<dyn Write + Send>>, |
9cc50fc6 | 44 | registry: Option<Registry>, |
c620b35d | 45 | #[setters(skip)] |
60c5eb7d | 46 | sm: Lrc<SourceMap>, |
04454e1e | 47 | fluent_bundle: Option<Lrc<FluentBundle>>, |
c620b35d | 48 | #[setters(skip)] |
04454e1e | 49 | fallback_bundle: LazyFallbackBundle, |
c620b35d | 50 | #[setters(skip)] |
abe05a73 | 51 | pretty: bool, |
0531ce1d | 52 | ui_testing: bool, |
781aab86 | 53 | ignored_directories_in_source_blocks: Vec<String>, |
c620b35d | 54 | #[setters(skip)] |
48663c56 | 55 | json_rendered: HumanReadableErrorType, |
064997fb | 56 | diagnostic_width: Option<usize>, |
74b04a01 | 57 | macro_backtrace: bool, |
487cf647 | 58 | track_diagnostics: bool, |
9ffffee4 | 59 | terminal_url: TerminalUrl, |
9cc50fc6 SL |
60 | } |
61 | ||
62 | impl JsonEmitter { | |
48663c56 XL |
63 | pub fn new( |
64 | dst: Box<dyn Write + Send>, | |
c620b35d | 65 | sm: Lrc<SourceMap>, |
04454e1e | 66 | fallback_bundle: LazyFallbackBundle, |
48663c56 XL |
67 | pretty: bool, |
68 | json_rendered: HumanReadableErrorType, | |
69 | ) -> JsonEmitter { | |
9cc50fc6 | 70 | JsonEmitter { |
781aab86 | 71 | dst: IntoDynSyncSend(dst), |
c620b35d FG |
72 | registry: None, |
73 | sm, | |
74 | fluent_bundle: None, | |
04454e1e | 75 | fallback_bundle, |
abe05a73 | 76 | pretty, |
0531ce1d | 77 | ui_testing: false, |
781aab86 | 78 | ignored_directories_in_source_blocks: Vec::new(), |
48663c56 | 79 | json_rendered, |
c620b35d FG |
80 | diagnostic_width: None, |
81 | macro_backtrace: false, | |
82 | track_diagnostics: false, | |
83 | terminal_url: TerminalUrl::No, | |
9cc50fc6 SL |
84 | } |
85 | } | |
0531ce1d | 86 | |
4b012472 FG |
87 | fn emit(&mut self, val: EmitTyped<'_>) -> io::Result<()> { |
88 | if self.pretty { | |
89 | serde_json::to_writer_pretty(&mut *self.dst, &val)? | |
90 | } else { | |
91 | serde_json::to_writer(&mut *self.dst, &val)? | |
92 | }; | |
93 | self.dst.write_all(b"\n")?; | |
94 | self.dst.flush() | |
95 | } | |
96 | } | |
97 | ||
98 | #[derive(Serialize)] | |
99 | #[serde(tag = "$message_type", rename_all = "snake_case")] | |
100 | enum EmitTyped<'a> { | |
101 | Diagnostic(Diagnostic), | |
102 | Artifact(ArtifactNotification<'a>), | |
103 | FutureIncompat(FutureIncompatReport<'a>), | |
c620b35d | 104 | UnusedExtern(UnusedExterns<'a>), |
9cc50fc6 SL |
105 | } |
106 | ||
f2b60f7d FG |
107 | impl Translate for JsonEmitter { |
108 | fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> { | |
109 | self.fluent_bundle.as_ref() | |
110 | } | |
111 | ||
112 | fn fallback_fluent_bundle(&self) -> &FluentBundle { | |
487cf647 | 113 | &self.fallback_bundle |
f2b60f7d FG |
114 | } |
115 | } | |
116 | ||
9cc50fc6 | 117 | impl Emitter for JsonEmitter { |
c620b35d | 118 | fn emit_diagnostic(&mut self, diag: crate::DiagInner) { |
e74abb32 | 119 | let data = Diagnostic::from_errors_diagnostic(diag, self); |
4b012472 | 120 | let result = self.emit(EmitTyped::Diagnostic(data)); |
abe05a73 | 121 | if let Err(e) = result { |
add651ee | 122 | panic!("failed to print diagnostics: {e:?}"); |
9cc50fc6 SL |
123 | } |
124 | } | |
48663c56 | 125 | |
dc9dc135 XL |
126 | fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) { |
127 | let data = ArtifactNotification { artifact: path, emit: artifact_type }; | |
4b012472 | 128 | let result = self.emit(EmitTyped::Artifact(data)); |
48663c56 | 129 | if let Err(e) = result { |
add651ee | 130 | panic!("failed to print notification: {e:?}"); |
48663c56 XL |
131 | } |
132 | } | |
e74abb32 | 133 | |
c620b35d | 134 | fn emit_future_breakage_report(&mut self, diags: Vec<crate::DiagInner>) { |
4b012472 | 135 | let data: Vec<FutureBreakageItem<'_>> = diags |
29967ef6 | 136 | .into_iter() |
136023e0 | 137 | .map(|mut diag| { |
29967ef6 | 138 | if diag.level == crate::Level::Allow { |
c0240ec0 | 139 | diag.level = crate::Level::Warning; |
29967ef6 | 140 | } |
4b012472 FG |
141 | FutureBreakageItem { |
142 | diagnostic: EmitTyped::Diagnostic(Diagnostic::from_errors_diagnostic( | |
c620b35d | 143 | diag, self, |
4b012472 FG |
144 | )), |
145 | } | |
29967ef6 XL |
146 | }) |
147 | .collect(); | |
148 | let report = FutureIncompatReport { future_incompat_report: data }; | |
4b012472 | 149 | let result = self.emit(EmitTyped::FutureIncompat(report)); |
29967ef6 | 150 | if let Err(e) = result { |
add651ee | 151 | panic!("failed to print future breakage report: {e:?}"); |
29967ef6 XL |
152 | } |
153 | } | |
154 | ||
04454e1e FG |
155 | fn emit_unused_externs(&mut self, lint_level: rustc_lint_defs::Level, unused_externs: &[&str]) { |
156 | let lint_level = lint_level.as_str(); | |
cdc7bbd5 | 157 | let data = UnusedExterns { lint_level, unused_extern_names: unused_externs }; |
4b012472 | 158 | let result = self.emit(EmitTyped::UnusedExtern(data)); |
cdc7bbd5 | 159 | if let Err(e) = result { |
add651ee | 160 | panic!("failed to print unused externs: {e:?}"); |
cdc7bbd5 XL |
161 | } |
162 | } | |
163 | ||
60c5eb7d | 164 | fn source_map(&self) -> Option<&Lrc<SourceMap>> { |
e74abb32 XL |
165 | Some(&self.sm) |
166 | } | |
167 | ||
168 | fn should_show_explain(&self) -> bool { | |
29967ef6 | 169 | !matches!(self.json_rendered, HumanReadableErrorType::Short(_)) |
e74abb32 | 170 | } |
9cc50fc6 SL |
171 | } |
172 | ||
173 | // The following data types are provided just for serialisation. | |
174 | ||
923072b8 | 175 | #[derive(Serialize)] |
32a655c1 | 176 | struct Diagnostic { |
9cc50fc6 | 177 | /// The primary error message. |
32a655c1 | 178 | message: String, |
9cc50fc6 SL |
179 | code: Option<DiagnosticCode>, |
180 | /// "error: internal compiler error", "error", "warning", "note", "help". | |
181 | level: &'static str, | |
7453a54e | 182 | spans: Vec<DiagnosticSpan>, |
a7813a04 | 183 | /// Associated diagnostic messages. |
32a655c1 | 184 | children: Vec<Diagnostic>, |
ff7c6d11 | 185 | /// The message as rustc would render it. |
a7813a04 | 186 | rendered: Option<String>, |
9cc50fc6 SL |
187 | } |
188 | ||
923072b8 | 189 | #[derive(Serialize)] |
9cc50fc6 SL |
190 | struct DiagnosticSpan { |
191 | file_name: String, | |
192 | byte_start: u32, | |
193 | byte_end: u32, | |
194 | /// 1-based. | |
195 | line_start: usize, | |
196 | line_end: usize, | |
197 | /// 1-based, character offset. | |
198 | column_start: usize, | |
199 | column_end: usize, | |
a7813a04 XL |
200 | /// Is this a "primary" span -- meaning the point, or one of the points, |
201 | /// where the error occurred? | |
202 | is_primary: bool, | |
54a0048b SL |
203 | /// Source text from the start of line_start to the end of line_end. |
204 | text: Vec<DiagnosticSpanLine>, | |
a7813a04 XL |
205 | /// Label that should be placed at this location (if any) |
206 | label: Option<String>, | |
207 | /// If we are suggesting a replacement, this will contain text | |
abe05a73 | 208 | /// that should be sliced in atop this span. |
a7813a04 | 209 | suggested_replacement: Option<String>, |
2c00a5a8 | 210 | /// If the suggestion is approximate |
83c7162d | 211 | suggestion_applicability: Option<Applicability>, |
a7813a04 XL |
212 | /// Macro invocations that created the code at this span, if any. |
213 | expansion: Option<Box<DiagnosticSpanMacroExpansion>>, | |
54a0048b SL |
214 | } |
215 | ||
923072b8 | 216 | #[derive(Serialize)] |
54a0048b SL |
217 | struct DiagnosticSpanLine { |
218 | text: String, | |
a7813a04 | 219 | |
54a0048b SL |
220 | /// 1-based, character offset in self.text. |
221 | highlight_start: usize, | |
a7813a04 | 222 | |
54a0048b | 223 | highlight_end: usize, |
9cc50fc6 SL |
224 | } |
225 | ||
923072b8 | 226 | #[derive(Serialize)] |
a7813a04 XL |
227 | struct DiagnosticSpanMacroExpansion { |
228 | /// span where macro was applied to generate this code; note that | |
229 | /// this may itself derive from a macro (if | |
230 | /// `span.expansion.is_some()`) | |
231 | span: DiagnosticSpan, | |
232 | ||
233 | /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]") | |
234 | macro_decl_name: String, | |
235 | ||
236 | /// span where macro was defined (if known) | |
416331ca | 237 | def_site_span: DiagnosticSpan, |
a7813a04 XL |
238 | } |
239 | ||
923072b8 | 240 | #[derive(Serialize)] |
9cc50fc6 | 241 | struct DiagnosticCode { |
c0240ec0 FG |
242 | /// The error code (e.g. "E1234"), if the diagnostic has one. Or the lint |
243 | /// name, if it's a lint without an error code. | |
9cc50fc6 SL |
244 | code: String, |
245 | /// An explanation for the code. | |
246 | explanation: Option<&'static str>, | |
247 | } | |
248 | ||
923072b8 | 249 | #[derive(Serialize)] |
48663c56 XL |
250 | struct ArtifactNotification<'a> { |
251 | /// The path of the artifact. | |
252 | artifact: &'a Path, | |
dc9dc135 XL |
253 | /// What kind of artifact we're emitting. |
254 | emit: &'a str, | |
48663c56 XL |
255 | } |
256 | ||
923072b8 | 257 | #[derive(Serialize)] |
4b012472 FG |
258 | struct FutureBreakageItem<'a> { |
259 | // Always EmitTyped::Diagnostic, but we want to make sure it gets serialized | |
260 | // with "$message_type". | |
261 | diagnostic: EmitTyped<'a>, | |
29967ef6 XL |
262 | } |
263 | ||
923072b8 | 264 | #[derive(Serialize)] |
4b012472 FG |
265 | struct FutureIncompatReport<'a> { |
266 | future_incompat_report: Vec<FutureBreakageItem<'a>>, | |
29967ef6 XL |
267 | } |
268 | ||
cdc7bbd5 XL |
269 | // NOTE: Keep this in sync with the equivalent structs in rustdoc's |
270 | // doctest component (as well as cargo). | |
271 | // We could unify this struct the one in rustdoc but they have different | |
272 | // ownership semantics, so doing so would create wasteful allocations. | |
923072b8 | 273 | #[derive(Serialize)] |
c620b35d | 274 | struct UnusedExterns<'a> { |
cdc7bbd5 XL |
275 | /// The severity level of the unused dependencies lint |
276 | lint_level: &'a str, | |
277 | /// List of unused externs by their names. | |
c620b35d | 278 | unused_extern_names: &'a [&'a str], |
cdc7bbd5 XL |
279 | } |
280 | ||
32a655c1 | 281 | impl Diagnostic { |
c620b35d FG |
282 | /// Converts from `rustc_errors::DiagInner` to `Diagnostic`. |
283 | fn from_errors_diagnostic(diag: crate::DiagInner, je: &JsonEmitter) -> Diagnostic { | |
284 | let args = to_fluent_args(diag.args.iter()); | |
04454e1e | 285 | let sugg = diag.suggestions.iter().flatten().map(|sugg| { |
9c376795 FG |
286 | let translated_message = |
287 | je.translate_message(&sugg.msg, &args).map_err(Report::new).unwrap(); | |
04454e1e FG |
288 | Diagnostic { |
289 | message: translated_message.to_string(), | |
290 | code: None, | |
291 | level: "help", | |
292 | spans: DiagnosticSpan::from_suggestion(sugg, &args, je), | |
293 | children: vec![], | |
294 | rendered: None, | |
295 | } | |
7cac9316 | 296 | }); |
ff7c6d11 XL |
297 | |
298 | // generate regular command line output and store it in the json | |
299 | ||
300 | // A threadsafe buffer for writing. | |
301 | #[derive(Default, Clone)] | |
302 | struct BufWriter(Arc<Mutex<Vec<u8>>>); | |
303 | ||
304 | impl Write for BufWriter { | |
305 | fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | |
306 | self.0.lock().unwrap().write(buf) | |
307 | } | |
308 | fn flush(&mut self) -> io::Result<()> { | |
309 | self.0.lock().unwrap().flush() | |
310 | } | |
311 | } | |
add651ee FG |
312 | impl WriteColor for BufWriter { |
313 | fn supports_color(&self) -> bool { | |
314 | false | |
315 | } | |
316 | ||
317 | fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> { | |
318 | Ok(()) | |
319 | } | |
320 | ||
321 | fn reset(&mut self) -> io::Result<()> { | |
322 | Ok(()) | |
323 | } | |
324 | } | |
ff7c6d11 | 325 | |
c0240ec0 FG |
326 | let translated_message = je.translate_messages(&diag.messages, &args); |
327 | ||
328 | let code = if let Some(code) = diag.code { | |
329 | Some(DiagnosticCode { | |
330 | code: code.to_string(), | |
331 | explanation: je.registry.as_ref().unwrap().try_find_description(code).ok(), | |
332 | }) | |
333 | } else if let Some(IsLint { name, .. }) = &diag.is_lint { | |
334 | Some(DiagnosticCode { code: name.to_string(), explanation: None }) | |
335 | } else { | |
336 | None | |
337 | }; | |
c620b35d FG |
338 | let level = diag.level.to_str(); |
339 | let spans = DiagnosticSpan::from_multispan(&diag.span, &args, je); | |
340 | let children = diag | |
341 | .children | |
342 | .iter() | |
343 | .map(|c| Diagnostic::from_sub_diagnostic(c, &args, je)) | |
344 | .chain(sugg) | |
345 | .collect(); | |
346 | ||
347 | let buf = BufWriter::default(); | |
348 | let mut dst: Destination = Box::new(buf.clone()); | |
349 | let (short, color_config) = je.json_rendered.unzip(); | |
350 | match color_config { | |
351 | ColorConfig::Always | ColorConfig::Auto => dst = Box::new(termcolor::Ansi::new(dst)), | |
352 | ColorConfig::Never => {} | |
353 | } | |
354 | HumanEmitter::new(dst, je.fallback_bundle.clone()) | |
355 | .short_message(short) | |
356 | .sm(Some(je.sm.clone())) | |
357 | .fluent_bundle(je.fluent_bundle.clone()) | |
358 | .diagnostic_width(je.diagnostic_width) | |
359 | .macro_backtrace(je.macro_backtrace) | |
360 | .track_diagnostics(je.track_diagnostics) | |
361 | .terminal_url(je.terminal_url) | |
362 | .ui_testing(je.ui_testing) | |
363 | .ignored_directories_in_source_blocks(je.ignored_directories_in_source_blocks.clone()) | |
364 | .emit_diagnostic(diag); | |
365 | let buf = Arc::try_unwrap(buf.0).unwrap().into_inner().unwrap(); | |
366 | let buf = String::from_utf8(buf).unwrap(); | |
c0240ec0 | 367 | |
9cc50fc6 | 368 | Diagnostic { |
04454e1e | 369 | message: translated_message.to_string(), |
c0240ec0 | 370 | code, |
c620b35d FG |
371 | level, |
372 | spans, | |
373 | children, | |
374 | rendered: Some(buf), | |
9cc50fc6 SL |
375 | } |
376 | } | |
377 | ||
04454e1e | 378 | fn from_sub_diagnostic( |
c620b35d | 379 | subdiag: &Subdiag, |
04454e1e FG |
380 | args: &FluentArgs<'_>, |
381 | je: &JsonEmitter, | |
382 | ) -> Diagnostic { | |
c620b35d | 383 | let translated_message = je.translate_messages(&subdiag.messages, args); |
9cc50fc6 | 384 | Diagnostic { |
04454e1e | 385 | message: translated_message.to_string(), |
9cc50fc6 | 386 | code: None, |
c620b35d FG |
387 | level: subdiag.level.to_str(), |
388 | spans: DiagnosticSpan::from_multispan(&subdiag.span, args, je), | |
9cc50fc6 | 389 | children: vec![], |
7cac9316 | 390 | rendered: None, |
9cc50fc6 SL |
391 | } |
392 | } | |
393 | } | |
394 | ||
395 | impl DiagnosticSpan { | |
dfeec247 XL |
396 | fn from_span_label( |
397 | span: SpanLabel, | |
398 | suggestion: Option<(&String, Applicability)>, | |
04454e1e | 399 | args: &FluentArgs<'_>, |
dfeec247 XL |
400 | je: &JsonEmitter, |
401 | ) -> DiagnosticSpan { | |
04454e1e FG |
402 | Self::from_span_etc( |
403 | span.span, | |
404 | span.is_primary, | |
9c376795 FG |
405 | span.label |
406 | .as_ref() | |
407 | .map(|m| je.translate_message(m, args).unwrap()) | |
408 | .map(|m| m.to_string()), | |
04454e1e FG |
409 | suggestion, |
410 | je, | |
411 | ) | |
9cc50fc6 SL |
412 | } |
413 | ||
dfeec247 XL |
414 | fn from_span_etc( |
415 | span: Span, | |
416 | is_primary: bool, | |
417 | label: Option<String>, | |
418 | suggestion: Option<(&String, Applicability)>, | |
419 | je: &JsonEmitter, | |
420 | ) -> DiagnosticSpan { | |
a7813a04 XL |
421 | // obtain the full backtrace from the `macro_backtrace` |
422 | // helper; in some ways, it'd be better to expand the | |
423 | // backtrace ourselves, but the `macro_backtrace` helper makes | |
424 | // some decision, such as dropping some frames, and I don't | |
425 | // want to duplicate that logic here. | |
dfeec247 XL |
426 | let backtrace = span.macro_backtrace(); |
427 | DiagnosticSpan::from_span_full(span, is_primary, label, suggestion, backtrace, je) | |
a7813a04 XL |
428 | } |
429 | ||
dfeec247 | 430 | fn from_span_full( |
c620b35d | 431 | mut span: Span, |
dfeec247 XL |
432 | is_primary: bool, |
433 | label: Option<String>, | |
434 | suggestion: Option<(&String, Applicability)>, | |
435 | mut backtrace: impl Iterator<Item = ExpnData>, | |
436 | je: &JsonEmitter, | |
437 | ) -> DiagnosticSpan { | |
a1dfa0c6 | 438 | let start = je.sm.lookup_char_pos(span.lo()); |
c620b35d FG |
439 | // If this goes from the start of a line to the end and the replacement |
440 | // is an empty string, increase the length to include the newline so we don't | |
441 | // leave an empty line | |
442 | if start.col.0 == 0 | |
443 | && suggestion.map_or(false, |(s, _)| s.is_empty()) | |
444 | && let Ok(after) = je.sm.span_to_next_source(span) | |
445 | && after.starts_with('\n') | |
446 | { | |
447 | span = span.with_hi(span.hi() + rustc_span::BytePos(1)); | |
448 | } | |
a1dfa0c6 | 449 | let end = je.sm.lookup_char_pos(span.hi()); |
a7813a04 | 450 | let backtrace_step = backtrace.next().map(|bt| { |
dfeec247 | 451 | let call_site = Self::from_span_full(bt.call_site, false, None, None, backtrace, je); |
5e7ed085 FG |
452 | let def_site_span = Self::from_span_full( |
453 | je.sm.guess_head_span(bt.def_site), | |
454 | false, | |
455 | None, | |
456 | None, | |
457 | [].into_iter(), | |
458 | je, | |
459 | ); | |
a7813a04 XL |
460 | Box::new(DiagnosticSpanMacroExpansion { |
461 | span: call_site, | |
dfeec247 | 462 | macro_decl_name: bt.kind.descr(), |
3b2f2976 | 463 | def_site_span, |
a7813a04 XL |
464 | }) |
465 | }); | |
2c00a5a8 | 466 | |
a7813a04 | 467 | DiagnosticSpan { |
94222f64 | 468 | file_name: je.sm.filename_for_diagnostics(&start.file.name).to_string(), |
e74abb32 XL |
469 | byte_start: start.file.original_relative_byte_pos(span.lo()).0, |
470 | byte_end: start.file.original_relative_byte_pos(span.hi()).0, | |
a7813a04 XL |
471 | line_start: start.line, |
472 | line_end: end.line, | |
473 | column_start: start.col.0 + 1, | |
474 | column_end: end.col.0 + 1, | |
3b2f2976 | 475 | is_primary, |
a7813a04 | 476 | text: DiagnosticSpanLine::from_span(span, je), |
2c00a5a8 | 477 | suggested_replacement: suggestion.map(|x| x.0.clone()), |
94b46f34 | 478 | suggestion_applicability: suggestion.map(|x| x.1), |
a7813a04 | 479 | expansion: backtrace_step, |
3b2f2976 | 480 | label, |
9cc50fc6 SL |
481 | } |
482 | } | |
9cc50fc6 | 483 | |
04454e1e FG |
484 | fn from_multispan( |
485 | msp: &MultiSpan, | |
486 | args: &FluentArgs<'_>, | |
487 | je: &JsonEmitter, | |
488 | ) -> Vec<DiagnosticSpan> { | |
a7813a04 | 489 | msp.span_labels() |
dfeec247 | 490 | .into_iter() |
04454e1e | 491 | .map(|span_str| Self::from_span_label(span_str, None, args, je)) |
dfeec247 | 492 | .collect() |
a7813a04 XL |
493 | } |
494 | ||
04454e1e FG |
495 | fn from_suggestion( |
496 | suggestion: &CodeSuggestion, | |
497 | args: &FluentArgs<'_>, | |
498 | je: &JsonEmitter, | |
499 | ) -> Vec<DiagnosticSpan> { | |
dfeec247 XL |
500 | suggestion |
501 | .substitutions | |
502 | .iter() | |
503 | .flat_map(|substitution| { | |
504 | substitution.parts.iter().map(move |suggestion_inner| { | |
505 | let span_label = | |
506 | SpanLabel { span: suggestion_inner.span, is_primary: true, label: None }; | |
507 | DiagnosticSpan::from_span_label( | |
508 | span_label, | |
509 | Some((&suggestion_inner.snippet, suggestion.applicability)), | |
04454e1e | 510 | args, |
dfeec247 XL |
511 | je, |
512 | ) | |
513 | }) | |
514 | }) | |
515 | .collect() | |
a7813a04 | 516 | } |
54a0048b SL |
517 | } |
518 | ||
519 | impl DiagnosticSpanLine { | |
dfeec247 | 520 | fn line_from_source_file( |
74b04a01 | 521 | sf: &rustc_span::SourceFile, |
dfeec247 XL |
522 | index: usize, |
523 | h_start: usize, | |
524 | h_end: usize, | |
525 | ) -> DiagnosticSpanLine { | |
54a0048b | 526 | DiagnosticSpanLine { |
cdc7bbd5 | 527 | text: sf.get_line(index).map_or_else(String::new, |l| l.into_owned()), |
54a0048b SL |
528 | highlight_start: h_start, |
529 | highlight_end: h_end, | |
530 | } | |
531 | } | |
532 | ||
9fa01778 | 533 | /// Creates a list of DiagnosticSpanLines from span - each line with any part |
54a0048b SL |
534 | /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the |
535 | /// `span` within the line. | |
a7813a04 | 536 | fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> { |
dfeec247 XL |
537 | je.sm |
538 | .span_to_lines(span) | |
48663c56 | 539 | .map(|lines| { |
ba9703b0 | 540 | // We can't get any lines if the source is unavailable. |
781aab86 FG |
541 | if !should_show_source_code( |
542 | &je.ignored_directories_in_source_blocks, | |
543 | &je.sm, | |
544 | &lines.file, | |
545 | ) { | |
ba9703b0 XL |
546 | return vec![]; |
547 | } | |
548 | ||
74b04a01 | 549 | let sf = &*lines.file; |
dfeec247 XL |
550 | lines |
551 | .lines | |
48663c56 | 552 | .iter() |
dfeec247 XL |
553 | .map(|line| { |
554 | DiagnosticSpanLine::line_from_source_file( | |
74b04a01 | 555 | sf, |
dfeec247 XL |
556 | line.line_index, |
557 | line.start_col.0 + 1, | |
558 | line.end_col.0 + 1, | |
559 | ) | |
560 | }) | |
561 | .collect() | |
562 | }) | |
563 | .unwrap_or_else(|_| vec![]) | |
54a0048b SL |
564 | } |
565 | } |