]> git.lizzy.rs Git - rust.git/blob - src/librustc_errors/annotate_rs_emitter.rs
Simplify source_string and block-format methods
[rust.git] / src / librustc_errors / annotate_rs_emitter.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-rs
22 pub struct AnnotateRsEmitterWriter {
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 AnnotateRsEmitterWriter {
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: 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 AnnotateRsEmitterWriter {
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     // FIXME(#59346): This method is used via the public interface, but setting the `ui_testing`
177     // flag currently does not anonymize line numbers. We would have to add the `maybe_anonymized`
178     // method from `emitter.rs` and implement rust-lang/annotate-snippets-rs#2 in order to
179     // anonymize line numbers.
180     pub fn ui_testing(mut self, ui_testing: bool) -> Self {
181         self.ui_testing = ui_testing;
182         self
183     }
184
185     fn emit_messages_default(
186         &mut self,
187         level: &Level,
188         message: String,
189         code: &Option<DiagnosticId>,
190         msp: &MultiSpan,
191         children: &[SubDiagnostic],
192         suggestions: &[CodeSuggestion]
193     ) {
194         let converter = DiagnosticConverter {
195             source_map: self.source_map.clone(),
196             level: level.clone(),
197             message: message.clone(),
198             code: code.clone(),
199             msp: msp.clone(),
200             children,
201             suggestions
202         };
203         if let Some(snippet) = converter.to_annotation_snippet() {
204             let dl = DisplayList::from(snippet);
205             let dlf = DisplayListFormatter::new(true);
206             // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
207             // `emitter.rs` has the `Destination` enum that lists various possible output
208             // destinations.
209             eprint!("{}", dlf.format(&dl));
210         };
211     }
212 }