blob: 5b47364e714e910dbf6792c98af13e119944a74b [file] [log] [blame]
//! 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, Emitter, Level, SubDiagnostic};
use annotate_snippets::display_list::{DisplayList, FormatOptions};
use annotate_snippets::snippet::*;
use rustc_data_structures::sync::Lrc;
use rustc_span::source_map::SourceMap;
use rustc_span::{MultiSpan, SourceFile};
/// Generates diagnostics using annotate-snippet
pub struct AnnotateSnippetEmitterWriter {
source_map: Option<Lrc<SourceMap>>,
/// 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 mut children = diag.children.clone();
let (mut primary_span, suggestions) = self.primary_span_formatted(&diag);
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(),
&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::Fatal | Level::Error => AnnotationType::Error,
Level::Warning => AnnotationType::Warning,
Level::Note => AnnotationType::Note,
Level::Help => AnnotationType::Help,
// FIXME(#59346): Not sure how to map these two levels
Level::Cancelled | Level::FailureNote => AnnotationType::Error,
}
}
impl AnnotateSnippetEmitterWriter {
pub fn new(
source_map: Option<Lrc<SourceMap>>,
short_message: bool,
macro_backtrace: bool,
) -> Self {
Self { source_map, 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,
message: String,
code: &Option<DiagnosticId>,
msp: &MultiSpan,
_children: &[SubDiagnostic],
_suggestions: &[CodeSuggestion],
) {
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(msp, &self.source_map);
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 origin = primary_lo.file.name.to_string();
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(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
.into_iter()
.map(|annotation| SourceAnnotation {
range: (annotation.start_col, annotation.end_col),
label: annotation
.label
.as_ref()
.map(|s| s.as_str())
.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?
}
}