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
;
11 CodeSuggestion
, Diagnostic
, DiagnosticId
, DiagnosticMessage
, Emitter
, FluentBundle
,
12 LazyFallbackBundle
, Level
, MultiSpan
, Style
, SubDiagnostic
,
14 use annotate_snippets
::display_list
::{DisplayList, FormatOptions}
;
15 use annotate_snippets
::snippet
::*;
16 use rustc_data_structures
::sync
::Lrc
;
17 use rustc_error_messages
::FluentArgs
;
18 use rustc_span
::source_map
::SourceMap
;
19 use rustc_span
::SourceFile
;
21 /// Generates diagnostics using annotate-snippet
22 pub struct AnnotateSnippetEmitterWriter
{
23 source_map
: Option
<Lrc
<SourceMap
>>,
24 fluent_bundle
: Option
<Lrc
<FluentBundle
>>,
25 fallback_bundle
: LazyFallbackBundle
,
27 /// If true, hides the longer explanation text
29 /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
32 macro_backtrace
: bool
,
35 impl Emitter
for AnnotateSnippetEmitterWriter
{
36 /// The entry point for the diagnostics generation
37 fn emit_diagnostic(&mut self, diag
: &Diagnostic
) {
38 let fluent_args
= self.to_fluent_args(diag
.args());
40 let mut children
= diag
.children
.clone();
41 let (mut primary_span
, suggestions
) = self.primary_span_formatted(&diag
, &fluent_args
);
43 self.fix_multispans_in_extern_macros_and_render_macro_backtrace(
51 self.emit_messages_default(
62 fn source_map(&self) -> Option
<&Lrc
<SourceMap
>> {
63 self.source_map
.as_ref()
66 fn fluent_bundle(&self) -> Option
<&Lrc
<FluentBundle
>> {
67 self.fluent_bundle
.as_ref()
70 fn fallback_fluent_bundle(&self) -> &FluentBundle
{
71 &**self.fallback_bundle
74 fn should_show_explain(&self) -> bool
{
79 /// Provides the source string for the given `line` of `file`
80 fn source_string(file
: Lrc
<SourceFile
>, line
: &Line
) -> String
{
81 file
.get_line(line
.line_index
- 1).map(|a
| a
.to_string()).unwrap_or_default()
84 /// Maps `Diagnostic::Level` to `snippet::AnnotationType`
85 fn annotation_type_for_level(level
: Level
) -> AnnotationType
{
87 Level
::Bug
| Level
::DelayedBug
| Level
::Fatal
| Level
::Error { .. }
=> {
90 Level
::Warning(_
) => AnnotationType
::Warning
,
91 Level
::Note
| Level
::OnceNote
=> AnnotationType
::Note
,
92 Level
::Help
=> AnnotationType
::Help
,
93 // FIXME(#59346): Not sure how to map this level
94 Level
::FailureNote
=> AnnotationType
::Error
,
95 Level
::Allow
=> panic
!("Should not call with Allow"),
96 Level
::Expect(_
) => panic
!("Should not call with Expect"),
100 impl AnnotateSnippetEmitterWriter
{
102 source_map
: Option
<Lrc
<SourceMap
>>,
103 fluent_bundle
: Option
<Lrc
<FluentBundle
>>,
104 fallback_bundle
: LazyFallbackBundle
,
106 macro_backtrace
: bool
,
118 /// Allows to modify `Self` to enable or disable the `ui_testing` flag.
120 /// If this is set to true, line numbers will be normalized as `LL` in the output.
121 pub fn ui_testing(mut self, ui_testing
: bool
) -> Self {
122 self.ui_testing
= ui_testing
;
126 fn emit_messages_default(
129 messages
: &[(DiagnosticMessage
, Style
)],
130 args
: &FluentArgs
<'_
>,
131 code
: &Option
<DiagnosticId
>,
133 _children
: &[SubDiagnostic
],
134 _suggestions
: &[CodeSuggestion
],
136 let message
= self.translate_messages(messages
, args
);
137 if let Some(source_map
) = &self.source_map
{
138 // Make sure our primary file comes first
139 let primary_lo
= if let Some(ref primary_span
) = msp
.primary_span().as_ref() {
140 if primary_span
.is_dummy() {
141 // FIXME(#59346): Not sure when this is the case and what
142 // should be done if it happens
145 source_map
.lookup_char_pos(primary_span
.lo())
148 // FIXME(#59346): Not sure when this is the case and what
149 // should be done if it happens
152 let mut annotated_files
= FileWithAnnotatedLines
::collect_annotations(self, args
, msp
);
154 annotated_files
.binary_search_by(|x
| x
.file
.name
.cmp(&primary_lo
.file
.name
))
156 annotated_files
.swap(0, pos
);
158 // owned: line source, line index, annotations
159 type Owned
= (String
, usize, Vec
<crate::snippet
::Annotation
>);
160 let filename
= source_map
.filename_for_diagnostics(&primary_lo
.file
.name
);
161 let origin
= filename
.to_string_lossy();
162 let annotated_files
: Vec
<Owned
> = annotated_files
164 .flat_map(|annotated_file
| {
165 let file
= annotated_file
.file
;
170 (source_string(file
.clone(), &line
), line
.line_index
, line
.annotations
)
172 .collect
::<Vec
<Owned
>>()
175 let snippet
= Snippet
{
176 title
: Some(Annotation
{
177 label
: Some(&message
),
178 id
: code
.as_ref().map(|c
| match c
{
179 DiagnosticId
::Error(val
) | DiagnosticId
::Lint { name: val, .. }
=> {
183 annotation_type
: annotation_type_for_level(*level
),
186 opt
: FormatOptions { color: true, anonymized_line_numbers: self.ui_testing }
,
187 slices
: annotated_files
189 .map(|(source
, line_index
, annotations
)| {
192 line_start
: *line_index
,
193 origin
: Some(&origin
),
194 // FIXME(#59346): Not really sure when `fold` should be true or false
196 annotations
: annotations
198 .map(|annotation
| SourceAnnotation
{
199 range
: (annotation
.start_col
, annotation
.end_col
),
200 label
: annotation
.label
.as_deref().unwrap_or_default(),
201 annotation_type
: annotation_type_for_level(*level
),
208 // FIXME(#59346): Figure out if we can _always_ print to stderr or not.
209 // `emitter.rs` has the `Destination` enum that lists various possible output
211 eprintln
!("{}", DisplayList
::from(snippet
))
213 // FIXME(#59346): Is it ok to return None if there's no source_map?