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