Skip to content

Enhance error messages #1884

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 157 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extern crate unicode_segmentation;
use std::collections::HashMap;
use std::fmt;
use std::io::{self, stdout, Write};
use std::iter::repeat;
use std::ops::{Add, Sub};
use std::path::{Path, PathBuf};
use std::rc::Rc;
Expand All @@ -42,7 +43,7 @@ use checkstyle::{output_footer, output_header};
use config::Config;
use filemap::FileMap;
use issues::{BadIssueSeeker, Issue};
use utils::{mk_sp, outer_attributes};
use utils::{isatty, mk_sp, outer_attributes};
use visitor::FmtVisitor;

pub use self::summary::Summary;
Expand Down Expand Up @@ -456,7 +457,7 @@ impl fmt::Display for ErrorKind {
match *self {
ErrorKind::LineOverflow(found, maximum) => write!(
fmt,
"line exceeded maximum length (maximum: {}, found: {})",
"line exceeded maximum width (maximum: {}, found: {})",
maximum,
found
),
Expand All @@ -468,22 +469,43 @@ impl fmt::Display for ErrorKind {

// Formatting errors that are identified *after* rustfmt has run.
pub struct FormattingError {
line: u32,
line: usize,
kind: ErrorKind,
is_comment: bool,
line_buffer: String,
}

impl FormattingError {
fn msg_prefix(&self) -> &str {
match self.kind {
ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => "Rustfmt failed at",
ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => "error:",
ErrorKind::BadIssue(_) => "WARNING:",
}
}

fn msg_suffix(&self) -> &str {
fn msg_suffix(&self) -> String {
match self.kind {
ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => "(sorry)",
ErrorKind::BadIssue(_) => "",
ErrorKind::LineOverflow(..) if self.is_comment => format!(
"use `error_on_lineoverflow_comments = false` to suppress \
the warning against line comments\n",
),
_ => String::from(""),
}
}

// (space, target)
pub fn format_len(&self) -> (usize, usize) {
match self.kind {
ErrorKind::LineOverflow(found, max) => (max, found - max),
ErrorKind::TrailingWhitespace => {
let trailing_ws_len = self.line_buffer
.chars()
.rev()
.take_while(|c| c.is_whitespace())
.count();
(self.line_buffer.len() - trailing_ws_len, trailing_ws_len)
}
_ => (0, 0), // unreachable
}
}
}
Expand All @@ -510,24 +532,127 @@ impl FormatReport {
pub fn has_warnings(&self) -> bool {
self.warning_count() > 0
}

pub fn print_warnings_fancy(
&self,
mut t: Box<term::Terminal<Output = io::Stderr>>,
) -> Result<(), term::Error> {
for (file, errors) in &self.file_error_map {
for error in errors {
let prefix_space_len = error.line.to_string().len();
let prefix_spaces: String = repeat(" ").take(1 + prefix_space_len).collect();

// First line: the overview of error
t.fg(term::color::RED)?;
t.attr(term::Attr::Bold)?;
write!(t, "{} ", error.msg_prefix())?;
t.reset()?;
t.attr(term::Attr::Bold)?;
write!(t, "{}\n", error.kind)?;

// Second line: file info
write!(t, "{}--> ", &prefix_spaces[1..])?;
t.reset()?;
write!(t, "{}:{}\n", file, error.line)?;

// Third to fifth lines: show the line which triggered error, if available.
if !error.line_buffer.is_empty() {
let (space_len, target_len) = error.format_len();
t.attr(term::Attr::Bold)?;
write!(t, "{}|\n{} | ", prefix_spaces, error.line)?;
t.reset()?;
write!(t, "{}\n", error.line_buffer)?;
t.attr(term::Attr::Bold)?;
write!(t, "{}| ", prefix_spaces)?;
t.fg(term::color::RED)?;
write!(t, "{}\n", target_str(space_len, target_len))?;
t.reset()?;
}

// The last line: show note if available.
let msg_suffix = error.msg_suffix();
if !msg_suffix.is_empty() {
t.attr(term::Attr::Bold)?;
write!(t, "{}= note: ", prefix_spaces)?;
t.reset()?;
write!(t, "{}\n", error.msg_suffix())?;
} else {
write!(t, "\n")?;
}
t.reset()?;
}
}

if !self.file_error_map.is_empty() {
t.attr(term::Attr::Bold)?;
write!(t, "warning: ")?;
t.reset()?;
write!(
t,
"rustfmt may have failed to format. See previous {} errors.\n\n",
self.warning_count(),
)?;
}

Ok(())
}
}

fn target_str(space_len: usize, target_len: usize) -> String {
let empty_line: String = repeat(" ").take(space_len).collect();
let overflowed: String = repeat("^").take(target_len).collect();
empty_line + &overflowed
}

impl fmt::Display for FormatReport {
// Prints all the formatting errors.
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
for (file, errors) in &self.file_error_map {
for error in errors {
let prefix_space_len = error.line.to_string().len();
let prefix_spaces: String = repeat(" ").take(1 + prefix_space_len).collect();

let error_line_buffer = if error.line_buffer.is_empty() {
String::from(" ")
} else {
let (space_len, target_len) = error.format_len();
format!(
"{}|\n{} | {}\n{}| {}",
prefix_spaces,
error.line,
error.line_buffer,
prefix_spaces,
target_str(space_len, target_len)
)
};

let error_info = format!("{} {}", error.msg_prefix(), error.kind);
let file_info = format!("{}--> {}:{}", &prefix_spaces[1..], file, error.line);
let msg_suffix = error.msg_suffix();
let note = if msg_suffix.is_empty() {
String::new()
} else {
format!("{}note= ", prefix_spaces)
};

write!(
fmt,
"{} {}:{}: {} {}\n",
error.msg_prefix(),
file,
error.line,
error.kind,
"{}\n{}\n{}\n{}{}\n",
error_info,
file_info,
error_line_buffer,
note,
error.msg_suffix()
)?;
}
}
if !self.file_error_map.is_empty() {
write!(
fmt,
"warning: rustfmt may have failed to format. See previous {} errors.\n",
self.warning_count(),
)?;
}
Ok(())
}
}
Expand Down Expand Up @@ -601,6 +726,7 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
let mut issue_seeker = BadIssueSeeker::new(config.report_todo(), config.report_fixme());
let mut prev_char: Option<char> = None;
let mut is_comment = false;
let mut line_buffer = String::with_capacity(config.max_width() * 2);

for (c, b) in text.chars() {
if c == '\r' {
Expand All @@ -615,6 +741,8 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
errors.push(FormattingError {
line: cur_line,
kind: ErrorKind::BadIssue(issue),
is_comment: false,
line_buffer: String::new(),
});
}
}
Expand All @@ -623,7 +751,7 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
if format_line {
// Check for (and record) trailing whitespace.
if let Some(lw) = last_wspace {
trims.push((cur_line, lw, b));
trims.push((cur_line, lw, b, line_buffer.clone()));
line_len -= 1;
}

Expand All @@ -634,6 +762,8 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
errors.push(FormattingError {
line: cur_line,
kind: ErrorKind::LineOverflow(line_len, config.max_width()),
is_comment: is_comment,
line_buffer: line_buffer.clone(),
});
}
}
Expand All @@ -644,6 +774,7 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
last_wspace = None;
prev_char = None;
is_comment = false;
line_buffer.clear();
} else {
newline_count = 0;
line_len += 1;
Expand All @@ -661,6 +792,7 @@ fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &m
last_wspace = None;
}
prev_char = Some(c);
line_buffer.push(c);
}
}

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

for &(l, _, _) in &trims {
for &(l, _, _, ref b) in &trims {
errors.push(FormattingError {
line: l,
kind: ErrorKind::TrailingWhitespace,
is_comment: false,
line_buffer: b.clone(),
});
}

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

if report.has_warnings() {
msg!("{}", report);
match term::stderr() {
Some(ref t) if isatty() && t.supports_color() => {
match report.print_warnings_fancy(term::stderr().unwrap()) {
Ok(..) => (),
Err(..) => panic!("Unable to write to stderr: {}", report),
}
}
_ => msg!("{}", report),
}
}

summary
Expand Down
20 changes: 1 addition & 19 deletions src/rustfmt_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use diff;
use std::collections::VecDeque;
use std::io;
use term;
use utils::isatty;

#[derive(Debug, PartialEq)]
pub enum DiffLine {
Expand Down Expand Up @@ -105,25 +106,6 @@ where
}
_ => print_diff_basic(diff, get_section_title),
}

// isatty shamelessly adapted from cargo.
#[cfg(unix)]
fn isatty() -> bool {
extern crate libc;

unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 }
}
#[cfg(windows)]
fn isatty() -> bool {
extern crate kernel32;
extern crate winapi;

unsafe {
let handle = kernel32::GetStdHandle(winapi::winbase::STD_OUTPUT_HANDLE);
let mut out = 0;
kernel32::GetConsoleMode(handle, &mut out) != 0
}
}
}

fn print_diff_fancy<F>(
Expand Down
19 changes: 19 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,22 @@ pub fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr {
_ => e,
}
}

// isatty shamelessly adapted from cargo.
#[cfg(unix)]
pub fn isatty() -> bool {
extern crate libc;

unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 }
}
#[cfg(windows)]
pub fn isatty() -> bool {
extern crate kernel32;
extern crate winapi;

unsafe {
let handle = kernel32::GetStdHandle(winapi::winbase::STD_OUTPUT_HANDLE);
let mut out = 0;
kernel32::GetConsoleMode(handle, &mut out) != 0
}
}