Skip to main content

rustfmt_nightly/
format_report_formatter.rs

1use crate::formatting::FormattingError;
2use crate::{ErrorKind, FormatReport};
3use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Padding, Renderer, Snippet};
4use std::fmt::{self, Display};
5
6/// A builder for [`FormatReportFormatter`].
7pub struct FormatReportFormatterBuilder<'a> {
8    report: &'a FormatReport,
9    enable_colors: bool,
10}
11
12impl<'a> FormatReportFormatterBuilder<'a> {
13    /// Creates a new [`FormatReportFormatterBuilder`].
14    pub fn new(report: &'a FormatReport) -> Self {
15        Self {
16            report,
17            enable_colors: false,
18        }
19    }
20
21    /// Enables colors and formatting in the output.
22    #[must_use]
23    pub fn enable_colors(self, enable_colors: bool) -> Self {
24        Self {
25            enable_colors,
26            ..self
27        }
28    }
29
30    /// Creates a new [`FormatReportFormatter`] from the settings in this builder.
31    pub fn build(self) -> FormatReportFormatter<'a> {
32        FormatReportFormatter {
33            report: self.report,
34            enable_colors: self.enable_colors,
35        }
36    }
37}
38
39/// Formats the warnings/errors in a [`FormatReport`].
40///
41/// Can be created using a [`FormatReportFormatterBuilder`].
42pub struct FormatReportFormatter<'a> {
43    report: &'a FormatReport,
44    enable_colors: bool,
45}
46
47impl<'a> Display for FormatReportFormatter<'a> {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        let errors_by_file = &self.report.internal.borrow().0;
50
51        let renderer = if self.enable_colors {
52            Renderer::styled()
53        } else {
54            Renderer::plain()
55        };
56
57        for (file, errors) in errors_by_file {
58            for error in errors {
59                let error_kind = error.kind.to_string();
60                let mut title =
61                    error_kind_to_snippet_annotation_level(&error.kind).primary_title(error_kind);
62                if error.is_internal() {
63                    title = title.id("internal");
64                }
65                let snippet = Snippet::source(&error.line_buffer)
66                    .line_start(error.line)
67                    .path(format!("{file}:{}", error.line))
68                    .fold(false)
69                    .annotations(annotation(error));
70                let mut group = title.element(snippet);
71                if let Some(message_suffix) = error.msg_suffix() {
72                    group = group.element(Level::NOTE.message(message_suffix));
73                } else {
74                    group = group.element(Padding);
75                }
76                writeln!(f, "{}\n", renderer.render(&[group]))?;
77            }
78        }
79
80        if !errors_by_file.is_empty() {
81            let label = format!(
82                "rustfmt has failed to format. See previous {} errors.",
83                self.report.warning_count()
84            );
85            let group = Group::with_title(Level::WARNING.primary_title(label));
86            writeln!(f, "{}\n", renderer.render(&[group]))?;
87        }
88        Ok(())
89    }
90}
91
92fn annotation(error: &FormattingError) -> Option<Annotation<'_>> {
93    error
94        .highlight
95        .clone()
96        .map(|range| AnnotationKind::Primary.span(range))
97}
98
99fn error_kind_to_snippet_annotation_level(error_kind: &ErrorKind) -> Level<'_> {
100    match error_kind {
101        ErrorKind::LineOverflow(..)
102        | ErrorKind::TrailingWhitespace
103        | ErrorKind::IoError(_)
104        | ErrorKind::ModuleResolutionError(_)
105        | ErrorKind::ParseError
106        | ErrorKind::LostComment
107        | ErrorKind::BadAttr
108        | ErrorKind::InvalidGlobPattern(_)
109        | ErrorKind::VersionMismatch => Level::ERROR,
110        ErrorKind::DeprecatedAttr => Level::WARNING,
111    }
112}