Skip to content

Commit beb88ab

Browse files
authored
Merge pull request #1884 from topecongiro/better-error-report
Enhance error messages
2 parents a1d28bf + 5c50766 commit beb88ab

File tree

3 files changed

+177
-34
lines changed

3 files changed

+177
-34
lines changed

src/lib.rs

Lines changed: 157 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ extern crate unicode_segmentation;
2727
use std::collections::HashMap;
2828
use std::fmt;
2929
use std::io::{self, stdout, Write};
30+
use std::iter::repeat;
3031
use std::ops::{Add, Sub};
3132
use std::path::{Path, PathBuf};
3233
use std::rc::Rc;
@@ -42,7 +43,7 @@ use checkstyle::{output_footer, output_header};
4243
use config::Config;
4344
use filemap::FileMap;
4445
use issues::{BadIssueSeeker, Issue};
45-
use utils::{mk_sp, outer_attributes};
46+
use utils::{isatty, mk_sp, outer_attributes};
4647
use visitor::FmtVisitor;
4748

4849
pub use self::summary::Summary;
@@ -456,7 +457,7 @@ impl fmt::Display for ErrorKind {
456457
match *self {
457458
ErrorKind::LineOverflow(found, maximum) => write!(
458459
fmt,
459-
"line exceeded maximum length (maximum: {}, found: {})",
460+
"line exceeded maximum width (maximum: {}, found: {})",
460461
maximum,
461462
found
462463
),
@@ -468,22 +469,43 @@ impl fmt::Display for ErrorKind {
468469

469470
// Formatting errors that are identified *after* rustfmt has run.
470471
pub struct FormattingError {
471-
line: u32,
472+
line: usize,
472473
kind: ErrorKind,
474+
is_comment: bool,
475+
line_buffer: String,
473476
}
474477

475478
impl FormattingError {
476479
fn msg_prefix(&self) -> &str {
477480
match self.kind {
478-
ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => "Rustfmt failed at",
481+
ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => "error:",
479482
ErrorKind::BadIssue(_) => "WARNING:",
480483
}
481484
}
482485

483-
fn msg_suffix(&self) -> &str {
486+
fn msg_suffix(&self) -> String {
484487
match self.kind {
485-
ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => "(sorry)",
486-
ErrorKind::BadIssue(_) => "",
488+
ErrorKind::LineOverflow(..) if self.is_comment => format!(
489+
"use `error_on_lineoverflow_comments = false` to suppress \
490+
the warning against line comments\n",
491+
),
492+
_ => String::from(""),
493+
}
494+
}
495+
496+
// (space, target)
497+
pub fn format_len(&self) -> (usize, usize) {
498+
match self.kind {
499+
ErrorKind::LineOverflow(found, max) => (max, found - max),
500+
ErrorKind::TrailingWhitespace => {
501+
let trailing_ws_len = self.line_buffer
502+
.chars()
503+
.rev()
504+
.take_while(|c| c.is_whitespace())
505+
.count();
506+
(self.line_buffer.len() - trailing_ws_len, trailing_ws_len)
507+
}
508+
_ => (0, 0), // unreachable
487509
}
488510
}
489511
}
@@ -510,24 +532,127 @@ impl FormatReport {
510532
pub fn has_warnings(&self) -> bool {
511533
self.warning_count() > 0
512534
}
535+
536+
pub fn print_warnings_fancy(
537+
&self,
538+
mut t: Box<term::Terminal<Output = io::Stderr>>,
539+
) -> Result<(), term::Error> {
540+
for (file, errors) in &self.file_error_map {
541+
for error in errors {
542+
let prefix_space_len = error.line.to_string().len();
543+
let prefix_spaces: String = repeat(" ").take(1 + prefix_space_len).collect();
544+
545+
// First line: the overview of error
546+
t.fg(term::color::RED)?;
547+
t.attr(term::Attr::Bold)?;
548+
write!(t, "{} ", error.msg_prefix())?;
549+
t.reset()?;
550+
t.attr(term::Attr::Bold)?;
551+
write!(t, "{}\n", error.kind)?;
552+
553+
// Second line: file info
554+
write!(t, "{}--> ", &prefix_spaces[1..])?;
555+
t.reset()?;
556+
write!(t, "{}:{}\n", file, error.line)?;
557+
558+
// Third to fifth lines: show the line which triggered error, if available.
559+
if !error.line_buffer.is_empty() {
560+
let (space_len, target_len) = error.format_len();
561+
t.attr(term::Attr::Bold)?;
562+
write!(t, "{}|\n{} | ", prefix_spaces, error.line)?;
563+
t.reset()?;
564+
write!(t, "{}\n", error.line_buffer)?;
565+
t.attr(term::Attr::Bold)?;
566+
write!(t, "{}| ", prefix_spaces)?;
567+
t.fg(term::color::RED)?;
568+
write!(t, "{}\n", target_str(space_len, target_len))?;
569+
t.reset()?;
570+
}
571+
572+
// The last line: show note if available.
573+
let msg_suffix = error.msg_suffix();
574+
if !msg_suffix.is_empty() {
575+
t.attr(term::Attr::Bold)?;
576+
write!(t, "{}= note: ", prefix_spaces)?;
577+
t.reset()?;
578+
write!(t, "{}\n", error.msg_suffix())?;
579+
} else {
580+
write!(t, "\n")?;
581+
}
582+
t.reset()?;
583+
}
584+
}
585+
586+
if !self.file_error_map.is_empty() {
587+
t.attr(term::Attr::Bold)?;
588+
write!(t, "warning: ")?;
589+
t.reset()?;
590+
write!(
591+
t,
592+
"rustfmt may have failed to format. See previous {} errors.\n\n",
593+
self.warning_count(),
594+
)?;
595+
}
596+
597+
Ok(())
598+
}
599+
}
600+
601+
fn target_str(space_len: usize, target_len: usize) -> String {
602+
let empty_line: String = repeat(" ").take(space_len).collect();
603+
let overflowed: String = repeat("^").take(target_len).collect();
604+
empty_line + &overflowed
513605
}
514606

515607
impl fmt::Display for FormatReport {
516608
// Prints all the formatting errors.
517609
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
518610
for (file, errors) in &self.file_error_map {
519611
for error in errors {
612+
let prefix_space_len = error.line.to_string().len();
613+
let prefix_spaces: String = repeat(" ").take(1 + prefix_space_len).collect();
614+
615+
let error_line_buffer = if error.line_buffer.is_empty() {
616+
String::from(" ")
617+
} else {
618+
let (space_len, target_len) = error.format_len();
619+
format!(
620+
"{}|\n{} | {}\n{}| {}",
621+
prefix_spaces,
622+
error.line,
623+
error.line_buffer,
624+
prefix_spaces,
625+
target_str(space_len, target_len)
626+
)
627+
};
628+
629+
let error_info = format!("{} {}", error.msg_prefix(), error.kind);
630+
let file_info = format!("{}--> {}:{}", &prefix_spaces[1..], file, error.line);
631+
let msg_suffix = error.msg_suffix();
632+
let note = if msg_suffix.is_empty() {
633+
String::new()
634+
} else {
635+
format!("{}note= ", prefix_spaces)
636+
};
637+
520638
write!(
521639
fmt,
522-
"{} {}:{}: {} {}\n",
523-
error.msg_prefix(),
524-
file,
525-
error.line,
526-
error.kind,
640+
"{}\n{}\n{}\n{}{}\n",
641+
error_info,
642+
file_info,
643+
error_line_buffer,
644+
note,
527645
error.msg_suffix()
528646
)?;
529647
}
530648
}
649+
if !self.file_error_map.is_empty() {
650+
write!(
651+
fmt,
652+
"warning: rustfmt may have failed to format. See previous {} errors.\n",
653+
self.warning_count(),
654+
)?;
655+
}
531656
Ok(())
532657
}
533658
}
@@ -601,6 +726,7 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
601726
let mut issue_seeker = BadIssueSeeker::new(config.report_todo(), config.report_fixme());
602727
let mut prev_char: Option<char> = None;
603728
let mut is_comment = false;
729+
let mut line_buffer = String::with_capacity(config.max_width() * 2);
604730

605731
for (c, b) in text.chars() {
606732
if c == '\r' {
@@ -615,6 +741,8 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
615741
errors.push(FormattingError {
616742
line: cur_line,
617743
kind: ErrorKind::BadIssue(issue),
744+
is_comment: false,
745+
line_buffer: String::new(),
618746
});
619747
}
620748
}
@@ -623,7 +751,7 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
623751
if format_line {
624752
// Check for (and record) trailing whitespace.
625753
if let Some(lw) = last_wspace {
626-
trims.push((cur_line, lw, b));
754+
trims.push((cur_line, lw, b, line_buffer.clone()));
627755
line_len -= 1;
628756
}
629757

@@ -634,6 +762,8 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
634762
errors.push(FormattingError {
635763
line: cur_line,
636764
kind: ErrorKind::LineOverflow(line_len, config.max_width()),
765+
is_comment: is_comment,
766+
line_buffer: line_buffer.clone(),
637767
});
638768
}
639769
}
@@ -644,6 +774,7 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
644774
last_wspace = None;
645775
prev_char = None;
646776
is_comment = false;
777+
line_buffer.clear();
647778
} else {
648779
newline_count = 0;
649780
line_len += 1;
@@ -661,6 +792,7 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
661792
last_wspace = None;
662793
}
663794
prev_char = Some(c);
795+
line_buffer.push(c);
664796
}
665797
}
666798

@@ -670,10 +802,12 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
670802
text.truncate(line);
671803
}
672804

673-
for &(l, _, _) in &trims {
805+
for &(l, _, _, ref b) in &trims {
674806
errors.push(FormattingError {
675807
line: l,
676808
kind: ErrorKind::TrailingWhitespace,
809+
is_comment: false,
810+
line_buffer: b.clone(),
677811
});
678812
}
679813

@@ -803,7 +937,15 @@ pub fn run(input: Input, config: &Config) -> Summary {
803937
output_footer(out, config.write_mode()).ok();
804938

805939
if report.has_warnings() {
806-
msg!("{}", report);
940+
match term::stderr() {
941+
Some(ref t) if isatty() && t.supports_color() => {
942+
match report.print_warnings_fancy(term::stderr().unwrap()) {
943+
Ok(..) => (),
944+
Err(..) => panic!("Unable to write to stderr: {}", report),
945+
}
946+
}
947+
_ => msg!("{}", report),
948+
}
807949
}
808950

809951
summary

src/rustfmt_diff.rs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use diff;
1212
use std::collections::VecDeque;
1313
use std::io;
1414
use term;
15+
use utils::isatty;
1516

1617
#[derive(Debug, PartialEq)]
1718
pub enum DiffLine {
@@ -105,25 +106,6 @@ where
105106
}
106107
_ => print_diff_basic(diff, get_section_title),
107108
}
108-
109-
// isatty shamelessly adapted from cargo.
110-
#[cfg(unix)]
111-
fn isatty() -> bool {
112-
extern crate libc;
113-
114-
unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 }
115-
}
116-
#[cfg(windows)]
117-
fn isatty() -> bool {
118-
extern crate kernel32;
119-
extern crate winapi;
120-
121-
unsafe {
122-
let handle = kernel32::GetStdHandle(winapi::winbase::STD_OUTPUT_HANDLE);
123-
let mut out = 0;
124-
kernel32::GetConsoleMode(handle, &mut out) != 0
125-
}
126-
}
127109
}
128110

129111
fn print_diff_fancy<F>(

src/utils.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,3 +516,22 @@ pub fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr {
516516
_ => e,
517517
}
518518
}
519+
520+
// isatty shamelessly adapted from cargo.
521+
#[cfg(unix)]
522+
pub fn isatty() -> bool {
523+
extern crate libc;
524+
525+
unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 }
526+
}
527+
#[cfg(windows)]
528+
pub fn isatty() -> bool {
529+
extern crate kernel32;
530+
extern crate winapi;
531+
532+
unsafe {
533+
let handle = kernel32::GetStdHandle(winapi::winbase::STD_OUTPUT_HANDLE);
534+
let mut out = 0;
535+
kernel32::GetConsoleMode(handle, &mut out) != 0
536+
}
537+
}

0 commit comments

Comments
 (0)