// option. This file may not be copied, modified, or distributed
// except according to those terms.
-#![feature(custom_attribute)]
+#![feature(tool_attributes)]
#![feature(decl_macro)]
-#![feature(match_default_bindings)]
+#![allow(unused_attributes)]
#![feature(type_ascription)]
+#![feature(unicode_internals)]
#[macro_use]
extern crate derive_new;
extern crate diff;
+#[macro_use]
+extern crate failure;
+extern crate getopts;
extern crate itertools;
+#[cfg(test)]
+#[macro_use]
+extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate regex;
-extern crate rustc_errors as errors;
+extern crate rustc_target;
extern crate serde;
#[macro_use]
extern crate serde_derive;
use std::collections::HashMap;
use std::fmt;
-use std::io::{self, stdout, BufRead, Write};
-use std::iter::repeat;
+use std::io::{self, stdout, Write};
+use std::panic::{catch_unwind, AssertUnwindSafe};
use std::path::PathBuf;
use std::rc::Rc;
use std::time::Duration;
-use errors::{DiagnosticBuilder, Handler};
-use errors::emitter::{ColorConfig, EmitterWriter};
use syntax::ast;
-use syntax::codemap::{CodeMap, FilePathMapping};
pub use syntax::codemap::FileName;
+use syntax::codemap::{CodeMap, FilePathMapping};
+use syntax::errors::emitter::{ColorConfig, EmitterWriter};
+use syntax::errors::{DiagnosticBuilder, Handler};
use syntax::parse::{self, ParseSess};
-use checkstyle::{output_footer, output_header};
-use comment::{CharClasses, FullCodeCharKind};
+use comment::{CharClasses, FullCodeCharKind, LineClasses};
+use failure::Fail;
use issues::{BadIssueSeeker, Issue};
use shape::Indent;
use utils::use_colored_tty;
use visitor::{FmtVisitor, SnippetProvider};
-pub use config::Config;
+pub use config::options::CliOptions;
pub use config::summary::Summary;
+pub use config::{file_lines, load_config, Config, Verbosity, WriteMode};
+
+pub type FmtResult<T> = std::result::Result<T, failure::Error>;
#[macro_use]
mod utils;
mod attr;
mod chains;
-mod checkstyle;
+pub(crate) mod checkstyle;
mod closures;
-pub mod codemap;
+pub(crate) mod codemap;
mod comment;
-pub mod config;
+pub(crate) mod config;
mod expr;
-pub mod filemap;
+pub(crate) mod filemap;
mod imports;
mod issues;
mod items;
mod lists;
mod macros;
+mod matches;
mod missed_spans;
-pub mod modules;
+pub(crate) mod modules;
+mod overflow;
mod patterns;
mod reorder;
mod rewrite;
-pub mod rustfmt_diff;
+pub(crate) mod rustfmt_diff;
mod shape;
mod spanned;
mod string;
+#[cfg(test)]
+mod test;
mod types;
mod vertical;
-pub mod visitor;
+pub(crate) mod visitor;
const STDIN: &str = "<stdin>";
// A map of the files of a crate, with their new content
-pub type FileMap = Vec<FileRecord>;
+pub(crate) type FileMap = Vec<FileRecord>;
-pub type FileRecord = (FileName, String);
+pub(crate) type FileRecord = (FileName, String);
-#[derive(Clone, Copy)]
+#[derive(Fail, Debug, Clone, Copy)]
pub enum ErrorKind {
// Line has exceeded character limit (found, maximum)
+ #[fail(
+ display = "line formatted, but exceeded maximum width \
+ (maximum: {} (see `max_width` option), found: {})",
+ _0,
+ _1
+ )]
LineOverflow(usize, usize),
// Line ends in whitespace
+ #[fail(display = "left behind trailing whitespace")]
TrailingWhitespace,
- // TO-DO or FIX-ME item without an issue number
+ // TODO or FIXME item without an issue number
+ #[fail(display = "found {}", _0)]
BadIssue(Issue),
// License check has failed
+ #[fail(display = "license check failed")]
LicenseCheck,
}
-impl fmt::Display for ErrorKind {
- fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
- match *self {
- ErrorKind::LineOverflow(found, maximum) => write!(
- fmt,
- "line exceeded maximum width (maximum: {}, found: {})",
- maximum, found
- ),
- ErrorKind::TrailingWhitespace => write!(fmt, "left behind trailing whitespace"),
- ErrorKind::BadIssue(issue) => write!(fmt, "found {}", issue),
- ErrorKind::LicenseCheck => write!(fmt, "license check failed"),
- }
- }
-}
-
// Formatting errors that are identified *after* rustfmt has run.
-pub struct FormattingError {
+struct FormattingError {
line: usize,
kind: ErrorKind,
is_comment: bool,
impl FormattingError {
fn msg_prefix(&self) -> &str {
match self.kind {
- ErrorKind::LineOverflow(..)
- | ErrorKind::TrailingWhitespace
- | ErrorKind::LicenseCheck => "error:",
- ErrorKind::BadIssue(_) => "WARNING:",
+ ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => "internal error:",
+ ErrorKind::LicenseCheck => "error:",
+ ErrorKind::BadIssue(_) => "warning:",
}
}
}
// (space, target)
- pub fn format_len(&self) -> (usize, usize) {
+ 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)
+ let trailing_ws_start = self
+ .line_buffer
+ .rfind(|c: char| !c.is_whitespace())
+ .map(|pos| pos + 1)
+ .unwrap_or(0);
+ (
+ trailing_ws_start,
+ self.line_buffer.len() - trailing_ws_start,
+ )
}
_ => unreachable!(),
}
}
}
- pub fn warning_count(&self) -> usize {
+ fn warning_count(&self) -> usize {
self.file_error_map
.iter()
.map(|(_, errors)| errors.len())
.sum()
}
- pub fn has_warnings(&self) -> bool {
+ fn has_warnings(&self) -> bool {
self.warning_count() > 0
}
- pub fn print_warnings_fancy(
+ 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();
+ let prefix_spaces = " ".repeat(1 + prefix_space_len);
// First line: the overview of error
t.fg(term::color::RED)?;
write!(t, "{} ", error.msg_prefix())?;
t.reset()?;
t.attr(term::Attr::Bold)?;
- write!(t, "{}\n", error.kind)?;
+ writeln!(t, "{}", error.kind)?;
// Second line: file info
write!(t, "{}--> ", &prefix_spaces[1..])?;
t.reset()?;
- write!(t, "{}:{}\n", file, error.line)?;
+ writeln!(t, "{}:{}", file, error.line)?;
// Third to fifth lines: show the line which triggered error, if available.
if !error.line_buffer.is_empty() {
t.attr(term::Attr::Bold)?;
write!(t, "{}|\n{} | ", prefix_spaces, error.line)?;
t.reset()?;
- write!(t, "{}\n", error.line_buffer)?;
+ writeln!(t, "{}", 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))?;
+ writeln!(t, "{}", target_str(space_len, target_len))?;
t.reset()?;
}
t.attr(term::Attr::Bold)?;
write!(t, "{}= note: ", prefix_spaces)?;
t.reset()?;
- write!(t, "{}\n", error.msg_suffix())?;
+ writeln!(t, "{}", error.msg_suffix())?;
} else {
- write!(t, "\n")?;
+ writeln!(t)?;
}
t.reset()?;
}
}
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();
+ let empty_line = " ".repeat(space_len);
+ let overflowed = "^".repeat(target_len);
empty_line + &overflowed
}
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 prefix_spaces = " ".repeat(1 + prefix_space_len);
let error_line_buffer = if error.line_buffer.is_empty() {
String::from(" ")
format!("{}note= ", prefix_spaces)
};
- write!(
+ writeln!(
fmt,
- "{}\n{}\n{}\n{}{}\n",
+ "{}\n{}\n{}\n{}{}",
error_info,
file_info,
error_line_buffer,
}
}
if !self.file_error_map.is_empty() {
- write!(
+ writeln!(
fmt,
- "warning: rustfmt may have failed to format. See previous {} errors.\n",
+ "warning: rustfmt may have failed to format. See previous {} errors.",
self.warning_count(),
)?;
}
where
F: Fn(),
{
- if config.verbose() && path.to_string() != STDIN {
+ if config.verbose() == Verbosity::Verbose && path.to_string() != STDIN {
f();
}
}
// diff mode: check if any files are differing
let mut has_diff = false;
- // We always skip children for the "Plain" write mode, since there is
- // nothing to distinguish the nested module contents.
- let skip_children = config.skip_children() || config.write_mode() == config::WriteMode::Plain;
+ let skip_children = config.skip_children();
for (path, module) in modules::list_files(krate, parse_session.codemap())? {
- if skip_children && path != *main_file {
+ if (skip_children && path != *main_file) || config.ignore().skip_file(&path) {
continue;
}
should_emit_verbose(&path, config, || println!("Formatting {}", path));
debug_assert_eq!(
visitor.line_number,
- ::utils::count_newlines(&format!("{}", visitor.buffer))
+ ::utils::count_newlines(&visitor.buffer)
);
let filename = path.clone();
Ok((result, has_diff))
}
-/// Returns true if the line with the given line number was skipped by `#[rustfmt_skip]`.
+/// Returns true if the line with the given line number was skipped by `#[rustfmt::skip]`.
fn is_skipped_line(line_number: usize, skipped_range: &[(usize, usize)]) -> bool {
skipped_range
.iter()
// Check for any line width errors we couldn't correct.
let error_kind = ErrorKind::LineOverflow(line_len, config.max_width());
- if line_len > config.max_width() && !is_skipped_line(cur_line, skipped_range)
+ if line_len > config.max_width()
+ && !is_skipped_line(cur_line, skipped_range)
&& should_report_error(config, kind, is_string, error_kind)
{
errors.push(FormattingError {
input: Input,
parse_session: &'sess ParseSess,
config: &Config,
-) -> Result<ast::Crate, Option<DiagnosticBuilder<'sess>>> {
- let result = match input {
- Input::File(file) => {
- let mut parser = parse::new_parser_from_file(parse_session, &file);
- parser.cfg_mods = false;
- if config.skip_children() {
- parser.recurse_into_file_modules = false;
- }
- parser.parse_crate_mod()
- }
- Input::Text(text) => {
- let mut parser = parse::new_parser_from_source_str(
- parse_session,
- FileName::Custom("stdin".to_owned()),
- text,
- );
- parser.cfg_mods = false;
- if config.skip_children() {
- parser.recurse_into_file_modules = false;
- }
- parser.parse_crate_mod()
- }
+) -> Result<ast::Crate, ParseError<'sess>> {
+ let mut parser = match input {
+ Input::File(file) => parse::new_parser_from_file(parse_session, &file),
+ Input::Text(text) => parse::new_parser_from_source_str(
+ parse_session,
+ FileName::Custom("stdin".to_owned()),
+ text,
+ ),
};
+ parser.cfg_mods = false;
+ if config.skip_children() {
+ parser.recurse_into_file_modules = false;
+ }
+
+ let mut parser = AssertUnwindSafe(parser);
+ let result = catch_unwind(move || parser.0.parse_crate_mod());
+
match result {
- Ok(c) => {
+ Ok(Ok(c)) => {
if parse_session.span_diagnostic.has_errors() {
// Bail out if the parser recovered from an error.
- Err(None)
+ Err(ParseError::Recovered)
} else {
Ok(c)
}
}
- Err(e) => Err(Some(e)),
+ Ok(Err(e)) => Err(ParseError::Error(e)),
+ Err(_) => Err(ParseError::Panic),
}
}
+/// All the ways that parsing can fail.
+enum ParseError<'sess> {
+ /// There was an error, but the parser recovered.
+ Recovered,
+ /// There was an error (supplied) and parsing failed.
+ Error(DiagnosticBuilder<'sess>),
+ /// The parser panicked.
+ Panic,
+}
+
/// Format the given snippet. The snippet is expected to be *complete* code.
/// When we cannot parse the given snippet, this function returns `None`.
pub fn format_snippet(snippet: &str, config: &Config) -> Option<String> {
let mut out: Vec<u8> = Vec::with_capacity(snippet.len() * 2);
let input = Input::Text(snippet.into());
let mut config = config.clone();
- config.set().write_mode(config::WriteMode::Plain);
+ config.set().write_mode(config::WriteMode::Display);
+ config.set().verbose(Verbosity::Quiet);
config.set().hide_parse_errors(true);
match format_input(input, &config, Some(&mut out)) {
// `format_input()` returns an empty string on parsing error.
}
}
+const FN_MAIN_PREFIX: &str = "fn main() {\n";
+
+fn enclose_in_main_block(s: &str, config: &Config) -> String {
+ let indent = Indent::from_width(config, config.tab_spaces());
+ let mut result = String::with_capacity(s.len() * 2);
+ result.push_str(FN_MAIN_PREFIX);
+ let mut need_indent = true;
+ for (kind, line) in LineClasses::new(s) {
+ if need_indent {
+ result.push_str(&indent.to_string(config));
+ }
+ result.push_str(&line);
+ result.push('\n');
+ need_indent = !kind.is_string() || line.ends_with('\\');
+ }
+ result.push('}');
+ result
+}
+
/// Format the given code block. Mainly targeted for code block in comment.
/// The code block may be incomplete (i.e. parser may be unable to parse it).
/// To avoid panic in parser, we wrap the code block with a dummy function.
/// The returned code block does *not* end with newline.
pub fn format_code_block(code_snippet: &str, config: &Config) -> Option<String> {
// Wrap the given code block with `fn main()` if it does not have one.
- let fn_main_prefix = "fn main() {\n";
- let snippet = fn_main_prefix.to_owned() + code_snippet + "\n}";
+ let snippet = enclose_in_main_block(code_snippet, config);
let mut result = String::with_capacity(snippet.len());
let mut is_first = true;
// then unindent the whole code block.
let formatted = format_snippet(&snippet, config)?;
// 2 = "}\n"
- let block_len = formatted.len().checked_sub(2).unwrap_or(0);
- for line in formatted[fn_main_prefix.len()..block_len].lines() {
+ let block_len = formatted.rfind('}').unwrap_or(formatted.len());
+ let mut is_indented = true;
+ for (kind, ref line) in LineClasses::new(&formatted[FN_MAIN_PREFIX.len()..block_len]) {
if !is_first {
result.push('\n');
} else {
is_first = false;
}
- let trimmed_line = if line.len() > config.max_width() {
+ let trimmed_line = if !is_indented {
+ line
+ } else if line.len() > config.max_width() {
// If there are lines that are larger than max width, we cannot tell
// whether we have succeeded but have some comments or strings that
// are too long, or we have failed to format code block. We will be
line
};
result.push_str(trimmed_line);
+ is_indented = !kind.is_string() || line.ends_with('\\');
}
Some(result)
}
pub fn format_input<T: Write>(
+ input: Input,
+ config: &Config,
+ out: Option<&mut T>,
+) -> Result<(Summary, FileMap, FormatReport), (io::Error, Summary)> {
+ syntax::with_globals(|| format_input_inner(input, config, out))
+}
+
+fn format_input_inner<T: Write>(
input: Input,
config: &Config,
mut out: Option<&mut T>,
let krate = match parse_input(input, &parse_session, config) {
Ok(krate) => krate,
- Err(diagnostic) => {
- if let Some(mut diagnostic) = diagnostic {
- diagnostic.emit();
+ Err(err) => {
+ match err {
+ ParseError::Error(mut diagnostic) => diagnostic.emit(),
+ ParseError::Panic => {
+ // Note that if you see this message and want more information,
+ // then go to `parse_input` and run the parse function without
+ // `catch_unwind` so rustfmt panics and you can get a backtrace.
+ should_emit_verbose(&main_file, config, || {
+ println!("The Rust parser panicked")
+ });
+ }
+ ParseError::Recovered => {}
}
summary.add_parsing_error();
return Ok((summary, FileMap::new(), FormatReport::new()));
summary.mark_parse_time();
- if parse_session.span_diagnostic.has_errors() {
- summary.add_parsing_error();
- }
-
// Suppress error output after parsing.
let silent_emitter = Box::new(EmitterWriter::new(
Box::new(Vec::new()),
/// A single span of changed lines, with 0 or more removed lines
/// and a vector of 0 or more inserted lines.
#[derive(Debug, PartialEq, Eq)]
-pub struct ModifiedChunk {
+struct ModifiedChunk {
/// The first to be removed from the original text
pub line_number_orig: u32,
/// The number of lines which have been replaced
/// Set of changed sections of a file.
#[derive(Debug, PartialEq, Eq)]
-pub struct ModifiedLines {
+struct ModifiedLines {
/// The set of changed chunks.
pub chunks: Vec<ModifiedChunk>,
}
/// The successful result of formatting via `get_modified_lines()`.
-pub struct ModifiedLinesResult {
+#[cfg(test)]
+struct ModifiedLinesResult {
/// The high level summary details
pub summary: Summary,
/// The result Filemap
/// Format a file and return a `ModifiedLines` data structure describing
/// the changed ranges of lines.
-pub fn get_modified_lines(
+#[cfg(test)]
+fn get_modified_lines(
input: Input,
config: &Config,
) -> Result<ModifiedLinesResult, (io::Error, Summary)> {
+ use std::io::BufRead;
+
let mut data = Vec::new();
let mut config = config.clone();
Text(String),
}
-pub fn run(input: Input, config: &Config) -> Summary {
+pub fn format_and_emit_report(input: Input, config: &Config) -> FmtResult<Summary> {
+ if !config.version_meets_requirement() {
+ return Err(format_err!("Version mismatch"));
+ }
let out = &mut stdout();
- output_header(out, config.write_mode()).ok();
match format_input(input, config, Some(out)) {
Ok((summary, _, report)) => {
- output_footer(out, config.write_mode()).ok();
-
if report.has_warnings() {
match term::stderr() {
Some(ref t)
- if use_colored_tty(config.color()) && t.supports_color()
+ if use_colored_tty(config.color())
+ && t.supports_color()
&& t.supports_attr(term::Attr::Bold) =>
{
match report.print_warnings_fancy(term::stderr().unwrap()) {
Err(..) => panic!("Unable to write to stderr: {}", report),
}
}
- _ => msg!("{}", report),
+ _ => eprintln!("{}", report),
}
}
- summary
+ Ok(summary)
}
Err((msg, mut summary)) => {
- msg!("Error writing files: {}", msg);
+ eprintln!("Error writing files: {}", msg);
summary.add_operational_error();
- summary
+ Ok(summary)
}
}
}
+pub fn emit_pre_matter(config: &Config) -> FmtResult<()> {
+ if config.write_mode() == WriteMode::Checkstyle {
+ let mut out = &mut stdout();
+ checkstyle::output_header(&mut out)?;
+ }
+ Ok(())
+}
+
+pub fn emit_post_matter(config: &Config) -> FmtResult<()> {
+ if config.write_mode() == WriteMode::Checkstyle {
+ let mut out = &mut stdout();
+ checkstyle::output_footer(&mut out)?;
+ }
+ Ok(())
+}
+
#[cfg(test)]
-mod test {
+mod unit_tests {
use super::{format_code_block, format_snippet, Config};
#[test]
#[test]
fn test_format_code_block_fail() {
- #[rustfmt_skip]
+ #[rustfmt::skip]
let code_block = "this_line_is_100_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(x, y, z);";
assert!(format_code_block(code_block, &Config::default()).is_none());
}