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, DiagnosticBuilder, 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-rs
22 pub struct AnnotateRsEmitterWriter {
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.
30 impl Emitter for AnnotateRsEmitterWriter {
31 /// The entry point for the diagnostics generation
32 fn emit_diagnostic(&mut self, db: &DiagnosticBuilder<'_>) {
33 let primary_span = db.span.clone();
34 let children = db.children.clone();
35 // FIXME(#59346): Collect suggestions (see emitter.rs)
36 let suggestions: &[_] = &[];
38 // FIXME(#59346): Add `fix_multispans_in_std_macros` function from emitter.rs
40 self.emit_messages_default(&db.level,
48 fn should_show_explain(&self) -> bool {
53 /// Collects all the data needed to generate the data structures needed for the
54 /// `annotate-snippets` library.
55 struct DiagnosticConverter<'a> {
56 source_map: Option<Lrc<SourceMapperDyn>>,
59 code: Option<DiagnosticId>,
62 children: &'a [SubDiagnostic],
64 suggestions: &'a [CodeSuggestion]
67 impl<'a> DiagnosticConverter<'a> {
68 /// Turns rustc Diagnostic information into a `annotate_snippets::snippet::Snippet`.
69 fn to_annotation_snippet(&self) -> Option<Snippet> {
70 if let Some(source_map) = &self.source_map {
71 // Make sure our primary file comes first
72 let primary_lo = if let Some(ref primary_span) =
73 self.msp.primary_span().as_ref() {
74 source_map.lookup_char_pos(primary_span.lo())
76 // FIXME(#59346): Not sure when this is the case and what
77 // should be done if it happens
80 let annotated_files = FileWithAnnotatedLines::collect_annotations(
84 let slices = self.slices_for_files(annotated_files, primary_lo);
87 title: Some(Annotation {
88 label: Some(self.message.to_string()),
89 id: self.code.clone().map(|c| {
91 DiagnosticId::Error(val) | DiagnosticId::Lint(val) => val
94 annotation_type: Self::annotation_type_for_level(self.level),
100 // FIXME(#59346): Is it ok to return None if there's no source_map?
107 annotated_files: Vec<FileWithAnnotatedLines>,
110 // FIXME(#59346): Provide a test case where `annotated_files` is > 1
111 annotated_files.iter().flat_map(|annotated_file| {
112 annotated_file.lines.iter().map(|line| {
113 let line_source = Self::source_string(annotated_file.file.clone(), &line);
116 line_start: line.line_index,
117 origin: Some(primary_lo.file.name.to_string()),
118 // FIXME(#59346): Not really sure when `fold` should be true or false
120 annotations: line.annotations.iter().map(|a| {
121 self.annotation_to_source_annotation(a.clone())
124 }).collect::<Vec<Slice>>()
125 }).collect::<Vec<Slice>>()
128 /// Turns a `crate::snippet::Annotation` into a `SourceAnnotation`
129 fn annotation_to_source_annotation(
131 annotation: crate::snippet::Annotation
132 ) -> SourceAnnotation {
134 range: (annotation.start_col, annotation.end_col),
135 label: annotation.label.unwrap_or("".to_string()),
136 annotation_type: Self::annotation_type_for_level(self.level)
140 /// Provides the source string for the given `line` of `file`
142 file: Lrc<SourceFile>,
145 file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or(String::new())
148 /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
149 fn annotation_type_for_level(level: Level) -> AnnotationType {
151 Level::Bug | Level::Fatal | Level::PhaseFatal | Level::Error => AnnotationType::Error,
152 Level::Warning => AnnotationType::Warning,
153 Level::Note => AnnotationType::Note,
154 Level::Help => AnnotationType::Help,
155 // FIXME(#59346): Not sure how to map these two levels
156 Level::Cancelled | Level::FailureNote => AnnotationType::Error
161 impl AnnotateRsEmitterWriter {
163 source_map: Option<Lrc<SourceMapperDyn>>,
173 /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
175 /// If this is set to true, line numbers will be normalized as `LL` in the output.
176 // FIXME(#59346): This method is used via the public interface, but setting the `ui_testing`
177 // flag currently does not anonymize line numbers. We would have to add the `maybe_anonymized`
178 // method from `emitter.rs` and implement rust-lang/annotate-snippets-rs#2 in order to
179 // anonymize line numbers.
180 pub fn ui_testing(mut self, ui_testing: bool) -> Self {
181 self.ui_testing = ui_testing;
185 fn emit_messages_default(
189 code: &Option<DiagnosticId>,
191 children: &[SubDiagnostic],
192 suggestions: &[CodeSuggestion]
194 let converter = DiagnosticConverter {
195 source_map: self.source_map.clone(),
196 level: level.clone(),
197 message: message.clone(),
203 if let Some(snippet) = converter.to_annotation_snippet() {
204 let dl = DisplayList::from(snippet);
205 let dlf = DisplayListFormatter::new(true);
206 // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
207 // `emitter.rs` has the `Destination` enum that lists various possible output
209 eprint!("{}", dlf.format(&dl));