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};
10 Level, CodeSuggestion, Diagnostic, Emitter,
11 SourceMapperDyn, SubDiagnostic, DiagnosticId
13 use crate::emitter::FileWithAnnotatedLines;
14 use rustc_data_structures::sync::Lrc;
15 use crate::snippet::Line;
16 use annotate_snippets::snippet::*;
17 use annotate_snippets::display_list::DisplayList;
18 use annotate_snippets::formatter::DisplayListFormatter;
21 /// Generates diagnostics using annotate-snippet
22 pub struct AnnotateSnippetEmitterWriter {
23 source_map: Option<Lrc<SourceMapperDyn>>,
24 /// If true, hides the longer explanation text
26 /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
29 external_macro_backtrace: bool,
32 impl Emitter for AnnotateSnippetEmitterWriter {
33 /// The entry point for the diagnostics generation
34 fn emit_diagnostic(&mut self, diag: &Diagnostic) {
35 let mut children = diag.children.clone();
36 let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
38 self.fix_multispans_in_std_macros(&self.source_map,
42 self.external_macro_backtrace);
44 self.emit_messages_default(&diag.level,
52 fn source_map(&self) -> Option<&Lrc<SourceMapperDyn>> {
53 self.source_map.as_ref()
56 fn should_show_explain(&self) -> bool {
61 /// Collects all the data needed to generate the data structures needed for the
62 /// `annotate-snippets` library.
63 struct DiagnosticConverter<'a> {
64 source_map: Option<Lrc<SourceMapperDyn>>,
67 code: Option<DiagnosticId>,
70 children: &'a [SubDiagnostic],
72 suggestions: &'a [CodeSuggestion]
75 impl<'a> DiagnosticConverter<'a> {
76 /// Turns rustc Diagnostic information into a `annotate_snippets::snippet::Snippet`.
77 fn to_annotation_snippet(&self) -> Option<Snippet> {
78 if let Some(source_map) = &self.source_map {
79 // Make sure our primary file comes first
80 let primary_lo = if let Some(ref primary_span) =
81 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
88 let annotated_files = FileWithAnnotatedLines::collect_annotations(
92 let slices = self.slices_for_files(annotated_files, primary_lo);
95 title: Some(Annotation {
96 label: Some(self.message.to_string()),
97 id: self.code.clone().map(|c| {
99 DiagnosticId::Error(val) | DiagnosticId::Lint(val) => val
102 annotation_type: Self::annotation_type_for_level(self.level),
108 // FIXME(#59346): Is it ok to return None if there's no source_map?
115 annotated_files: Vec<FileWithAnnotatedLines>,
118 // FIXME(#64205): Provide a test case where `annotated_files` is > 1
119 annotated_files.iter().flat_map(|annotated_file| {
120 annotated_file.lines.iter().map(|line| {
121 let line_source = Self::source_string(annotated_file.file.clone(), &line);
124 line_start: line.line_index,
125 origin: Some(primary_lo.file.name.to_string()),
126 // FIXME(#59346): Not really sure when `fold` should be true or false
128 annotations: line.annotations.iter().map(|a| {
129 self.annotation_to_source_annotation(a.clone())
132 }).collect::<Vec<Slice>>()
133 }).collect::<Vec<Slice>>()
136 /// Turns a `crate::snippet::Annotation` into a `SourceAnnotation`
137 fn annotation_to_source_annotation(
139 annotation: crate::snippet::Annotation
140 ) -> SourceAnnotation {
142 range: (annotation.start_col, annotation.end_col),
143 label: annotation.label.unwrap_or("".to_string()),
144 annotation_type: Self::annotation_type_for_level(self.level)
148 /// Provides the source string for the given `line` of `file`
150 file: Lrc<SourceFile>,
153 file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or(String::new())
156 /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
157 fn annotation_type_for_level(level: Level) -> AnnotationType {
159 Level::Bug | Level::Fatal | Level::Error => AnnotationType::Error,
160 Level::Warning => AnnotationType::Warning,
161 Level::Note => AnnotationType::Note,
162 Level::Help => AnnotationType::Help,
163 // FIXME(#59346): Not sure how to map these two levels
164 Level::Cancelled | Level::FailureNote => AnnotationType::Error
169 impl AnnotateSnippetEmitterWriter {
171 source_map: Option<Lrc<SourceMapperDyn>>,
173 external_macro_backtrace: bool,
179 external_macro_backtrace,
183 /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
185 /// If this is set to true, line numbers will be normalized as `LL` in the output.
186 pub fn ui_testing(mut self, ui_testing: bool) -> Self {
187 self.ui_testing = ui_testing;
191 fn emit_messages_default(
195 code: &Option<DiagnosticId>,
197 children: &[SubDiagnostic],
198 suggestions: &[CodeSuggestion]
200 let converter = DiagnosticConverter {
201 source_map: self.source_map.clone(),
202 level: level.clone(),
209 if let Some(snippet) = converter.to_annotation_snippet() {
210 let dl = DisplayList::from(snippet);
211 let dlf = DisplayListFormatter::new(true, self.ui_testing);
212 // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
213 // `emitter.rs` has the `Destination` enum that lists various possible output
215 eprintln!("{}", dlf.format(&dl));