]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
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 | } |