Skip to content

Commit 3dc625c

Browse files
Ruben Schmidmeistertopecongiro
Ruben Schmidmeister
authored andcommitted
Use annotate-snippets for emitting errors (rust-lang#3507)
1 parent efa3a62 commit 3dc625c

File tree

8 files changed

+238
-136
lines changed

8 files changed

+238
-136
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ unicode-width = "0.1.5"
5858
unicode_categories = "0.1.1"
5959
dirs = "1.0.4"
6060
ignore = "0.4.6"
61+
annotate-snippets = { version = "0.5.0", features = ["ansi_term"] }
6162

6263
# A noop dependency that changes in the Rust repository, it's a bit of a hack.
6364
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`

src/bin/main.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use getopts::{Matches, Options};
1616

1717
use crate::rustfmt::{
1818
load_config, CliOptions, Color, Config, Edition, EmitMode, ErrorKind, FileLines, FileName,
19-
Input, Session, Verbosity,
19+
FormatReportFormatterBuilder, Input, Session, Verbosity,
2020
};
2121

2222
fn main() {
@@ -310,19 +310,12 @@ fn format_and_emit_report<T: Write>(session: &mut Session<'_, T>, input: Input)
310310
match session.format(input) {
311311
Ok(report) => {
312312
if report.has_warnings() {
313-
match term::stderr() {
314-
Some(ref t)
315-
if session.config.color().use_colored_tty()
316-
&& t.supports_color()
317-
&& t.supports_attr(term::Attr::Bold) =>
318-
{
319-
match report.fancy_print(term::stderr().unwrap()) {
320-
Ok(..) => (),
321-
Err(..) => panic!("Unable to write to stderr: {}", report),
322-
}
323-
}
324-
_ => eprintln!("{}", report),
325-
}
313+
eprintln!(
314+
"{}",
315+
FormatReportFormatterBuilder::new(&report)
316+
.enable_colors(should_print_with_colors(session))
317+
.build()
318+
);
326319
}
327320
}
328321
Err(msg) => {
@@ -332,6 +325,19 @@ fn format_and_emit_report<T: Write>(session: &mut Session<'_, T>, input: Input)
332325
}
333326
}
334327

328+
fn should_print_with_colors<T: Write>(session: &mut Session<'_, T>) -> bool {
329+
match term::stderr() {
330+
Some(ref t)
331+
if session.config.color().use_colored_tty()
332+
&& t.supports_color()
333+
&& t.supports_attr(term::Attr::Bold) =>
334+
{
335+
true
336+
}
337+
_ => false,
338+
}
339+
}
340+
335341
fn print_usage_to_stdout(opts: &Options, reason: &str) {
336342
let sep = if reason.is_empty() {
337343
String::new()

src/format_report_formatter.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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);
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::ParseError
166+
| ErrorKind::LostComment
167+
| ErrorKind::LicenseCheck
168+
| ErrorKind::BadAttr
169+
| ErrorKind::InvalidGlobPattern(_)
170+
| ErrorKind::VersionMismatch => AnnotationType::Error,
171+
ErrorKind::BadIssue(_) | ErrorKind::DeprecatedAttr => AnnotationType::Warning,
172+
}
173+
}

src/formatting.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -275,18 +275,14 @@ impl FormattingError {
275275
}
276276
}
277277

278-
pub(crate) fn msg_prefix(&self) -> &str {
278+
pub(crate) fn is_internal(&self) -> bool {
279279
match self.kind {
280280
ErrorKind::LineOverflow(..)
281281
| ErrorKind::TrailingWhitespace
282282
| ErrorKind::IoError(_)
283283
| ErrorKind::ParseError
284-
| ErrorKind::LostComment => "internal error:",
285-
ErrorKind::LicenseCheck
286-
| ErrorKind::BadAttr
287-
| ErrorKind::InvalidGlobPattern(..)
288-
| ErrorKind::VersionMismatch => "error:",
289-
ErrorKind::BadIssue(_) | ErrorKind::DeprecatedAttr => "warning:",
284+
| ErrorKind::LostComment => true,
285+
_ => false,
290286
}
291287
}
292288

src/git-rustfmt/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use env_logger;
1111
use getopts::{Matches, Options};
1212
use rustfmt_nightly as rustfmt;
1313

14-
use crate::rustfmt::{load_config, CliOptions, Input, Session};
14+
use crate::rustfmt::{load_config, CliOptions, FormatReportFormatterBuilder, Input, Session};
1515

1616
fn prune_files(files: Vec<&str>) -> Vec<&str> {
1717
let prefixes: Vec<_> = files
@@ -67,7 +67,7 @@ fn fmt_files(files: &[&str]) -> i32 {
6767
for file in files {
6868
let report = session.format(Input::File(PathBuf::from(file))).unwrap();
6969
if report.has_warnings() {
70-
eprintln!("{}", report);
70+
eprintln!("{}", FormatReportFormatterBuilder::new(&report).build());
7171
}
7272
if !session.has_no_errors() {
7373
exit_code = 1;

0 commit comments

Comments
 (0)