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;
10 use crate::{CodeSuggestion, Diagnostic, DiagnosticId, Emitter, Level, SubDiagnostic};
11 use annotate_snippets::display_list::DisplayList;
12 use annotate_snippets::formatter::DisplayListFormatter;
13 use annotate_snippets::snippet::*;
14 use rustc_data_structures::sync::Lrc;
15 use rustc_span::source_map::SourceMap;
16 use rustc_span::{Loc, MultiSpan, SourceFile};
18 /// Generates diagnostics using annotate-snippet
19 pub struct AnnotateSnippetEmitterWriter {
20 source_map: Option<Lrc<SourceMap>>,
21 /// If true, hides the longer explanation text
23 /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
26 external_macro_backtrace: bool,
29 impl Emitter for AnnotateSnippetEmitterWriter {
30 /// The entry point for the diagnostics generation
31 fn emit_diagnostic(&mut self, diag: &Diagnostic) {
32 let mut children = diag.children.clone();
33 let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
35 self.render_multispans_macro_backtrace_and_fix_extern_macros(
40 self.external_macro_backtrace,
43 self.emit_messages_default(
53 fn source_map(&self) -> Option<&Lrc<SourceMap>> {
54 self.source_map.as_ref()
57 fn should_show_explain(&self) -> bool {
62 /// Collects all the data needed to generate the data structures needed for the
63 /// `annotate-snippets` library.
64 struct DiagnosticConverter<'a> {
65 source_map: Option<Lrc<SourceMap>>,
68 code: Option<DiagnosticId>,
71 children: &'a [SubDiagnostic],
73 suggestions: &'a [CodeSuggestion],
76 impl<'a> DiagnosticConverter<'a> {
77 /// Turns rustc Diagnostic information into a `annotate_snippets::snippet::Snippet`.
78 fn to_annotation_snippet(&self) -> Option<Snippet> {
79 if let Some(source_map) = &self.source_map {
80 // Make sure our primary file comes first
81 let primary_lo = if let Some(ref primary_span) = self.msp.primary_span().as_ref() {
82 source_map.lookup_char_pos(primary_span.lo())
84 // FIXME(#59346): Not sure when this is the case and what
85 // should be done if it happens
89 FileWithAnnotatedLines::collect_annotations(&self.msp, &self.source_map);
90 let slices = self.slices_for_files(annotated_files, primary_lo);
93 title: Some(Annotation {
94 label: Some(self.message.to_string()),
95 id: self.code.clone().map(|c| match c {
96 DiagnosticId::Error(val) | DiagnosticId::Lint(val) => val,
98 annotation_type: Self::annotation_type_for_level(self.level),
104 // FIXME(#59346): Is it ok to return None if there's no source_map?
111 annotated_files: Vec<FileWithAnnotatedLines>,
114 // FIXME(#64205): Provide a test case where `annotated_files` is > 1
117 .flat_map(|annotated_file| {
122 let line_source = Self::source_string(annotated_file.file.clone(), &line);
125 line_start: line.line_index,
126 origin: Some(primary_lo.file.name.to_string()),
127 // FIXME(#59346): Not really sure when `fold` should be true or false
132 .map(|a| self.annotation_to_source_annotation(a.clone()))
136 .collect::<Vec<Slice>>()
138 .collect::<Vec<Slice>>()
141 /// Turns a `crate::snippet::Annotation` into a `SourceAnnotation`
142 fn annotation_to_source_annotation(
144 annotation: crate::snippet::Annotation,
145 ) -> SourceAnnotation {
147 range: (annotation.start_col, annotation.end_col),
148 label: annotation.label.unwrap_or("".to_string()),
149 annotation_type: Self::annotation_type_for_level(self.level),
153 /// Provides the source string for the given `line` of `file`
154 fn source_string(file: Lrc<SourceFile>, line: &Line) -> String {
155 file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or(String::new())
158 /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
159 fn annotation_type_for_level(level: Level) -> AnnotationType {
161 Level::Bug | Level::Fatal | Level::Error => AnnotationType::Error,
162 Level::Warning => AnnotationType::Warning,
163 Level::Note => AnnotationType::Note,
164 Level::Help => AnnotationType::Help,
165 // FIXME(#59346): Not sure how to map these two levels
166 Level::Cancelled | Level::FailureNote => AnnotationType::Error,
171 impl AnnotateSnippetEmitterWriter {
173 source_map: Option<Lrc<SourceMap>>,
175 external_macro_backtrace: bool,
177 Self { source_map, short_message, ui_testing: false, external_macro_backtrace }
180 /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
182 /// If this is set to true, line numbers will be normalized as `LL` in the output.
183 pub fn ui_testing(mut self, ui_testing: bool) -> Self {
184 self.ui_testing = ui_testing;
188 fn emit_messages_default(
192 code: &Option<DiagnosticId>,
194 children: &[SubDiagnostic],
195 suggestions: &[CodeSuggestion],
197 let converter = DiagnosticConverter {
198 source_map: self.source_map.clone(),
206 if let Some(snippet) = converter.to_annotation_snippet() {
207 let dl = DisplayList::from(snippet);
208 let dlf = DisplayListFormatter::new(true, self.ui_testing);
209 // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
210 // `emitter.rs` has the `Destination` enum that lists various possible output
212 eprintln!("{}", dlf.format(&dl));