-
Notifications
You must be signed in to change notification settings - Fork 889
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use annotate-snippets for emitting errors (#3507)
- Loading branch information
1 parent
efa3a62
commit 3dc625c
Showing
8 changed files
with
238 additions
and
136 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
use crate::config::FileName; | ||
use crate::formatting::FormattingError; | ||
use crate::{ErrorKind, FormatReport}; | ||
use annotate_snippets::display_list::DisplayList; | ||
use annotate_snippets::formatter::DisplayListFormatter; | ||
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; | ||
use std::fmt::{self, Display}; | ||
|
||
/// A builder for [`FormatReportFormatter`]. | ||
pub struct FormatReportFormatterBuilder<'a> { | ||
report: &'a FormatReport, | ||
enable_colors: bool, | ||
} | ||
|
||
impl<'a> FormatReportFormatterBuilder<'a> { | ||
/// Creates a new [`FormatReportFormatterBuilder`]. | ||
pub fn new(report: &'a FormatReport) -> Self { | ||
Self { | ||
report, | ||
enable_colors: false, | ||
} | ||
} | ||
|
||
/// Enables colors and formatting in the output. | ||
pub fn enable_colors(self, enable_colors: bool) -> Self { | ||
Self { | ||
enable_colors, | ||
..self | ||
} | ||
} | ||
|
||
/// Creates a new [`FormatReportFormatter`] from the settings in this builder. | ||
pub fn build(self) -> FormatReportFormatter<'a> { | ||
FormatReportFormatter { | ||
report: self.report, | ||
enable_colors: self.enable_colors, | ||
} | ||
} | ||
} | ||
|
||
/// Formats the warnings/errors in a [`FormatReport`]. | ||
/// | ||
/// Can be created using a [`FormatReportFormatterBuilder`]. | ||
pub struct FormatReportFormatter<'a> { | ||
report: &'a FormatReport, | ||
enable_colors: bool, | ||
} | ||
|
||
impl<'a> Display for FormatReportFormatter<'a> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
let formatter = DisplayListFormatter::new(self.enable_colors); | ||
let errors_by_file = &self.report.internal.borrow().0; | ||
|
||
for (file, errors) in errors_by_file { | ||
for error in errors { | ||
let snippet = formatting_error_to_snippet(file, error); | ||
writeln!(f, "{}\n", formatter.format(&DisplayList::from(snippet)))?; | ||
} | ||
} | ||
|
||
if !errors_by_file.is_empty() { | ||
let snippet = formatting_failure_snippet(self.report.warning_count()); | ||
writeln!(f, "{}", formatter.format(&DisplayList::from(snippet)))?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
fn formatting_failure_snippet(warning_count: usize) -> Snippet { | ||
Snippet { | ||
title: Some(Annotation { | ||
id: None, | ||
label: Some(format!( | ||
"rustfmt has failed to format. See previous {} errors.", | ||
warning_count | ||
)), | ||
annotation_type: AnnotationType::Warning, | ||
}), | ||
footer: Vec::new(), | ||
slices: Vec::new(), | ||
} | ||
} | ||
|
||
fn formatting_error_to_snippet(file: &FileName, error: &FormattingError) -> Snippet { | ||
let slices = vec![snippet_code_slice(file, error)]; | ||
let title = Some(snippet_title(error)); | ||
let footer = snippet_footer(error).into_iter().collect(); | ||
|
||
Snippet { | ||
title, | ||
footer, | ||
slices, | ||
} | ||
} | ||
|
||
fn snippet_title(error: &FormattingError) -> Annotation { | ||
let annotation_type = error_kind_to_snippet_annotation_type(&error.kind); | ||
|
||
Annotation { | ||
id: title_annotation_id(error), | ||
label: Some(error.kind.to_string()), | ||
annotation_type, | ||
} | ||
} | ||
|
||
fn snippet_footer(error: &FormattingError) -> Option<Annotation> { | ||
let message_suffix = error.msg_suffix(); | ||
|
||
if !message_suffix.is_empty() { | ||
Some(Annotation { | ||
id: None, | ||
label: Some(message_suffix.to_string()), | ||
annotation_type: AnnotationType::Note, | ||
}) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
fn snippet_code_slice(file: &FileName, error: &FormattingError) -> Slice { | ||
let annotations = slice_annotation(error).into_iter().collect(); | ||
let origin = Some(format!("{}:{}", file, error.line)); | ||
let source = error.line_buffer.clone(); | ||
|
||
Slice { | ||
source, | ||
line_start: error.line, | ||
origin, | ||
fold: false, | ||
annotations, | ||
} | ||
} | ||
|
||
fn slice_annotation(error: &FormattingError) -> Option<SourceAnnotation> { | ||
let (range_start, range_length) = error.format_len(); | ||
let range_end = range_start + range_length; | ||
|
||
if range_length > 0 { | ||
Some(SourceAnnotation { | ||
annotation_type: AnnotationType::Error, | ||
range: (range_start, range_end), | ||
label: String::new(), | ||
}) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
fn title_annotation_id(error: &FormattingError) -> Option<String> { | ||
const INTERNAL_ERROR_ID: &str = "internal"; | ||
|
||
if error.is_internal() { | ||
Some(INTERNAL_ERROR_ID.to_string()) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
fn error_kind_to_snippet_annotation_type(error_kind: &ErrorKind) -> AnnotationType { | ||
match error_kind { | ||
ErrorKind::LineOverflow(..) | ||
| ErrorKind::TrailingWhitespace | ||
| ErrorKind::IoError(_) | ||
| ErrorKind::ParseError | ||
| ErrorKind::LostComment | ||
| ErrorKind::LicenseCheck | ||
| ErrorKind::BadAttr | ||
| ErrorKind::InvalidGlobPattern(_) | ||
| ErrorKind::VersionMismatch => AnnotationType::Error, | ||
ErrorKind::BadIssue(_) | ErrorKind::DeprecatedAttr => AnnotationType::Warning, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.