Skip to content

Commit

Permalink
Use annotate-snippets for emitting errors (#3507)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ruben Schmidmeister authored and topecongiro committed Apr 17, 2019
1 parent efa3a62 commit 3dc625c
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 136 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ unicode-width = "0.1.5"
unicode_categories = "0.1.1"
dirs = "1.0.4"
ignore = "0.4.6"
annotate-snippets = { version = "0.5.0", features = ["ansi_term"] }

# A noop dependency that changes in the Rust repository, it's a bit of a hack.
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
Expand Down
34 changes: 20 additions & 14 deletions src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use getopts::{Matches, Options};

use crate::rustfmt::{
load_config, CliOptions, Color, Config, Edition, EmitMode, ErrorKind, FileLines, FileName,
Input, Session, Verbosity,
FormatReportFormatterBuilder, Input, Session, Verbosity,
};

fn main() {
Expand Down Expand Up @@ -310,19 +310,12 @@ fn format_and_emit_report<T: Write>(session: &mut Session<'_, T>, input: Input)
match session.format(input) {
Ok(report) => {
if report.has_warnings() {
match term::stderr() {
Some(ref t)
if session.config.color().use_colored_tty()
&& t.supports_color()
&& t.supports_attr(term::Attr::Bold) =>
{
match report.fancy_print(term::stderr().unwrap()) {
Ok(..) => (),
Err(..) => panic!("Unable to write to stderr: {}", report),
}
}
_ => eprintln!("{}", report),
}
eprintln!(
"{}",
FormatReportFormatterBuilder::new(&report)
.enable_colors(should_print_with_colors(session))
.build()
);
}
}
Err(msg) => {
Expand All @@ -332,6 +325,19 @@ fn format_and_emit_report<T: Write>(session: &mut Session<'_, T>, input: Input)
}
}

fn should_print_with_colors<T: Write>(session: &mut Session<'_, T>) -> bool {
match term::stderr() {
Some(ref t)
if session.config.color().use_colored_tty()
&& t.supports_color()
&& t.supports_attr(term::Attr::Bold) =>
{
true
}
_ => false,
}
}

fn print_usage_to_stdout(opts: &Options, reason: &str) {
let sep = if reason.is_empty() {
String::new()
Expand Down
173 changes: 173 additions & 0 deletions src/format_report_formatter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
use crate::config::FileName;
use crate::formatting::FormattingError;
use crate::{ErrorKind, FormatReport};
use annotate_snippets::display_list::DisplayList;
use annotate_snippets::formatter::DisplayListFormatter;
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
use std::fmt::{self, Display};

/// A builder for [`FormatReportFormatter`].
pub struct FormatReportFormatterBuilder<'a> {
report: &'a FormatReport,
enable_colors: bool,
}

impl<'a> FormatReportFormatterBuilder<'a> {
/// Creates a new [`FormatReportFormatterBuilder`].
pub fn new(report: &'a FormatReport) -> Self {
Self {
report,
enable_colors: false,
}
}

/// Enables colors and formatting in the output.
pub fn enable_colors(self, enable_colors: bool) -> Self {
Self {
enable_colors,
..self
}
}

/// Creates a new [`FormatReportFormatter`] from the settings in this builder.
pub fn build(self) -> FormatReportFormatter<'a> {
FormatReportFormatter {
report: self.report,
enable_colors: self.enable_colors,
}
}
}

/// Formats the warnings/errors in a [`FormatReport`].
///
/// Can be created using a [`FormatReportFormatterBuilder`].
pub struct FormatReportFormatter<'a> {
report: &'a FormatReport,
enable_colors: bool,
}

impl<'a> Display for FormatReportFormatter<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let formatter = DisplayListFormatter::new(self.enable_colors);
let errors_by_file = &self.report.internal.borrow().0;

for (file, errors) in errors_by_file {
for error in errors {
let snippet = formatting_error_to_snippet(file, error);
writeln!(f, "{}\n", formatter.format(&DisplayList::from(snippet)))?;
}
}

if !errors_by_file.is_empty() {
let snippet = formatting_failure_snippet(self.report.warning_count());
writeln!(f, "{}", formatter.format(&DisplayList::from(snippet)))?;
}

Ok(())
}
}

fn formatting_failure_snippet(warning_count: usize) -> Snippet {
Snippet {
title: Some(Annotation {
id: None,
label: Some(format!(
"rustfmt has failed to format. See previous {} errors.",
warning_count
)),
annotation_type: AnnotationType::Warning,
}),
footer: Vec::new(),
slices: Vec::new(),
}
}

fn formatting_error_to_snippet(file: &FileName, error: &FormattingError) -> Snippet {
let slices = vec![snippet_code_slice(file, error)];
let title = Some(snippet_title(error));
let footer = snippet_footer(error).into_iter().collect();

Snippet {
title,
footer,
slices,
}
}

fn snippet_title(error: &FormattingError) -> Annotation {
let annotation_type = error_kind_to_snippet_annotation_type(&error.kind);

Annotation {
id: title_annotation_id(error),
label: Some(error.kind.to_string()),
annotation_type,
}
}

fn snippet_footer(error: &FormattingError) -> Option<Annotation> {
let message_suffix = error.msg_suffix();

if !message_suffix.is_empty() {
Some(Annotation {
id: None,
label: Some(message_suffix.to_string()),
annotation_type: AnnotationType::Note,
})
} else {
None
}
}

fn snippet_code_slice(file: &FileName, error: &FormattingError) -> Slice {
let annotations = slice_annotation(error).into_iter().collect();
let origin = Some(format!("{}:{}", file, error.line));
let source = error.line_buffer.clone();

Slice {
source,
line_start: error.line,
origin,
fold: false,
annotations,
}
}

fn slice_annotation(error: &FormattingError) -> Option<SourceAnnotation> {
let (range_start, range_length) = error.format_len();
let range_end = range_start + range_length;

if range_length > 0 {
Some(SourceAnnotation {
annotation_type: AnnotationType::Error,
range: (range_start, range_end),
label: String::new(),
})
} else {
None
}
}

fn title_annotation_id(error: &FormattingError) -> Option<String> {
const INTERNAL_ERROR_ID: &str = "internal";

if error.is_internal() {
Some(INTERNAL_ERROR_ID.to_string())
} else {
None
}
}

fn error_kind_to_snippet_annotation_type(error_kind: &ErrorKind) -> AnnotationType {
match error_kind {
ErrorKind::LineOverflow(..)
| ErrorKind::TrailingWhitespace
| ErrorKind::IoError(_)
| ErrorKind::ParseError
| ErrorKind::LostComment
| ErrorKind::LicenseCheck
| ErrorKind::BadAttr
| ErrorKind::InvalidGlobPattern(_)
| ErrorKind::VersionMismatch => AnnotationType::Error,
ErrorKind::BadIssue(_) | ErrorKind::DeprecatedAttr => AnnotationType::Warning,
}
}
10 changes: 3 additions & 7 deletions src/formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,18 +275,14 @@ impl FormattingError {
}
}

pub(crate) fn msg_prefix(&self) -> &str {
pub(crate) fn is_internal(&self) -> bool {
match self.kind {
ErrorKind::LineOverflow(..)
| ErrorKind::TrailingWhitespace
| ErrorKind::IoError(_)
| ErrorKind::ParseError
| ErrorKind::LostComment => "internal error:",
ErrorKind::LicenseCheck
| ErrorKind::BadAttr
| ErrorKind::InvalidGlobPattern(..)
| ErrorKind::VersionMismatch => "error:",
ErrorKind::BadIssue(_) | ErrorKind::DeprecatedAttr => "warning:",
| ErrorKind::LostComment => true,
_ => false,
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/git-rustfmt/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use env_logger;
use getopts::{Matches, Options};
use rustfmt_nightly as rustfmt;

use crate::rustfmt::{load_config, CliOptions, Input, Session};
use crate::rustfmt::{load_config, CliOptions, FormatReportFormatterBuilder, Input, Session};

fn prune_files(files: Vec<&str>) -> Vec<&str> {
let prefixes: Vec<_> = files
Expand Down Expand Up @@ -67,7 +67,7 @@ fn fmt_files(files: &[&str]) -> i32 {
for file in files {
let report = session.format(Input::File(PathBuf::from(file))).unwrap();
if report.has_warnings() {
eprintln!("{}", report);
eprintln!("{}", FormatReportFormatterBuilder::new(&report).build());
}
if !session.has_no_errors() {
exit_code = 1;
Expand Down
Loading

0 comments on commit 3dc625c

Please sign in to comment.