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, FormatOptions};
12 use annotate_snippets::snippet::*;
13 use rustc_data_structures::sync::Lrc;
14 use rustc_span::source_map::SourceMap;
15 use rustc_span::{MultiSpan, SourceFile};
16 
17 /// Generates diagnostics using annotate-snippet
18 pub struct AnnotateSnippetEmitterWriter {
19     source_map: Option<Lrc<SourceMap>>,
20     /// If true, hides the longer explanation text
21     short_message: bool,
22     /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
23     ui_testing: bool,
24 
25     macro_backtrace: bool,
26 }
27 
28 impl Emitter for AnnotateSnippetEmitterWriter {
29     /// The entry point for the diagnostics generation
emit_diagnostic(&mut self, diag: &Diagnostic)30     fn emit_diagnostic(&mut self, diag: &Diagnostic) {
31         let mut children = diag.children.clone();
32         let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
33 
34         self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
35             &self.source_map,
36             &mut primary_span,
37             &mut children,
38             &diag.level,
39             self.macro_backtrace,
40         );
41 
42         self.emit_messages_default(
43             &diag.level,
44             diag.message(),
45             &diag.code,
46             &primary_span,
47             &children,
48             &suggestions,
49         );
50     }
51 
source_map(&self) -> Option<&Lrc<SourceMap>>52     fn source_map(&self) -> Option<&Lrc<SourceMap>> {
53         self.source_map.as_ref()
54     }
55 
should_show_explain(&self) -> bool56     fn should_show_explain(&self) -> bool {
57         !self.short_message
58     }
59 }
60 
61 /// Provides the source string for the given `line` of `file`
source_string(file: Lrc<SourceFile>, line: &Line) -> String62 fn source_string(file: Lrc<SourceFile>, line: &Line) -> String {
63     file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or_default()
64 }
65 
66 /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
annotation_type_for_level(level: Level) -> AnnotationType67 fn annotation_type_for_level(level: Level) -> AnnotationType {
68     match level {
69         Level::Bug | Level::Fatal | Level::Error => AnnotationType::Error,
70         Level::Warning => AnnotationType::Warning,
71         Level::Note => AnnotationType::Note,
72         Level::Help => AnnotationType::Help,
73         // FIXME(#59346): Not sure how to map these two levels
74         Level::Cancelled | Level::FailureNote => AnnotationType::Error,
75         Level::Allow => panic!("Should not call with Allow"),
76     }
77 }
78 
79 impl AnnotateSnippetEmitterWriter {
new( source_map: Option<Lrc<SourceMap>>, short_message: bool, macro_backtrace: bool, ) -> Self80     pub fn new(
81         source_map: Option<Lrc<SourceMap>>,
82         short_message: bool,
83         macro_backtrace: bool,
84     ) -> Self {
85         Self { source_map, short_message, ui_testing: false, macro_backtrace }
86     }
87 
88     /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
89     ///
90     /// If this is set to true, line numbers will be normalized as `LL` in the output.
ui_testing(mut self, ui_testing: bool) -> Self91     pub fn ui_testing(mut self, ui_testing: bool) -> Self {
92         self.ui_testing = ui_testing;
93         self
94     }
95 
emit_messages_default( &mut self, level: &Level, message: String, code: &Option<DiagnosticId>, msp: &MultiSpan, _children: &[SubDiagnostic], _suggestions: &[CodeSuggestion], )96     fn emit_messages_default(
97         &mut self,
98         level: &Level,
99         message: String,
100         code: &Option<DiagnosticId>,
101         msp: &MultiSpan,
102         _children: &[SubDiagnostic],
103         _suggestions: &[CodeSuggestion],
104     ) {
105         if let Some(source_map) = &self.source_map {
106             // Make sure our primary file comes first
107             let primary_lo = if let Some(ref primary_span) = msp.primary_span().as_ref() {
108                 if primary_span.is_dummy() {
109                     // FIXME(#59346): Not sure when this is the case and what
110                     // should be done if it happens
111                     return;
112                 } else {
113                     source_map.lookup_char_pos(primary_span.lo())
114                 }
115             } else {
116                 // FIXME(#59346): Not sure when this is the case and what
117                 // should be done if it happens
118                 return;
119             };
120             let mut annotated_files =
121                 FileWithAnnotatedLines::collect_annotations(msp, &self.source_map);
122             if let Ok(pos) =
123                 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
124             {
125                 annotated_files.swap(0, pos);
126             }
127             // owned: line source, line index, annotations
128             type Owned = (String, usize, Vec<crate::snippet::Annotation>);
129             let filename = primary_lo.file.name.prefer_local();
130             let origin = filename.to_string_lossy();
131             let annotated_files: Vec<Owned> = annotated_files
132                 .into_iter()
133                 .flat_map(|annotated_file| {
134                     let file = annotated_file.file;
135                     annotated_file
136                         .lines
137                         .into_iter()
138                         .map(|line| {
139                             (source_string(file.clone(), &line), line.line_index, line.annotations)
140                         })
141                         .collect::<Vec<Owned>>()
142                 })
143                 .collect();
144             let snippet = Snippet {
145                 title: Some(Annotation {
146                     label: Some(&message),
147                     id: code.as_ref().map(|c| match c {
148                         DiagnosticId::Error(val)
149                         | DiagnosticId::Lint { name: val, has_future_breakage: _ } => val.as_str(),
150                     }),
151                     annotation_type: annotation_type_for_level(*level),
152                 }),
153                 footer: vec![],
154                 opt: FormatOptions { color: true, anonymized_line_numbers: self.ui_testing },
155                 slices: annotated_files
156                     .iter()
157                     .map(|(source, line_index, annotations)| {
158                         Slice {
159                             source,
160                             line_start: *line_index,
161                             origin: Some(&origin),
162                             // FIXME(#59346): Not really sure when `fold` should be true or false
163                             fold: false,
164                             annotations: annotations
165                                 .iter()
166                                 .map(|annotation| SourceAnnotation {
167                                     range: (annotation.start_col, annotation.end_col),
168                                     label: annotation.label.as_deref().unwrap_or_default(),
169                                     annotation_type: annotation_type_for_level(*level),
170                                 })
171                                 .collect(),
172                         }
173                     })
174                     .collect(),
175             };
176             // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
177             // `emitter.rs` has the `Destination` enum that lists various possible output
178             // destinations.
179             eprintln!("{}", DisplayList::from(snippet))
180         }
181         // FIXME(#59346): Is it ok to return None if there's no source_map?
182     }
183 }
184