]> git.lizzy.rs Git - rust.git/blob - src/tools/rustfmt/src/format_report_formatter.rs
Rollup merge of #86803 - xfix:remove-unnecessary-ampersand-from-command-args-calls...
[rust.git] / src / tools / rustfmt / src / format_report_formatter.rs
1 use crate::formatting::FormattingError;
2 use crate::{ErrorKind, FormatReport};
3 use annotate_snippets::display_list::{DisplayList, FormatOptions};
4 use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
5 use std::fmt::{self, Display};
6
7 /// A builder for [`FormatReportFormatter`].
8 pub struct FormatReportFormatterBuilder<'a> {
9     report: &'a FormatReport,
10     enable_colors: bool,
11 }
12
13 impl<'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     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`].
42 pub struct FormatReportFormatter<'a> {
43     report: &'a FormatReport,
44     enable_colors: bool,
45 }
46
47 impl<'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 opt = FormatOptions {
52             color: self.enable_colors,
53             ..Default::default()
54         };
55
56         for (file, errors) in errors_by_file {
57             for error in errors {
58                 let error_kind = error.kind.to_string();
59                 let title = Some(Annotation {
60                     id: if error.is_internal() {
61                         Some("internal")
62                     } else {
63                         None
64                     },
65                     label: Some(&error_kind),
66                     annotation_type: error_kind_to_snippet_annotation_type(&error.kind),
67                 });
68
69                 let message_suffix = error.msg_suffix();
70                 let footer = if !message_suffix.is_empty() {
71                     Some(Annotation {
72                         id: None,
73                         label: Some(message_suffix),
74                         annotation_type: AnnotationType::Note,
75                     })
76                 } else {
77                     None
78                 };
79
80                 let origin = format!("{}:{}", file, error.line);
81                 let slice = Slice {
82                     source: &error.line_buffer.clone(),
83                     line_start: error.line,
84                     origin: Some(origin.as_str()),
85                     fold: false,
86                     annotations: slice_annotation(error).into_iter().collect(),
87                 };
88
89                 let snippet = Snippet {
90                     title,
91                     footer: footer.into_iter().collect(),
92                     slices: vec![slice],
93                     opt,
94                 };
95                 writeln!(f, "{}\n", DisplayList::from(snippet))?;
96             }
97         }
98
99         if !errors_by_file.is_empty() {
100             let label = format!(
101                 "rustfmt has failed to format. See previous {} errors.",
102                 self.report.warning_count()
103             );
104             let snippet = Snippet {
105                 title: Some(Annotation {
106                     id: None,
107                     label: Some(&label),
108                     annotation_type: AnnotationType::Warning,
109                 }),
110                 footer: Vec::new(),
111                 slices: Vec::new(),
112                 opt,
113             };
114             writeln!(f, "{}", DisplayList::from(snippet))?;
115         }
116
117         Ok(())
118     }
119 }
120
121 fn slice_annotation(error: &FormattingError) -> Option<SourceAnnotation<'_>> {
122     let (range_start, range_length) = error.format_len();
123     let range_end = range_start + range_length;
124
125     if range_length > 0 {
126         Some(SourceAnnotation {
127             annotation_type: AnnotationType::Error,
128             range: (range_start, range_end),
129             label: "",
130         })
131     } else {
132         None
133     }
134 }
135
136 fn error_kind_to_snippet_annotation_type(error_kind: &ErrorKind) -> AnnotationType {
137     match error_kind {
138         ErrorKind::LineOverflow(..)
139         | ErrorKind::TrailingWhitespace
140         | ErrorKind::IoError(_)
141         | ErrorKind::ModuleResolutionError(_)
142         | ErrorKind::ParseError
143         | ErrorKind::LostComment
144         | ErrorKind::LicenseCheck
145         | ErrorKind::BadAttr
146         | ErrorKind::InvalidGlobPattern(_)
147         | ErrorKind::VersionMismatch => AnnotationType::Error,
148         ErrorKind::BadIssue(_) | ErrorKind::DeprecatedAttr => AnnotationType::Warning,
149     }
150 }