]> git.lizzy.rs Git - rust.git/blob - src/format_report_formatter.rs
refactor: apply rustc mod parsing changes
[rust.git] / src / format_report_formatter.rs
1 use crate::config::FileName;
2 use crate::formatting::FormattingError;
3 use crate::{ErrorKind, FormatReport};
4 use annotate_snippets::display_list::DisplayList;
5 use annotate_snippets::formatter::DisplayListFormatter;
6 use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
7 use std::fmt::{self, Display};
8
9 /// A builder for [`FormatReportFormatter`].
10 pub struct FormatReportFormatterBuilder<'a> {
11     report: &'a FormatReport,
12     enable_colors: bool,
13 }
14
15 impl<'a> FormatReportFormatterBuilder<'a> {
16     /// Creates a new [`FormatReportFormatterBuilder`].
17     pub fn new(report: &'a FormatReport) -> Self {
18         Self {
19             report,
20             enable_colors: false,
21         }
22     }
23
24     /// Enables colors and formatting in the output.
25     pub fn enable_colors(self, enable_colors: bool) -> Self {
26         Self {
27             enable_colors,
28             ..self
29         }
30     }
31
32     /// Creates a new [`FormatReportFormatter`] from the settings in this builder.
33     pub fn build(self) -> FormatReportFormatter<'a> {
34         FormatReportFormatter {
35             report: self.report,
36             enable_colors: self.enable_colors,
37         }
38     }
39 }
40
41 /// Formats the warnings/errors in a [`FormatReport`].
42 ///
43 /// Can be created using a [`FormatReportFormatterBuilder`].
44 pub struct FormatReportFormatter<'a> {
45     report: &'a FormatReport,
46     enable_colors: bool,
47 }
48
49 impl<'a> Display for FormatReportFormatter<'a> {
50     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51         let formatter = DisplayListFormatter::new(self.enable_colors, false);
52         let errors_by_file = &self.report.internal.borrow().0;
53
54         for (file, errors) in errors_by_file {
55             for error in errors {
56                 let snippet = formatting_error_to_snippet(file, error);
57                 writeln!(f, "{}\n", formatter.format(&DisplayList::from(snippet)))?;
58             }
59         }
60
61         if !errors_by_file.is_empty() {
62             let snippet = formatting_failure_snippet(self.report.warning_count());
63             writeln!(f, "{}", formatter.format(&DisplayList::from(snippet)))?;
64         }
65
66         Ok(())
67     }
68 }
69
70 fn formatting_failure_snippet(warning_count: usize) -> Snippet {
71     Snippet {
72         title: Some(Annotation {
73             id: None,
74             label: Some(format!(
75                 "rustfmt has failed to format. See previous {} errors.",
76                 warning_count
77             )),
78             annotation_type: AnnotationType::Warning,
79         }),
80         footer: Vec::new(),
81         slices: Vec::new(),
82     }
83 }
84
85 fn formatting_error_to_snippet(file: &FileName, error: &FormattingError) -> Snippet {
86     let slices = vec![snippet_code_slice(file, error)];
87     let title = Some(snippet_title(error));
88     let footer = snippet_footer(error).into_iter().collect();
89
90     Snippet {
91         title,
92         footer,
93         slices,
94     }
95 }
96
97 fn snippet_title(error: &FormattingError) -> Annotation {
98     let annotation_type = error_kind_to_snippet_annotation_type(&error.kind);
99
100     Annotation {
101         id: title_annotation_id(error),
102         label: Some(error.kind.to_string()),
103         annotation_type,
104     }
105 }
106
107 fn snippet_footer(error: &FormattingError) -> Option<Annotation> {
108     let message_suffix = error.msg_suffix();
109
110     if !message_suffix.is_empty() {
111         Some(Annotation {
112             id: None,
113             label: Some(message_suffix.to_string()),
114             annotation_type: AnnotationType::Note,
115         })
116     } else {
117         None
118     }
119 }
120
121 fn snippet_code_slice(file: &FileName, error: &FormattingError) -> Slice {
122     let annotations = slice_annotation(error).into_iter().collect();
123     let origin = Some(format!("{}:{}", file, error.line));
124     let source = error.line_buffer.clone();
125
126     Slice {
127         source,
128         line_start: error.line,
129         origin,
130         fold: false,
131         annotations,
132     }
133 }
134
135 fn slice_annotation(error: &FormattingError) -> Option<SourceAnnotation> {
136     let (range_start, range_length) = error.format_len();
137     let range_end = range_start + range_length;
138
139     if range_length > 0 {
140         Some(SourceAnnotation {
141             annotation_type: AnnotationType::Error,
142             range: (range_start, range_end),
143             label: String::new(),
144         })
145     } else {
146         None
147     }
148 }
149
150 fn title_annotation_id(error: &FormattingError) -> Option<String> {
151     const INTERNAL_ERROR_ID: &str = "internal";
152
153     if error.is_internal() {
154         Some(INTERNAL_ERROR_ID.to_string())
155     } else {
156         None
157     }
158 }
159
160 fn error_kind_to_snippet_annotation_type(error_kind: &ErrorKind) -> AnnotationType {
161     match error_kind {
162         ErrorKind::LineOverflow(..)
163         | ErrorKind::TrailingWhitespace
164         | ErrorKind::IoError(_)
165         | ErrorKind::ModuleResolutionError(_)
166         | ErrorKind::ParseError
167         | ErrorKind::LostComment
168         | ErrorKind::LicenseCheck
169         | ErrorKind::BadAttr
170         | ErrorKind::InvalidGlobPattern(_)
171         | ErrorKind::VersionMismatch => AnnotationType::Error,
172         ErrorKind::BadIssue(_) | ErrorKind::DeprecatedAttr => AnnotationType::Warning,
173     }
174 }