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`
141 fn source_string(file: Lrc<SourceFile>,
142 line: &Line) -> String {
143 let source_string = match file.get_line(line.line_index - 1) {
144 Some(s) => s.clone(),
145 None => return String::new(),
147 source_string.to_string()
150 /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
151 fn annotation_type_for_level(level: Level) -> AnnotationType {
153 Level::Bug | Level::Fatal | Level::PhaseFatal | Level::Error => AnnotationType::Error,
154 Level::Warning => AnnotationType::Warning,
155 Level::Note => AnnotationType::Note,
156 Level::Help => AnnotationType::Help,
157 // FIXME(#59346): Not sure how to map these two levels
158 Level::Cancelled | Level::FailureNote => AnnotationType::Error
163 impl AnnotateRsEmitterWriter {
165 source_map: Option<Lrc<SourceMapperDyn>>,
175 /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
177 /// If this is set to true, line numbers will be normalized as `LL` in the output.
178 // FIXME(#59346): This method is used via the public interface, but setting the `ui_testing`
179 // flag currently does not anonymize line numbers. We would have to add the `maybe_anonymized`
180 // method from `emitter.rs` and implement rust-lang/annotate-snippets-rs#2 in order to
181 // anonymize line numbers.
182 pub fn ui_testing(mut self, ui_testing: bool) -> Self {
183 self.ui_testing = ui_testing;
187 fn emit_messages_default(&mut self,
190 code: &Option<DiagnosticId>,
192 children: &[SubDiagnostic],
193 suggestions: &[CodeSuggestion]
195 let converter = DiagnosticConverter {
196 source_map: self.source_map.clone(),
197 level: level.clone(),
198 message: message.clone(),
204 if let Some(snippet) = converter.to_annotation_snippet() {
205 let dl = DisplayList::from(snippet);
206 let dlf = DisplayListFormatter::new(true);
207 print!("{}", dlf.format(&dl));