| use crate::formatting::FormattingError; |
| use crate::{ErrorKind, FormatReport}; |
| use annotate_snippets::display_list::{DisplayList, FormatOptions}; |
| 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. |
| #[must_use] |
| 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 errors_by_file = &self.report.internal.borrow().0; |
| |
| let opt = FormatOptions { |
| color: self.enable_colors, |
| ..Default::default() |
| }; |
| |
| for (file, errors) in errors_by_file { |
| for error in errors { |
| let error_kind = error.kind.to_string(); |
| let title = Some(Annotation { |
| id: if error.is_internal() { |
| Some("internal") |
| } else { |
| None |
| }, |
| label: Some(&error_kind), |
| annotation_type: error_kind_to_snippet_annotation_type(&error.kind), |
| }); |
| |
| let message_suffix = error.msg_suffix(); |
| let footer = if !message_suffix.is_empty() { |
| Some(Annotation { |
| id: None, |
| label: Some(message_suffix), |
| annotation_type: AnnotationType::Note, |
| }) |
| } else { |
| None |
| }; |
| |
| let origin = format!("{}:{}", file, error.line); |
| let slice = Slice { |
| source: &error.line_buffer.clone(), |
| line_start: error.line, |
| origin: Some(origin.as_str()), |
| fold: false, |
| annotations: slice_annotation(error).into_iter().collect(), |
| }; |
| |
| let snippet = Snippet { |
| title, |
| footer: footer.into_iter().collect(), |
| slices: vec![slice], |
| opt, |
| }; |
| writeln!(f, "{}\n", DisplayList::from(snippet))?; |
| } |
| } |
| |
| if !errors_by_file.is_empty() { |
| let label = format!( |
| "rustfmt has failed to format. See previous {} errors.", |
| self.report.warning_count() |
| ); |
| let snippet = Snippet { |
| title: Some(Annotation { |
| id: None, |
| label: Some(&label), |
| annotation_type: AnnotationType::Warning, |
| }), |
| footer: Vec::new(), |
| slices: Vec::new(), |
| opt, |
| }; |
| writeln!(f, "{}", DisplayList::from(snippet))?; |
| } |
| |
| Ok(()) |
| } |
| } |
| |
| 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: "", |
| }) |
| } else { |
| None |
| } |
| } |
| |
| fn error_kind_to_snippet_annotation_type(error_kind: &ErrorKind) -> AnnotationType { |
| match error_kind { |
| ErrorKind::LineOverflow(..) |
| | ErrorKind::TrailingWhitespace |
| | ErrorKind::IoError(_) |
| | ErrorKind::ModuleResolutionError(_) |
| | ErrorKind::ParseError |
| | ErrorKind::LostComment |
| | ErrorKind::BadAttr |
| | ErrorKind::InvalidGlobPattern(_) |
| | ErrorKind::VersionMismatch => AnnotationType::Error, |
| ErrorKind::DeprecatedAttr => AnnotationType::Warning, |
| } |
| } |