mirror of
				https://github.com/rust-lang/rust.git
				synced 2025-10-31 21:16:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			232 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			232 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! Emit diagnostics using the `annotate-snippets` library
 | |
| //!
 | |
| //! This is the equivalent of `./emitter.rs` but making use of the
 | |
| //! [`annotate-snippets`][annotate_snippets] library instead of building the output ourselves.
 | |
| //!
 | |
| //! [annotate_snippets]: https://docs.rs/crate/annotate-snippets/
 | |
| 
 | |
| use crate::emitter::FileWithAnnotatedLines;
 | |
| use crate::snippet::Line;
 | |
| use crate::translation::{to_fluent_args, Translate};
 | |
| use crate::{
 | |
|     CodeSuggestion, Diagnostic, DiagnosticId, DiagnosticMessage, Emitter, FluentBundle,
 | |
|     LazyFallbackBundle, Level, MultiSpan, Style, SubDiagnostic,
 | |
| };
 | |
| use annotate_snippets::display_list::{DisplayList, FormatOptions};
 | |
| use annotate_snippets::snippet::*;
 | |
| use rustc_data_structures::sync::Lrc;
 | |
| use rustc_error_messages::FluentArgs;
 | |
| use rustc_span::source_map::SourceMap;
 | |
| use rustc_span::SourceFile;
 | |
| 
 | |
| /// Generates diagnostics using annotate-snippet
 | |
| pub struct AnnotateSnippetEmitterWriter {
 | |
|     source_map: Option<Lrc<SourceMap>>,
 | |
|     fluent_bundle: Option<Lrc<FluentBundle>>,
 | |
|     fallback_bundle: LazyFallbackBundle,
 | |
| 
 | |
|     /// If true, hides the longer explanation text
 | |
|     short_message: bool,
 | |
|     /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
 | |
|     ui_testing: bool,
 | |
| 
 | |
|     macro_backtrace: bool,
 | |
| }
 | |
| 
 | |
| impl Translate for AnnotateSnippetEmitterWriter {
 | |
|     fn fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
 | |
|         self.fluent_bundle.as_ref()
 | |
|     }
 | |
| 
 | |
|     fn fallback_fluent_bundle(&self) -> &FluentBundle {
 | |
|         &self.fallback_bundle
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Emitter for AnnotateSnippetEmitterWriter {
 | |
|     /// The entry point for the diagnostics generation
 | |
|     fn emit_diagnostic(&mut self, diag: &Diagnostic) {
 | |
|         let fluent_args = to_fluent_args(diag.args());
 | |
| 
 | |
|         let mut children = diag.children.clone();
 | |
|         let (mut primary_span, suggestions) = self.primary_span_formatted(diag, &fluent_args);
 | |
| 
 | |
|         self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
 | |
|             &mut primary_span,
 | |
|             &mut children,
 | |
|             &diag.level,
 | |
|             self.macro_backtrace,
 | |
|         );
 | |
| 
 | |
|         self.emit_messages_default(
 | |
|             &diag.level,
 | |
|             &diag.message,
 | |
|             &fluent_args,
 | |
|             &diag.code,
 | |
|             &primary_span,
 | |
|             &children,
 | |
|             suggestions,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     fn source_map(&self) -> Option<&Lrc<SourceMap>> {
 | |
|         self.source_map.as_ref()
 | |
|     }
 | |
| 
 | |
|     fn should_show_explain(&self) -> bool {
 | |
|         !self.short_message
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Provides the source string for the given `line` of `file`
 | |
| fn source_string(file: Lrc<SourceFile>, line: &Line) -> String {
 | |
|     file.get_line(line.line_index - 1).map(|a| a.to_string()).unwrap_or_default()
 | |
| }
 | |
| 
 | |
| /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
 | |
| fn annotation_type_for_level(level: Level) -> AnnotationType {
 | |
|     match level {
 | |
|         Level::Bug | Level::DelayedBug | Level::Fatal | Level::Error { .. } => {
 | |
|             AnnotationType::Error
 | |
|         }
 | |
|         Level::Warning(_) => AnnotationType::Warning,
 | |
|         Level::Note | Level::OnceNote => AnnotationType::Note,
 | |
|         Level::Help | Level::OnceHelp => AnnotationType::Help,
 | |
|         // FIXME(#59346): Not sure how to map this level
 | |
|         Level::FailureNote => AnnotationType::Error,
 | |
|         Level::Allow => panic!("Should not call with Allow"),
 | |
|         Level::Expect(_) => panic!("Should not call with Expect"),
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl AnnotateSnippetEmitterWriter {
 | |
|     pub fn new(
 | |
|         source_map: Option<Lrc<SourceMap>>,
 | |
|         fluent_bundle: Option<Lrc<FluentBundle>>,
 | |
|         fallback_bundle: LazyFallbackBundle,
 | |
|         short_message: bool,
 | |
|         macro_backtrace: bool,
 | |
|     ) -> Self {
 | |
|         Self {
 | |
|             source_map,
 | |
|             fluent_bundle,
 | |
|             fallback_bundle,
 | |
|             short_message,
 | |
|             ui_testing: false,
 | |
|             macro_backtrace,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
 | |
|     ///
 | |
|     /// If this is set to true, line numbers will be normalized as `LL` in the output.
 | |
|     pub fn ui_testing(mut self, ui_testing: bool) -> Self {
 | |
|         self.ui_testing = ui_testing;
 | |
|         self
 | |
|     }
 | |
| 
 | |
|     fn emit_messages_default(
 | |
|         &mut self,
 | |
|         level: &Level,
 | |
|         messages: &[(DiagnosticMessage, Style)],
 | |
|         args: &FluentArgs<'_>,
 | |
|         code: &Option<DiagnosticId>,
 | |
|         msp: &MultiSpan,
 | |
|         _children: &[SubDiagnostic],
 | |
|         _suggestions: &[CodeSuggestion],
 | |
|     ) {
 | |
|         let message = self.translate_messages(messages, args);
 | |
|         if let Some(source_map) = &self.source_map {
 | |
|             // Make sure our primary file comes first
 | |
|             let primary_lo = if let Some(ref primary_span) = msp.primary_span().as_ref() {
 | |
|                 if primary_span.is_dummy() {
 | |
|                     // FIXME(#59346): Not sure when this is the case and what
 | |
|                     // should be done if it happens
 | |
|                     return;
 | |
|                 } else {
 | |
|                     source_map.lookup_char_pos(primary_span.lo())
 | |
|                 }
 | |
|             } else {
 | |
|                 // FIXME(#59346): Not sure when this is the case and what
 | |
|                 // should be done if it happens
 | |
|                 return;
 | |
|             };
 | |
|             let mut annotated_files = FileWithAnnotatedLines::collect_annotations(self, args, msp);
 | |
|             if let Ok(pos) =
 | |
|                 annotated_files.binary_search_by(|x| x.file.name.cmp(&primary_lo.file.name))
 | |
|             {
 | |
|                 annotated_files.swap(0, pos);
 | |
|             }
 | |
|             // owned: file name, line source, line index, annotations
 | |
|             type Owned = (String, String, usize, Vec<crate::snippet::Annotation>);
 | |
|             let annotated_files: Vec<Owned> = annotated_files
 | |
|                 .into_iter()
 | |
|                 .flat_map(|annotated_file| {
 | |
|                     let file = annotated_file.file;
 | |
|                     annotated_file
 | |
|                         .lines
 | |
|                         .into_iter()
 | |
|                         .map(|line| {
 | |
|                             // Ensure the source file is present before we try
 | |
|                             // to load a string from it.
 | |
|                             // FIXME(#115869): support -Z ignore-directory-in-diagnostics-source-blocks
 | |
|                             source_map.ensure_source_file_source_present(&file);
 | |
|                             (
 | |
|                                 format!("{}", source_map.filename_for_diagnostics(&file.name)),
 | |
|                                 source_string(file.clone(), &line),
 | |
|                                 line.line_index,
 | |
|                                 line.annotations,
 | |
|                             )
 | |
|                         })
 | |
|                         .collect::<Vec<Owned>>()
 | |
|                 })
 | |
|                 .collect();
 | |
|             let snippet = Snippet {
 | |
|                 title: Some(Annotation {
 | |
|                     label: Some(&message),
 | |
|                     id: code.as_ref().map(|c| match c {
 | |
|                         DiagnosticId::Error(val) | DiagnosticId::Lint { name: val, .. } => {
 | |
|                             val.as_str()
 | |
|                         }
 | |
|                     }),
 | |
|                     annotation_type: annotation_type_for_level(*level),
 | |
|                 }),
 | |
|                 footer: vec![],
 | |
|                 opt: FormatOptions {
 | |
|                     color: true,
 | |
|                     anonymized_line_numbers: self.ui_testing,
 | |
|                     margin: None,
 | |
|                 },
 | |
|                 slices: annotated_files
 | |
|                     .iter()
 | |
|                     .map(|(file_name, source, line_index, annotations)| {
 | |
|                         Slice {
 | |
|                             source,
 | |
|                             line_start: *line_index,
 | |
|                             origin: Some(&file_name),
 | |
|                             // FIXME(#59346): Not really sure when `fold` should be true or false
 | |
|                             fold: false,
 | |
|                             annotations: annotations
 | |
|                                 .iter()
 | |
|                                 .map(|annotation| SourceAnnotation {
 | |
|                                     range: (
 | |
|                                         annotation.start_col.display,
 | |
|                                         annotation.end_col.display,
 | |
|                                     ),
 | |
|                                     label: annotation.label.as_deref().unwrap_or_default(),
 | |
|                                     annotation_type: annotation_type_for_level(*level),
 | |
|                                 })
 | |
|                                 .collect(),
 | |
|                         }
 | |
|                     })
 | |
|                     .collect(),
 | |
|             };
 | |
|             // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
 | |
|             // `emitter.rs` has the `Destination` enum that lists various possible output
 | |
|             // destinations.
 | |
|             eprintln!("{}", DisplayList::from(snippet))
 | |
|         }
 | |
|         // FIXME(#59346): Is it ok to return None if there's no source_map?
 | |
|     }
 | |
| }
 | 
