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