rustfmt_nightly/
format_report_formatter.rs

1use crate::formatting::FormattingError;
2use crate::{ErrorKind, FormatReport};
3use annotate_snippets::display_list::{DisplayList, FormatOptions};
4use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
5use std::fmt::{self, Display};
6
7/// A builder for [`FormatReportFormatter`].
8pub struct FormatReportFormatterBuilder<'a> {
9    report: &'a FormatReport,
10    enable_colors: bool,
11}
12
13impl<'a> FormatReportFormatterBuilder<'a> {
14    /// Creates a new [`FormatReportFormatterBuilder`].
15    pub fn new(report: &'a FormatReport) -> Self {
16        Self {
17            report,
18            enable_colors: false,
19        }
20    }
21
22    /// Enables colors and formatting in the output.
23    #[must_use]
24    pub fn enable_colors(self, enable_colors: bool) -> Self {
25        Self {
26            enable_colors,
27            ..self
28        }
29    }
30
31    /// Creates a new [`FormatReportFormatter`] from the settings in this builder.
32    pub fn build(self) -> FormatReportFormatter<'a> {
33        FormatReportFormatter {
34            report: self.report,
35            enable_colors: self.enable_colors,
36        }
37    }
38}
39
40/// Formats the warnings/errors in a [`FormatReport`].
41///
42/// Can be created using a [`FormatReportFormatterBuilder`].
43pub struct FormatReportFormatter<'a> {
44    report: &'a FormatReport,
45    enable_colors: bool,
46}
47
48impl<'a> Display for FormatReportFormatter<'a> {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        let errors_by_file = &self.report.internal.borrow().0;
51
52        let opt = FormatOptions {
53            color: self.enable_colors,
54            ..Default::default()
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 title = Some(Annotation {
61                    id: if error.is_internal() {
62                        Some("internal")
63                    } else {
64                        None
65                    },
66                    label: Some(&error_kind),
67                    annotation_type: error_kind_to_snippet_annotation_type(&error.kind),
68                });
69
70                let message_suffix = error.msg_suffix();
71                let footer = if !message_suffix.is_empty() {
72                    Some(Annotation {
73                        id: None,
74                        label: Some(message_suffix),
75                        annotation_type: AnnotationType::Note,
76                    })
77                } else {
78                    None
79                };
80
81                let origin = format!("{}:{}", file, error.line);
82                let slice = Slice {
83                    source: &error.line_buffer.clone(),
84                    line_start: error.line,
85                    origin: Some(origin.as_str()),
86                    fold: false,
87                    annotations: slice_annotation(error).into_iter().collect(),
88                };
89
90                let snippet = Snippet {
91                    title,
92                    footer: footer.into_iter().collect(),
93                    slices: vec![slice],
94                    opt,
95                };
96                writeln!(f, "{}\n", DisplayList::from(snippet))?;
97            }
98        }
99
100        if !errors_by_file.is_empty() {
101            let label = format!(
102                "rustfmt has failed to format. See previous {} errors.",
103                self.report.warning_count()
104            );
105            let snippet = Snippet {
106                title: Some(Annotation {
107                    id: None,
108                    label: Some(&label),
109                    annotation_type: AnnotationType::Warning,
110                }),
111                footer: Vec::new(),
112                slices: Vec::new(),
113                opt,
114            };
115            writeln!(f, "{}", DisplayList::from(snippet))?;
116        }
117
118        Ok(())
119    }
120}
121
122fn slice_annotation(error: &FormattingError) -> Option<SourceAnnotation<'_>> {
123    let (range_start, range_length) = error.format_len();
124    let range_end = range_start + range_length;
125
126    if range_length > 0 {
127        Some(SourceAnnotation {
128            annotation_type: AnnotationType::Error,
129            range: (range_start, range_end),
130            label: "",
131        })
132    } else {
133        None
134    }
135}
136
137fn error_kind_to_snippet_annotation_type(error_kind: &ErrorKind) -> AnnotationType {
138    match error_kind {
139        ErrorKind::LineOverflow(..)
140        | ErrorKind::TrailingWhitespace
141        | ErrorKind::IoError(_)
142        | ErrorKind::ModuleResolutionError(_)
143        | ErrorKind::ParseError
144        | ErrorKind::LostComment
145        | ErrorKind::BadAttr
146        | ErrorKind::InvalidGlobPattern(_)
147        | ErrorKind::VersionMismatch => AnnotationType::Error,
148        ErrorKind::DeprecatedAttr => AnnotationType::Warning,
149    }
150}