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