1 //! Emit diagnostics using the `annotate-snippets` library
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.
6 //! [annotate_snippets]: https://docs.rs/crate/annotate-snippets/
8 use crate::emitter::FileWithAnnotatedLines;
9 use crate::snippet::Line;
11 CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, Emitter, FluentBundle,
12 LazyFallbackBundle, Level, MultiSpan, Style, SubDiagnostic,
14 use annotate_snippets::display_list::{DisplayList, FormatOptions};
15 use annotate_snippets::snippet::*;
16 use rustc_data_structures::sync::Lrc;
17 use rustc_error_messages::FluentArgs;
18 use rustc_span::source_map::SourceMap;
19 use rustc_span::SourceFile;
21 /// Generates diagnostics using annotate-snippet
22 pub struct AnnotateSnippetEmitterWriter {
23 source_map: Option<Lrc<SourceMap>>,
24 fluent_bundle: Option<Lrc<FluentBundle>>,
25 fallback_bundle: LazyFallbackBundle,
27 /// If true, hides the longer explanation text
29 /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
32 macro_backtrace: bool,
35 impl Emitter for AnnotateSnippetEmitterWriter {
36 /// The entry point for the diagnostics generation
37 fn emit_diagnostic(&mut self, diag: &Diagnostic) {
38 let fluent_args = self.to_fluent_args(diag.args());
40 let mut children = diag.children.clone();
41 let (mut primary_span, suggestions) = self.primary_span_formatted(&diag, &fluent_args);
43 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
51 self.emit_messages_default(
62 fn source_map(&self) -> Option<&Lrc<SourceMap>> {
63 self.source_map.as_ref()
66 fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
67 self.fluent_bundle.as_ref()
70 fn fallback_fluent_bundle(&self) -> &FluentBundle {
71 &**self.fallback_bundle
74 fn should_show_explain(&self) -> bool {
79 /// Provides the source string for the given `line` of `file`
80 fn source_string(file: Lrc<SourceFile>, line: &Line) -> String {
81 file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or_default()
84 /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
85 fn annotation_type_for_level(level: Level) -> AnnotationType {
87 Level::Bug | Level::DelayedBug | Level::Fatal | Level::Error { .. } => {
90 Level::Warning(_) => AnnotationType::Warning,
91 Level::Note | Level::OnceNote => AnnotationType::Note,
92 Level::Help => AnnotationType::Help,
93 // FIXME(#59346): Not sure how to map this level
94 Level::FailureNote => AnnotationType::Error,
95 Level::Allow => panic!("Should not call with Allow"),
96 Level::Expect(_) => panic!("Should not call with Expect"),
100 impl AnnotateSnippetEmitterWriter {
102 source_map: Option<Lrc<SourceMap>>,
103 fluent_bundle: Option<Lrc<FluentBundle>>,
104 fallback_bundle: LazyFallbackBundle,
106 macro_backtrace: bool,
118 /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
120 /// If this is set to true, line numbers will be normalized as `LL` in the output.
121 pub fn ui_testing(mut self, ui_testing: bool) -> Self {
122 self.ui_testing = ui_testing;
126 fn emit_messages_default(
129 messages: &[(DiagnosticMessage, Style)],
130 args: &FluentArgs<'_>,
131 code: &Option<DiagnosticId>,
133 _children: &[SubDiagnostic],
134 _suggestions: &[CodeSuggestion],
136 let message = self.translate_messages(messages, args);
137 if let Some(source_map) = &self.source_map {
138 // Make sure our primary file comes first
139 let primary_lo = if let Some(ref primary_span) = msp.primary_span().as_ref() {
140 if primary_span.is_dummy() {
141 // FIXME(#59346): Not sure when this is the case and what
142 // should be done if it happens
145 source_map.lookup_char_pos(primary_span.lo())
148 // FIXME(#59346): Not sure when this is the case and what
149 // should be done if it happens
152 let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
154 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
156 annotated_files.swap(0, pos);
158 // owned: line source, line index, annotations
159 type Owned = (String, usize, Vec<crate::snippet::Annotation>);
160 let filename = source_map.filename_for_diagnostics(&primary_lo.file.name);
161 let origin = filename.to_string_lossy();
162 let annotated_files: Vec<Owned> = annotated_files
164 .flat_map(|annotated_file| {
165 let file = annotated_file.file;
170 (source_string(file.clone(), &line), line.line_index, line.annotations)
172 .collect::<Vec<Owned>>()
175 let snippet = Snippet {
176 title: Some(Annotation {
177 label: Some(&message),
178 id: code.as_ref().map(|c| match c {
179 DiagnosticId::Error(val) | DiagnosticId::Lint { name: val, .. } => {
183 annotation_type: annotation_type_for_level(*level),
186 opt: FormatOptions { color: true, anonymized_line_numbers: self.ui_testing },
187 slices: annotated_files
189 .map(|(source, line_index, annotations)| {
192 line_start: *line_index,
193 origin: Some(&origin),
194 // FIXME(#59346): Not really sure when `fold` should be true or false
196 annotations: annotations
198 .map(|annotation| SourceAnnotation {
199 range: (annotation.start_col, annotation.end_col),
200 label: annotation.label.as_deref().unwrap_or_default(),
201 annotation_type: annotation_type_for_level(*level),
208 // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
209 // `emitter.rs` has the `Destination` enum that lists various possible output
211 eprintln!("{}", DisplayList::from(snippet))
213 // FIXME(#59346): Is it ok to return None if there's no source_map?