|
| 1 | +/// Emit diagnostics using the `annotate-snippets` library |
| 2 | +/// |
| 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. |
| 5 | +/// |
| 6 | +/// [annotate_snippets]: https://docs.rs/crate/annotate-snippets/ |
| 7 | +
|
| 8 | +use syntax_pos::{SourceFile, MultiSpan, Loc}; |
| 9 | +use crate::{ |
| 10 | + Level, CodeSuggestion, DiagnosticBuilder, Emitter, |
| 11 | + SourceMapperDyn, SubDiagnostic, DiagnosticId |
| 12 | +}; |
| 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; |
| 19 | + |
| 20 | + |
| 21 | +/// Generates diagnostics using annotate-rs |
| 22 | +pub struct AnnotateRsEmitterWriter { |
| 23 | + source_map: Option<Lrc<SourceMapperDyn>>, |
| 24 | + /// If true, hides the longer explanation text |
| 25 | + short_message: bool, |
| 26 | + /// If true, will normalize line numbers with LL to prevent noise in UI test diffs. |
| 27 | + ui_testing: bool, |
| 28 | +} |
| 29 | + |
| 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: &[_] = &[]; |
| 37 | + |
| 38 | + // FIXME(#59346): Add `fix_multispans_in_std_macros` function from emitter.rs |
| 39 | + |
| 40 | + self.emit_messages_default(&db.level, |
| 41 | + db.message(), |
| 42 | + &db.code, |
| 43 | + &primary_span, |
| 44 | + &children, |
| 45 | + &suggestions); |
| 46 | + } |
| 47 | + |
| 48 | + fn should_show_explain(&self) -> bool { |
| 49 | + !self.short_message |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 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>>, |
| 57 | + level: Level, |
| 58 | + message: String, |
| 59 | + code: Option<DiagnosticId>, |
| 60 | + msp: MultiSpan, |
| 61 | + #[allow(dead_code)] |
| 62 | + children: &'a [SubDiagnostic], |
| 63 | + #[allow(dead_code)] |
| 64 | + suggestions: &'a [CodeSuggestion] |
| 65 | +} |
| 66 | + |
| 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()) |
| 75 | + } else { |
| 76 | + // FIXME(#59346): Not sure when this is the case and what |
| 77 | + // should be done if it happens |
| 78 | + return None |
| 79 | + }; |
| 80 | + let annotated_files = FileWithAnnotatedLines::collect_annotations( |
| 81 | + &self.msp, |
| 82 | + &self.source_map |
| 83 | + ); |
| 84 | + let slices = self.slices_for_files(annotated_files, primary_lo); |
| 85 | + |
| 86 | + Some(Snippet { |
| 87 | + title: Some(Annotation { |
| 88 | + label: Some(self.message.to_string()), |
| 89 | + id: self.code.clone().map(|c| { |
| 90 | + match c { |
| 91 | + DiagnosticId::Error(val) | DiagnosticId::Lint(val) => val |
| 92 | + } |
| 93 | + }), |
| 94 | + annotation_type: Self::annotation_type_for_level(self.level), |
| 95 | + }), |
| 96 | + footer: vec![], |
| 97 | + slices: slices, |
| 98 | + }) |
| 99 | + } else { |
| 100 | + // FIXME(#59346): Is it ok to return None if there's no source_map? |
| 101 | + None |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + fn slices_for_files( |
| 106 | + &self, |
| 107 | + annotated_files: Vec<FileWithAnnotatedLines>, |
| 108 | + primary_lo: Loc |
| 109 | + ) -> Vec<Slice> { |
| 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); |
| 114 | + Slice { |
| 115 | + source: line_source, |
| 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 |
| 119 | + fold: false, |
| 120 | + annotations: line.annotations.iter().map(|a| { |
| 121 | + self.annotation_to_source_annotation(a.clone()) |
| 122 | + }).collect(), |
| 123 | + } |
| 124 | + }).collect::<Vec<Slice>>() |
| 125 | + }).collect::<Vec<Slice>>() |
| 126 | + } |
| 127 | + |
| 128 | + /// Turns a `crate::snippet::Annotation` into a `SourceAnnotation` |
| 129 | + fn annotation_to_source_annotation( |
| 130 | + &self, |
| 131 | + annotation: crate::snippet::Annotation |
| 132 | + ) -> SourceAnnotation { |
| 133 | + 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) |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + /// Provides the source string for the given `line` of `file` |
| 141 | + fn source_string( |
| 142 | + file: Lrc<SourceFile>, |
| 143 | + line: &Line |
| 144 | + ) -> String { |
| 145 | + file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or(String::new()) |
| 146 | + } |
| 147 | + |
| 148 | + /// Maps `Diagnostic::Level` to `snippet::AnnotationType` |
| 149 | + fn annotation_type_for_level(level: Level) -> AnnotationType { |
| 150 | + match level { |
| 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 |
| 157 | + } |
| 158 | + } |
| 159 | +} |
| 160 | + |
| 161 | +impl AnnotateRsEmitterWriter { |
| 162 | + pub fn new( |
| 163 | + source_map: Option<Lrc<SourceMapperDyn>>, |
| 164 | + short_message: bool |
| 165 | + ) -> Self { |
| 166 | + Self { |
| 167 | + source_map, |
| 168 | + short_message, |
| 169 | + ui_testing: false, |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + /// Allows to modify `Self` to enable or disable the `ui_testing` flag. |
| 174 | + /// |
| 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; |
| 182 | + self |
| 183 | + } |
| 184 | + |
| 185 | + fn emit_messages_default( |
| 186 | + &mut self, |
| 187 | + level: &Level, |
| 188 | + message: String, |
| 189 | + code: &Option<DiagnosticId>, |
| 190 | + msp: &MultiSpan, |
| 191 | + children: &[SubDiagnostic], |
| 192 | + suggestions: &[CodeSuggestion] |
| 193 | + ) { |
| 194 | + let converter = DiagnosticConverter { |
| 195 | + source_map: self.source_map.clone(), |
| 196 | + level: level.clone(), |
| 197 | + message: message.clone(), |
| 198 | + code: code.clone(), |
| 199 | + msp: msp.clone(), |
| 200 | + children, |
| 201 | + suggestions |
| 202 | + }; |
| 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 |
| 208 | + // destinations. |
| 209 | + eprintln!("{}", dlf.format(&dl)); |
| 210 | + }; |
| 211 | + } |
| 212 | +} |
0 commit comments