
Loading the fallback bundle in compilation sessions that won't go on to emit any errors unnecessarily degrades compile time performance, so lazily create the Fluent bundle when it is first required. Signed-off-by: David Wood <david.wood@huawei.com>
215 lines
8 KiB
Rust
215 lines
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::{
|
|
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 Emitter for AnnotateSnippetEmitterWriter {
|
|
/// The entry point for the diagnostics generation
|
|
fn emit_diagnostic(&mut self, diag: &Diagnostic) {
|
|
let fluent_args = self.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(
|
|
&self.source_map,
|
|
&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 fluent_bundle(&self) -> Option<&Lrc<FluentBundle>> {
|
|
self.fluent_bundle.as_ref()
|
|
}
|
|
|
|
fn fallback_fluent_bundle(&self) -> &FluentBundle {
|
|
&**self.fallback_bundle
|
|
}
|
|
|
|
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 => 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: line source, line index, annotations
|
|
type Owned = (String, usize, Vec<crate::snippet::Annotation>);
|
|
let filename = source_map.filename_for_diagnostics(&primary_lo.file.name);
|
|
let origin = filename.to_string_lossy();
|
|
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| {
|
|
(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 },
|
|
slices: annotated_files
|
|
.iter()
|
|
.map(|(source, line_index, annotations)| {
|
|
Slice {
|
|
source,
|
|
line_start: *line_index,
|
|
origin: Some(&origin),
|
|
// FIXME(#59346): Not really sure when `fold` should be true or false
|
|
fold: false,
|
|
annotations: annotations
|
|
.iter()
|
|
.map(|annotation| SourceAnnotation {
|
|
range: (annotation.start_col, annotation.end_col),
|
|
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?
|
|
}
|
|
}
|