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 crate::emitter
::FileWithAnnotatedLines
;
9 use crate::snippet
::Line
;
10 use crate::{CodeSuggestion, Diagnostic, DiagnosticId, Emitter, Level, SubDiagnostic}
;
11 use annotate_snippets
::display_list
::{DisplayList, FormatOptions}
;
12 use annotate_snippets
::snippet
::*;
13 use rustc_data_structures
::sync
::Lrc
;
14 use rustc_span
::source_map
::SourceMap
;
15 use rustc_span
::{MultiSpan, SourceFile}
;
17 /// Generates diagnostics using annotate-snippet
18 pub struct AnnotateSnippetEmitterWriter
{
19 source_map
: Option
<Lrc
<SourceMap
>>,
20 /// If true, hides the longer explanation text
22 /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
25 macro_backtrace
: bool
,
28 impl Emitter
for AnnotateSnippetEmitterWriter
{
29 /// The entry point for the diagnostics generation
30 fn emit_diagnostic(&mut self, diag
: &Diagnostic
) {
31 let mut children
= diag
.children
.clone();
32 let (mut primary_span
, suggestions
) = self.primary_span_formatted(&diag
);
34 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
42 self.emit_messages_default(
52 fn source_map(&self) -> Option
<&Lrc
<SourceMap
>> {
53 self.source_map
.as_ref()
56 fn should_show_explain(&self) -> bool
{
61 /// Provides the source string for the given `line` of `file`
62 fn source_string(file
: Lrc
<SourceFile
>, line
: &Line
) -> String
{
63 file
.get_line(line
.line_index
- 1).map(|a
| a
.to_string()).unwrap_or_default()
66 /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
67 fn annotation_type_for_level(level
: Level
) -> AnnotationType
{
69 Level
::Bug
| Level
::DelayedBug
| Level
::Fatal
| Level
::Error { .. }
=> {
72 Level
::Warning
=> AnnotationType
::Warning
,
73 Level
::Note
| Level
::OnceNote
=> AnnotationType
::Note
,
74 Level
::Help
=> AnnotationType
::Help
,
75 // FIXME(#59346): Not sure how to map this level
76 Level
::FailureNote
=> AnnotationType
::Error
,
77 Level
::Allow
=> panic
!("Should not call with Allow"),
78 Level
::Expect(_
) => panic
!("Should not call with Expect"),
82 impl AnnotateSnippetEmitterWriter
{
84 source_map
: Option
<Lrc
<SourceMap
>>,
86 macro_backtrace
: bool
,
88 Self { source_map, short_message, ui_testing: false, macro_backtrace }
91 /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
93 /// If this is set to true, line numbers will be normalized as `LL` in the output.
94 pub fn ui_testing(mut self, ui_testing
: bool
) -> Self {
95 self.ui_testing
= ui_testing
;
99 fn emit_messages_default(
103 code
: &Option
<DiagnosticId
>,
105 _children
: &[SubDiagnostic
],
106 _suggestions
: &[CodeSuggestion
],
108 if let Some(source_map
) = &self.source_map
{
109 // Make sure our primary file comes first
110 let primary_lo
= if let Some(ref primary_span
) = msp
.primary_span().as_ref() {
111 if primary_span
.is_dummy() {
112 // FIXME(#59346): Not sure when this is the case and what
113 // should be done if it happens
116 source_map
.lookup_char_pos(primary_span
.lo())
119 // FIXME(#59346): Not sure when this is the case and what
120 // should be done if it happens
123 let mut annotated_files
=
124 FileWithAnnotatedLines
::collect_annotations(msp
, &self.source_map
);
126 annotated_files
.binary_search_by(|x
| x
.file
.name
.cmp(&primary_lo
.file
.name
))
128 annotated_files
.swap(0, pos
);
130 // owned: line source, line index, annotations
131 type Owned
= (String
, usize, Vec
<crate::snippet
::Annotation
>);
132 let filename
= source_map
.filename_for_diagnostics(&primary_lo
.file
.name
);
133 let origin
= filename
.to_string_lossy();
134 let annotated_files
: Vec
<Owned
> = annotated_files
136 .flat_map(|annotated_file
| {
137 let file
= annotated_file
.file
;
142 (source_string(file
.clone(), &line
), line
.line_index
, line
.annotations
)
144 .collect
::<Vec
<Owned
>>()
147 let snippet
= Snippet
{
148 title
: Some(Annotation
{
149 label
: Some(&message
),
150 id
: code
.as_ref().map(|c
| match c
{
151 DiagnosticId
::Error(val
) | DiagnosticId
::Lint { name: val, .. }
=> {
155 annotation_type
: annotation_type_for_level(*level
),
158 opt
: FormatOptions { color: true, anonymized_line_numbers: self.ui_testing }
,
159 slices
: annotated_files
161 .map(|(source
, line_index
, annotations
)| {
164 line_start
: *line_index
,
165 origin
: Some(&origin
),
166 // FIXME(#59346): Not really sure when `fold` should be true or false
168 annotations
: annotations
170 .map(|annotation
| SourceAnnotation
{
171 range
: (annotation
.start_col
, annotation
.end_col
),
172 label
: annotation
.label
.as_deref().unwrap_or_default(),
173 annotation_type
: annotation_type_for_level(*level
),
180 // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
181 // `emitter.rs` has the `Destination` enum that lists various possible output
183 eprintln
!("{}", DisplayList
::from(snippet
))
185 // FIXME(#59346): Is it ok to return None if there's no source_map?