]>
Commit | Line | Data |
---|---|---|
dc9dc135 XL |
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 | ||
dc9dc135 | 8 | use crate::emitter::FileWithAnnotatedLines; |
dc9dc135 | 9 | use crate::snippet::Line; |
2b03887a | 10 | use crate::translation::{to_fluent_args, Translate}; |
04454e1e FG |
11 | use crate::{ |
12 | CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, Emitter, FluentBundle, | |
13 | LazyFallbackBundle, Level, MultiSpan, Style, SubDiagnostic, | |
14 | }; | |
f035d41b | 15 | use annotate_snippets::display_list::{DisplayList, FormatOptions}; |
dfeec247 XL |
16 | use annotate_snippets::snippet::*; |
17 | use rustc_data_structures::sync::Lrc; | |
04454e1e | 18 | use rustc_error_messages::FluentArgs; |
dfeec247 | 19 | use rustc_span::source_map::SourceMap; |
04454e1e | 20 | use rustc_span::SourceFile; |
dc9dc135 XL |
21 | |
22 | /// Generates diagnostics using annotate-snippet | |
23 | pub struct AnnotateSnippetEmitterWriter { | |
60c5eb7d | 24 | source_map: Option<Lrc<SourceMap>>, |
04454e1e FG |
25 | fluent_bundle: Option<Lrc<FluentBundle>>, |
26 | fallback_bundle: LazyFallbackBundle, | |
27 | ||
dc9dc135 XL |
28 | /// If true, hides the longer explanation text |
29 | short_message: bool, | |
416331ca | 30 | /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs. |
dc9dc135 | 31 | ui_testing: bool, |
e1599b0c | 32 | |
74b04a01 | 33 | macro_backtrace: bool, |
dc9dc135 XL |
34 | } |
35 | ||
f2b60f7d FG |
36 | impl Translate for AnnotateSnippetEmitterWriter { |
37 | fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> { | |
38 | self.fluent_bundle.as_ref() | |
39 | } | |
40 | ||
41 | fn fallback_fluent_bundle(&self) -> &FluentBundle { | |
487cf647 | 42 | &self.fallback_bundle |
f2b60f7d FG |
43 | } |
44 | } | |
45 | ||
dc9dc135 XL |
46 | impl Emitter for AnnotateSnippetEmitterWriter { |
47 | /// The entry point for the diagnostics generation | |
e74abb32 | 48 | fn emit_diagnostic(&mut self, diag: &Diagnostic) { |
2b03887a | 49 | let fluent_args = to_fluent_args(diag.args()); |
04454e1e | 50 | |
e74abb32 | 51 | let mut children = diag.children.clone(); |
487cf647 | 52 | let (mut primary_span, suggestions) = self.primary_span_formatted(diag, &fluent_args); |
dc9dc135 | 53 | |
74b04a01 | 54 | self.fix_multispans_in_extern_macros_and_render_macro_backtrace( |
dfeec247 XL |
55 | &mut primary_span, |
56 | &mut children, | |
57 | &diag.level, | |
74b04a01 | 58 | self.macro_backtrace, |
dfeec247 XL |
59 | ); |
60 | ||
61 | self.emit_messages_default( | |
62 | &diag.level, | |
04454e1e FG |
63 | &diag.message, |
64 | &fluent_args, | |
dfeec247 XL |
65 | &diag.code, |
66 | &primary_span, | |
67 | &children, | |
487cf647 | 68 | suggestions, |
dfeec247 | 69 | ); |
dc9dc135 XL |
70 | } |
71 | ||
60c5eb7d | 72 | fn source_map(&self) -> Option<&Lrc<SourceMap>> { |
e74abb32 XL |
73 | self.source_map.as_ref() |
74 | } | |
75 | ||
dc9dc135 XL |
76 | fn should_show_explain(&self) -> bool { |
77 | !self.short_message | |
78 | } | |
79 | } | |
80 | ||
f035d41b XL |
81 | /// Provides the source string for the given `line` of `file` |
82 | fn source_string(file: Lrc<SourceFile>, line: &Line) -> String { | |
83 | file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or_default() | |
dc9dc135 XL |
84 | } |
85 | ||
f035d41b XL |
86 | /// Maps `Diagnostic::Level` to `snippet::AnnotationType` |
87 | fn annotation_type_for_level(level: Level) -> AnnotationType { | |
88 | match level { | |
5e7ed085 FG |
89 | Level::Bug | Level::DelayedBug | Level::Fatal | Level::Error { .. } => { |
90 | AnnotationType::Error | |
91 | } | |
923072b8 | 92 | Level::Warning(_) => AnnotationType::Warning, |
5e7ed085 | 93 | Level::Note | Level::OnceNote => AnnotationType::Note, |
f035d41b | 94 | Level::Help => AnnotationType::Help, |
5e7ed085 FG |
95 | // FIXME(#59346): Not sure how to map this level |
96 | Level::FailureNote => AnnotationType::Error, | |
29967ef6 | 97 | Level::Allow => panic!("Should not call with Allow"), |
5e7ed085 | 98 | Level::Expect(_) => panic!("Should not call with Expect"), |
dc9dc135 XL |
99 | } |
100 | } | |
101 | ||
102 | impl AnnotateSnippetEmitterWriter { | |
103 | pub fn new( | |
60c5eb7d | 104 | source_map: Option<Lrc<SourceMap>>, |
04454e1e FG |
105 | fluent_bundle: Option<Lrc<FluentBundle>>, |
106 | fallback_bundle: LazyFallbackBundle, | |
e1599b0c | 107 | short_message: bool, |
74b04a01 | 108 | macro_backtrace: bool, |
dc9dc135 | 109 | ) -> Self { |
04454e1e FG |
110 | Self { |
111 | source_map, | |
112 | fluent_bundle, | |
113 | fallback_bundle, | |
114 | short_message, | |
115 | ui_testing: false, | |
116 | macro_backtrace, | |
117 | } | |
dc9dc135 XL |
118 | } |
119 | ||
120 | /// Allows to modify `Self` to enable or disable the `ui_testing` flag. | |
121 | /// | |
122 | /// If this is set to true, line numbers will be normalized as `LL` in the output. | |
dc9dc135 XL |
123 | pub fn ui_testing(mut self, ui_testing: bool) -> Self { |
124 | self.ui_testing = ui_testing; | |
125 | self | |
126 | } | |
127 | ||
128 | fn emit_messages_default( | |
129 | &mut self, | |
130 | level: &Level, | |
04454e1e FG |
131 | messages: &[(DiagnosticMessage, Style)], |
132 | args: &FluentArgs<'_>, | |
dc9dc135 XL |
133 | code: &Option<DiagnosticId>, |
134 | msp: &MultiSpan, | |
f035d41b XL |
135 | _children: &[SubDiagnostic], |
136 | _suggestions: &[CodeSuggestion], | |
dc9dc135 | 137 | ) { |
04454e1e | 138 | let message = self.translate_messages(messages, args); |
f035d41b XL |
139 | if let Some(source_map) = &self.source_map { |
140 | // Make sure our primary file comes first | |
141 | let primary_lo = if let Some(ref primary_span) = msp.primary_span().as_ref() { | |
142 | if primary_span.is_dummy() { | |
143 | // FIXME(#59346): Not sure when this is the case and what | |
144 | // should be done if it happens | |
145 | return; | |
146 | } else { | |
147 | source_map.lookup_char_pos(primary_span.lo()) | |
148 | } | |
149 | } else { | |
150 | // FIXME(#59346): Not sure when this is the case and what | |
151 | // should be done if it happens | |
152 | return; | |
153 | }; | |
04454e1e | 154 | let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp); |
f035d41b XL |
155 | if let Ok(pos) = |
156 | annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name)) | |
157 | { | |
158 | annotated_files.swap(0, pos); | |
159 | } | |
160 | // owned: line source, line index, annotations | |
161 | type Owned = (String, usize, Vec<crate::snippet::Annotation>); | |
94222f64 | 162 | let filename = source_map.filename_for_diagnostics(&primary_lo.file.name); |
17df50a5 | 163 | let origin = filename.to_string_lossy(); |
f035d41b XL |
164 | let annotated_files: Vec<Owned> = annotated_files |
165 | .into_iter() | |
166 | .flat_map(|annotated_file| { | |
167 | let file = annotated_file.file; | |
168 | annotated_file | |
169 | .lines | |
170 | .into_iter() | |
171 | .map(|line| { | |
172 | (source_string(file.clone(), &line), line.line_index, line.annotations) | |
173 | }) | |
174 | .collect::<Vec<Owned>>() | |
175 | }) | |
176 | .collect(); | |
177 | let snippet = Snippet { | |
178 | title: Some(Annotation { | |
179 | label: Some(&message), | |
180 | id: code.as_ref().map(|c| match c { | |
136023e0 XL |
181 | DiagnosticId::Error(val) | DiagnosticId::Lint { name: val, .. } => { |
182 | val.as_str() | |
183 | } | |
f035d41b XL |
184 | }), |
185 | annotation_type: annotation_type_for_level(*level), | |
186 | }), | |
187 | footer: vec![], | |
f2b60f7d FG |
188 | opt: FormatOptions { |
189 | color: true, | |
190 | anonymized_line_numbers: self.ui_testing, | |
191 | margin: None, | |
192 | }, | |
f035d41b XL |
193 | slices: annotated_files |
194 | .iter() | |
195 | .map(|(source, line_index, annotations)| { | |
196 | Slice { | |
197 | source, | |
198 | line_start: *line_index, | |
199 | origin: Some(&origin), | |
200 | // FIXME(#59346): Not really sure when `fold` should be true or false | |
201 | fold: false, | |
202 | annotations: annotations | |
203 | .iter() | |
204 | .map(|annotation| SourceAnnotation { | |
353b0b11 FG |
205 | range: ( |
206 | annotation.start_col.display, | |
207 | annotation.end_col.display, | |
208 | ), | |
f035d41b XL |
209 | label: annotation.label.as_deref().unwrap_or_default(), |
210 | annotation_type: annotation_type_for_level(*level), | |
211 | }) | |
212 | .collect(), | |
213 | } | |
214 | }) | |
215 | .collect(), | |
216 | }; | |
dc9dc135 XL |
217 | // FIXME(#59346): Figure out if we can _always_ print to stderr or not. |
218 | // `emitter.rs` has the `Destination` enum that lists various possible output | |
219 | // destinations. | |
f035d41b XL |
220 | eprintln!("{}", DisplayList::from(snippet)) |
221 | } | |
222 | // FIXME(#59346): Is it ok to return None if there's no source_map? | |
dc9dc135 XL |
223 | } |
224 | } |