]> git.lizzy.rs Git - rust.git/blob - src/tools/rustfmt/src/format_report_formatter.rs
docs/test: add UI test and long-form error docs for `E0377`
[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     #[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`].
43 pub struct FormatReportFormatter<'a> {
44     report: &'a FormatReport,
45     enable_colors: bool,
46 }
47
48 impl<'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
122 fn 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
137 fn 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 }