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 syntax_pos::{SourceFile, MultiSpan, Loc};
9 use syntax_pos::source_map::SourceMap;
11 Level, CodeSuggestion, Diagnostic, Emitter,
12 SubDiagnostic, DiagnosticId
14 use crate::emitter::FileWithAnnotatedLines;
15 use rustc_data_structures::sync::Lrc;
16 use crate::snippet::Line;
17 use annotate_snippets::snippet::*;
18 use annotate_snippets::display_list::DisplayList;
19 use annotate_snippets::formatter::DisplayListFormatter;
22 /// Generates diagnostics using annotate-snippet
23 pub struct AnnotateSnippetEmitterWriter {
24 source_map: Option<Lrc<SourceMap>>,
25 /// If true, hides the longer explanation text
27 /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
30 external_macro_backtrace: bool,
33 impl Emitter for AnnotateSnippetEmitterWriter {
34 /// The entry point for the diagnostics generation
35 fn emit_diagnostic(&mut self, diag: &Diagnostic) {
36 let mut children = diag.children.clone();
37 let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
39 self.fix_multispans_in_std_macros(&self.source_map,
43 self.external_macro_backtrace);
45 self.emit_messages_default(&diag.level,
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) =
82 self.msp.primary_span().as_ref() {
83 source_map.lookup_char_pos(primary_span.lo())
85 // FIXME(#59346): Not sure when this is the case and what
86 // should be done if it happens
89 let annotated_files = FileWithAnnotatedLines::collect_annotations(
93 let slices = self.slices_for_files(annotated_files, primary_lo);
96 title: Some(Annotation {
97 label: Some(self.message.to_string()),
98 id: self.code.clone().map(|c| {
100 DiagnosticId::Error(val) | DiagnosticId::Lint(val) => val
103 annotation_type: Self::annotation_type_for_level(self.level),
109 // FIXME(#59346): Is it ok to return None if there's no source_map?
116 annotated_files: Vec<FileWithAnnotatedLines>,
119 // FIXME(#64205): Provide a test case where `annotated_files` is > 1
120 annotated_files.iter().flat_map(|annotated_file| {
121 annotated_file.lines.iter().map(|line| {
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
129 annotations: line.annotations.iter().map(|a| {
130 self.annotation_to_source_annotation(a.clone())
133 }).collect::<Vec<Slice>>()
134 }).collect::<Vec<Slice>>()
137 /// Turns a `crate::snippet::Annotation` into a `SourceAnnotation`
138 fn annotation_to_source_annotation(
140 annotation: crate::snippet::Annotation
141 ) -> SourceAnnotation {
143 range: (annotation.start_col, annotation.end_col),
144 label: annotation.label.unwrap_or("".to_string()),
145 annotation_type: Self::annotation_type_for_level(self.level)
149 /// Provides the source string for the given `line` of `file`
151 file: Lrc<SourceFile>,
154 file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or(String::new())
157 /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
158 fn annotation_type_for_level(level: Level) -> AnnotationType {
160 Level::Bug | Level::Fatal | Level::Error => AnnotationType::Error,
161 Level::Warning => AnnotationType::Warning,
162 Level::Note => AnnotationType::Note,
163 Level::Help => AnnotationType::Help,
164 // FIXME(#59346): Not sure how to map these two levels
165 Level::Cancelled | Level::FailureNote => AnnotationType::Error
170 impl AnnotateSnippetEmitterWriter {
172 source_map: Option<Lrc<SourceMap>>,
174 external_macro_backtrace: bool,
180 external_macro_backtrace,
184 /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
186 /// If this is set to true, line numbers will be normalized as `LL` in the output.
187 pub fn ui_testing(mut self, ui_testing: bool) -> Self {
188 self.ui_testing = ui_testing;
192 fn emit_messages_default(
196 code: &Option<DiagnosticId>,
198 children: &[SubDiagnostic],
199 suggestions: &[CodeSuggestion]
201 let converter = DiagnosticConverter {
202 source_map: self.source_map.clone(),
203 level: level.clone(),
210 if let Some(snippet) = converter.to_annotation_snippet() {
211 let dl = DisplayList::from(snippet);
212 let dlf = DisplayListFormatter::new(true, self.ui_testing);
213 // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
214 // `emitter.rs` has the `Destination` enum that lists various possible output
216 eprintln!("{}", dlf.format(&dl));