]> git.lizzy.rs Git - rust.git/blob - src/librustc_errors/annotate_snippet_emitter_writer.rs
Rollup merge of #66427 - Mark-Simulacrum:errors-json, r=Centril
[rust.git] / src / librustc_errors / annotate_snippet_emitter_writer.rs
1 //! Emit diagnostics using the `annotate-snippets` library
2 //!
3 //! This is the equivalent of `./emitter.rs` but making use of the
4 //! [`annotate-snippets`][annotate_snippets] library instead of building the output ourselves.
5 //!
6 //! [annotate_snippets]: https://docs.rs/crate/annotate-snippets/
7
8 use syntax_pos::{SourceFile, MultiSpan, Loc};
9 use syntax_pos::source_map::SourceMap;
10 use crate::{
11     Level, CodeSuggestion, Diagnostic, Emitter,
12     SubDiagnostic, DiagnosticId
13 };
14 use crate::emitter::FileWithAnnotatedLines;
15 use rustc_data_structures::sync::Lrc;
16 use crate::snippet::Line;
17 use annotate_snippets::snippet::*;
18 use annotate_snippets::display_list::DisplayList;
19 use annotate_snippets::formatter::DisplayListFormatter;
20
21
22 /// Generates diagnostics using annotate-snippet
23 pub struct AnnotateSnippetEmitterWriter {
24     source_map: Option<Lrc<SourceMap>>,
25     /// If true, hides the longer explanation text
26     short_message: bool,
27     /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
28     ui_testing: bool,
29
30     external_macro_backtrace: bool,
31 }
32
33 impl Emitter for AnnotateSnippetEmitterWriter {
34     /// The entry point for the diagnostics generation
35     fn emit_diagnostic(&mut self, diag: &Diagnostic) {
36         let mut children = diag.children.clone();
37         let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
38
39         self.fix_multispans_in_std_macros(&self.source_map,
40                                           &mut primary_span,
41                                           &mut children,
42                                           &diag.level,
43                                           self.external_macro_backtrace);
44
45         self.emit_messages_default(&diag.level,
46                                    diag.message(),
47                                    &diag.code,
48                                    &primary_span,
49                                    &children,
50                                    &suggestions);
51     }
52
53     fn source_map(&self) -> Option<&Lrc<SourceMap>> {
54         self.source_map.as_ref()
55     }
56
57     fn should_show_explain(&self) -> bool {
58         !self.short_message
59     }
60 }
61
62 /// Collects all the data needed to generate the data structures needed for the
63 /// `annotate-snippets` library.
64 struct DiagnosticConverter<'a> {
65     source_map: Option<Lrc<SourceMap>>,
66     level: Level,
67     message: String,
68     code: Option<DiagnosticId>,
69     msp: MultiSpan,
70     #[allow(dead_code)]
71     children: &'a [SubDiagnostic],
72     #[allow(dead_code)]
73     suggestions: &'a [CodeSuggestion]
74 }
75
76 impl<'a>  DiagnosticConverter<'a> {
77     /// Turns rustc Diagnostic information into a `annotate_snippets::snippet::Snippet`.
78     fn to_annotation_snippet(&self) -> Option<Snippet> {
79         if let Some(source_map) = &self.source_map {
80             // Make sure our primary file comes first
81             let primary_lo = if let Some(ref primary_span) =
82                 self.msp.primary_span().as_ref() {
83                 source_map.lookup_char_pos(primary_span.lo())
84             } else {
85                 // FIXME(#59346): Not sure when this is the case and what
86                 // should be done if it happens
87                 return None
88             };
89             let annotated_files = FileWithAnnotatedLines::collect_annotations(
90                 &self.msp,
91                 &self.source_map
92             );
93             let slices = self.slices_for_files(annotated_files, primary_lo);
94
95             Some(Snippet {
96                 title: Some(Annotation {
97                     label: Some(self.message.to_string()),
98                     id: self.code.clone().map(|c| {
99                         match c {
100                             DiagnosticId::Error(val) | DiagnosticId::Lint(val) => val
101                         }
102                     }),
103                     annotation_type: Self::annotation_type_for_level(self.level),
104                 }),
105                 footer: vec![],
106                 slices,
107             })
108         } else {
109             // FIXME(#59346): Is it ok to return None if there's no source_map?
110             None
111         }
112     }
113
114     fn slices_for_files(
115         &self,
116         annotated_files: Vec<FileWithAnnotatedLines>,
117         primary_lo: Loc
118     ) -> Vec<Slice> {
119         // FIXME(#64205): Provide a test case where `annotated_files` is > 1
120         annotated_files.iter().flat_map(|annotated_file| {
121             annotated_file.lines.iter().map(|line| {
122                 let line_source = Self::source_string(annotated_file.file.clone(), &line);
123                 Slice {
124                     source: line_source,
125                     line_start: line.line_index,
126                     origin: Some(primary_lo.file.name.to_string()),
127                     // FIXME(#59346): Not really sure when `fold` should be true or false
128                     fold: false,
129                     annotations: line.annotations.iter().map(|a| {
130                         self.annotation_to_source_annotation(a.clone())
131                     }).collect(),
132                 }
133             }).collect::<Vec<Slice>>()
134         }).collect::<Vec<Slice>>()
135     }
136
137     /// Turns a `crate::snippet::Annotation` into a `SourceAnnotation`
138     fn annotation_to_source_annotation(
139         &self,
140         annotation: crate::snippet::Annotation
141     ) -> SourceAnnotation {
142         SourceAnnotation {
143             range: (annotation.start_col, annotation.end_col),
144             label: annotation.label.unwrap_or("".to_string()),
145             annotation_type: Self::annotation_type_for_level(self.level)
146         }
147     }
148
149     /// Provides the source string for the given `line` of `file`
150     fn source_string(
151         file: Lrc<SourceFile>,
152         line: &Line
153     ) -> String {
154         file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or(String::new())
155     }
156
157     /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
158     fn annotation_type_for_level(level: Level) -> AnnotationType {
159         match level {
160             Level::Bug | Level::Fatal | Level::Error => AnnotationType::Error,
161             Level::Warning => AnnotationType::Warning,
162             Level::Note => AnnotationType::Note,
163             Level::Help => AnnotationType::Help,
164             // FIXME(#59346): Not sure how to map these two levels
165             Level::Cancelled | Level::FailureNote => AnnotationType::Error
166         }
167     }
168 }
169
170 impl AnnotateSnippetEmitterWriter {
171     pub fn new(
172         source_map: Option<Lrc<SourceMap>>,
173         short_message: bool,
174         external_macro_backtrace: bool,
175     ) -> Self {
176         Self {
177             source_map,
178             short_message,
179             ui_testing: false,
180             external_macro_backtrace,
181         }
182     }
183
184     /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
185     ///
186     /// If this is set to true, line numbers will be normalized as `LL` in the output.
187     pub fn ui_testing(mut self, ui_testing: bool) -> Self {
188         self.ui_testing = ui_testing;
189         self
190     }
191
192     fn emit_messages_default(
193         &mut self,
194         level: &Level,
195         message: String,
196         code: &Option<DiagnosticId>,
197         msp: &MultiSpan,
198         children: &[SubDiagnostic],
199         suggestions: &[CodeSuggestion]
200     ) {
201         let converter = DiagnosticConverter {
202             source_map: self.source_map.clone(),
203             level: level.clone(),
204             message,
205             code: code.clone(),
206             msp: msp.clone(),
207             children,
208             suggestions
209         };
210         if let Some(snippet) = converter.to_annotation_snippet() {
211             let dl = DisplayList::from(snippet);
212             let dlf = DisplayListFormatter::new(true, self.ui_testing);
213             // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
214             // `emitter.rs` has the `Destination` enum that lists various possible output
215             // destinations.
216             eprintln!("{}", dlf.format(&dl));
217         };
218     }
219 }