// option. This file may not be copied, modified, or distributed
// except according to those terms.
+#![feature(match_default_bindings)]
#![feature(rustc_private)]
+#![feature(type_ascription)]
+#[macro_use]
+extern crate derive_new;
extern crate diff;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
-extern crate strings;
extern crate syntax;
extern crate term;
extern crate unicode_segmentation;
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::path::PathBuf;
use std::rc::Rc;
+use std::time::Duration;
use errors::{DiagnosticBuilder, Handler};
use errors::emitter::{ColorConfig, EmitterWriter};
-use macros::MacroArg;
-use strings::string_buffer::StringBuffer;
use syntax::ast;
-use syntax::codemap::{CodeMap, FilePathMapping, Span};
+use syntax::codemap::{CodeMap, FilePathMapping};
+pub use syntax::codemap::FileName;
use syntax::parse::{self, ParseSess};
use checkstyle::{output_footer, output_header};
-use config::Config;
+use comment::{CharClasses, FullCodeCharKind};
+pub use config::Config;
use filemap::FileMap;
use issues::{BadIssueSeeker, Issue};
-use utils::{isatty, mk_sp, outer_attributes};
-use visitor::FmtVisitor;
+use shape::Indent;
+use utils::use_colored_tty;
+use visitor::{FmtVisitor, SnippetProvider};
pub use self::summary::Summary;
#[macro_use]
mod utils;
+mod shape;
+mod spanned;
pub mod config;
pub mod codemap;
pub mod filemap;
pub mod file_lines;
pub mod visitor;
mod checkstyle;
+mod closures;
mod items;
mod missed_spans;
mod lists;
mod summary;
mod vertical;
-/// Spanned returns a span including attributes, if available.
-pub trait Spanned {
- fn span(&self) -> Span;
-}
-
-macro_rules! span_with_attrs_lo_hi {
- ($this:ident, $lo:expr, $hi:expr) => {
- {
- let attrs = outer_attributes(&$this.attrs);
- if attrs.is_empty() {
- mk_sp($lo, $hi)
- } else {
- mk_sp(attrs[0].span.lo(), $hi)
- }
- }
- }
-}
-
-macro_rules! span_with_attrs {
- ($this:ident) => {
- span_with_attrs_lo_hi!($this, $this.span.lo(), $this.span.hi())
- }
-}
-
-macro_rules! implement_spanned {
- ($this:ty) => {
- impl Spanned for $this {
- fn span(&self) -> Span {
- span_with_attrs!(self)
- }
- }
- }
-}
-
-// Implement `Spanned` for structs with `attrs` field.
-implement_spanned!(ast::Expr);
-implement_spanned!(ast::Field);
-implement_spanned!(ast::ForeignItem);
-implement_spanned!(ast::Item);
-implement_spanned!(ast::Local);
-
-impl Spanned for ast::Stmt {
- fn span(&self) -> Span {
- match self.node {
- ast::StmtKind::Local(ref local) => mk_sp(local.span().lo(), self.span.hi()),
- ast::StmtKind::Item(ref item) => mk_sp(item.span().lo(), self.span.hi()),
- ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => {
- mk_sp(expr.span().lo(), self.span.hi())
- }
- ast::StmtKind::Mac(ref mac) => {
- let (_, _, ref attrs) = **mac;
- if attrs.is_empty() {
- self.span
- } else {
- mk_sp(attrs[0].span.lo(), self.span.hi())
- }
- }
- }
- }
-}
-
-impl Spanned for ast::Pat {
- fn span(&self) -> Span {
- self.span
- }
-}
-
-impl Spanned for ast::Ty {
- fn span(&self) -> Span {
- self.span
- }
-}
-
-impl Spanned for ast::Arm {
- fn span(&self) -> Span {
- span_with_attrs_lo_hi!(self, self.pats[0].span.lo(), self.body.span.hi())
- }
-}
-
-impl Spanned for ast::Arg {
- fn span(&self) -> Span {
- if items::is_named_arg(self) {
- utils::mk_sp(self.pat.span.lo(), self.ty.span.hi())
- } else {
- self.ty.span
- }
- }
-}
-
-impl Spanned for ast::StructField {
- fn span(&self) -> Span {
- span_with_attrs_lo_hi!(self, self.span.lo(), self.ty.span.hi())
- }
-}
-
-impl Spanned for ast::WherePredicate {
- fn span(&self) -> Span {
- match *self {
- ast::WherePredicate::BoundPredicate(ref p) => p.span,
- ast::WherePredicate::RegionPredicate(ref p) => p.span,
- ast::WherePredicate::EqPredicate(ref p) => p.span,
- }
- }
-}
-
-impl Spanned for ast::FunctionRetTy {
- fn span(&self) -> Span {
- match *self {
- ast::FunctionRetTy::Default(span) => span,
- ast::FunctionRetTy::Ty(ref ty) => ty.span,
- }
- }
-}
-
-impl Spanned for ast::TyParam {
- fn span(&self) -> Span {
- // Note that ty.span is the span for ty.ident, not the whole item.
- let lo = if self.attrs.is_empty() {
- self.span.lo()
- } else {
- self.attrs[0].span.lo()
- };
- if let Some(ref def) = self.default {
- return mk_sp(lo, def.span.hi());
- }
- if self.bounds.is_empty() {
- return mk_sp(lo, self.span.hi());
- }
- let hi = self.bounds[self.bounds.len() - 1].span().hi();
- mk_sp(lo, hi)
- }
-}
-
-impl Spanned for ast::TyParamBound {
- fn span(&self) -> Span {
- match *self {
- ast::TyParamBound::TraitTyParamBound(ref ptr, _) => ptr.span,
- ast::TyParamBound::RegionTyParamBound(ref l) => l.span,
- }
- }
-}
-
-impl Spanned for MacroArg {
- fn span(&self) -> Span {
- match *self {
- MacroArg::Expr(ref expr) => expr.span(),
- MacroArg::Ty(ref ty) => ty.span(),
- MacroArg::Pat(ref pat) => pat.span(),
- }
- }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct Indent {
- // Width of the block indent, in characters. Must be a multiple of
- // Config::tab_spaces.
- pub block_indent: usize,
- // Alignment in characters.
- pub alignment: usize,
-}
-
-impl Indent {
- pub fn new(block_indent: usize, alignment: usize) -> Indent {
- Indent {
- block_indent: block_indent,
- alignment: alignment,
- }
- }
-
- pub fn from_width(config: &Config, width: usize) -> Indent {
- if config.hard_tabs() {
- let tab_num = width / config.tab_spaces();
- let alignment = width % config.tab_spaces();
- Indent::new(config.tab_spaces() * tab_num, alignment)
- } else {
- Indent::new(width, 0)
- }
- }
-
- pub fn empty() -> Indent {
- Indent::new(0, 0)
- }
-
- pub fn block_only(&self) -> Indent {
- Indent {
- block_indent: self.block_indent,
- alignment: 0,
- }
- }
-
- pub fn block_indent(mut self, config: &Config) -> Indent {
- self.block_indent += config.tab_spaces();
- self
- }
-
- pub fn block_unindent(mut self, config: &Config) -> Indent {
- if self.block_indent < config.tab_spaces() {
- Indent::new(self.block_indent, 0)
- } else {
- self.block_indent -= config.tab_spaces();
- self
- }
- }
-
- pub fn width(&self) -> usize {
- self.block_indent + self.alignment
- }
-
- pub fn to_string(&self, config: &Config) -> String {
- let (num_tabs, num_spaces) = if config.hard_tabs() {
- (self.block_indent / config.tab_spaces(), self.alignment)
- } else {
- (0, self.width())
- };
- let num_chars = num_tabs + num_spaces;
- let mut indent = String::with_capacity(num_chars);
- for _ in 0..num_tabs {
- indent.push('\t')
- }
- for _ in 0..num_spaces {
- indent.push(' ')
- }
- indent
- }
-}
-
-impl Add for Indent {
- type Output = Indent;
-
- fn add(self, rhs: Indent) -> Indent {
- Indent {
- block_indent: self.block_indent + rhs.block_indent,
- alignment: self.alignment + rhs.alignment,
- }
- }
-}
-
-impl Sub for Indent {
- type Output = Indent;
-
- fn sub(self, rhs: Indent) -> Indent {
- Indent::new(
- self.block_indent - rhs.block_indent,
- self.alignment - rhs.alignment,
- )
- }
-}
-
-impl Add<usize> for Indent {
- type Output = Indent;
-
- fn add(self, rhs: usize) -> Indent {
- Indent::new(self.block_indent, self.alignment + rhs)
- }
-}
-
-impl Sub<usize> for Indent {
- type Output = Indent;
-
- fn sub(self, rhs: usize) -> Indent {
- Indent::new(self.block_indent, self.alignment - rhs)
- }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct Shape {
- pub width: usize,
- // The current indentation of code.
- pub indent: Indent,
- // Indentation + any already emitted text on the first line of the current
- // statement.
- pub offset: usize,
-}
-
-impl Shape {
- /// `indent` is the indentation of the first line. The next lines
- /// should begin with at least `indent` spaces (except backwards
- /// indentation). The first line should not begin with indentation.
- /// `width` is the maximum number of characters on the last line
- /// (excluding `indent`). The width of other lines is not limited by
- /// `width`.
- /// Note that in reality, we sometimes use width for lines other than the
- /// last (i.e., we are conservative).
- // .......*-------*
- // | |
- // | *-*
- // *-----|
- // |<------------>| max width
- // |<---->| indent
- // |<--->| width
- pub fn legacy(width: usize, indent: Indent) -> Shape {
- Shape {
- width: width,
- indent: indent,
- offset: indent.alignment,
- }
- }
-
- pub fn indented(indent: Indent, config: &Config) -> Shape {
- Shape {
- width: config.max_width().checked_sub(indent.width()).unwrap_or(0),
- indent: indent,
- offset: indent.alignment,
- }
- }
-
- pub fn with_max_width(&self, config: &Config) -> Shape {
- Shape {
- width: config
- .max_width()
- .checked_sub(self.indent.width())
- .unwrap_or(0),
- ..*self
- }
- }
-
- pub fn offset(width: usize, indent: Indent, offset: usize) -> Shape {
- Shape {
- width: width,
- indent: indent,
- offset: offset,
- }
- }
-
- pub fn visual_indent(&self, extra_width: usize) -> Shape {
- let alignment = self.offset + extra_width;
- Shape {
- width: self.width,
- indent: Indent::new(self.indent.block_indent, alignment),
- offset: alignment,
- }
- }
-
- pub fn block_indent(&self, extra_width: usize) -> Shape {
- if self.indent.alignment == 0 {
- Shape {
- width: self.width,
- indent: Indent::new(self.indent.block_indent + extra_width, 0),
- offset: 0,
- }
- } else {
- Shape {
- width: self.width,
- indent: self.indent + extra_width,
- offset: self.indent.alignment + extra_width,
- }
- }
- }
-
- pub fn block_left(&self, width: usize) -> Option<Shape> {
- self.block_indent(width).sub_width(width)
- }
-
- pub fn add_offset(&self, extra_width: usize) -> Shape {
- Shape {
- offset: self.offset + extra_width,
- ..*self
- }
- }
-
- pub fn block(&self) -> Shape {
- Shape {
- indent: self.indent.block_only(),
- ..*self
- }
- }
-
- pub fn sub_width(&self, width: usize) -> Option<Shape> {
- Some(Shape {
- width: try_opt!(self.width.checked_sub(width)),
- ..*self
- })
- }
-
- pub fn shrink_left(&self, width: usize) -> Option<Shape> {
- Some(Shape {
- width: try_opt!(self.width.checked_sub(width)),
- indent: self.indent + width,
- offset: self.offset + width,
- })
- }
-
- pub fn offset_left(&self, width: usize) -> Option<Shape> {
- self.add_offset(width).sub_width(width)
- }
-
- pub fn used_width(&self) -> usize {
- self.indent.block_indent + self.offset
- }
-
- pub fn rhs_overhead(&self, config: &Config) -> usize {
- config
- .max_width()
- .checked_sub(self.used_width() + self.width)
- .unwrap_or(0)
- }
-}
-
+#[derive(Clone, Copy)]
pub enum ErrorKind {
// Line has exceeded character limit (found, maximum)
LineOverflow(usize, usize),
ErrorKind::LineOverflow(found, maximum) => write!(
fmt,
"line exceeded maximum width (maximum: {}, found: {})",
- maximum,
- found
+ maximum, found
),
ErrorKind::TrailingWhitespace => write!(fmt, "left behind trailing whitespace"),
ErrorKind::BadIssue(issue) => write!(fmt, "found {}", issue),
line: usize,
kind: ErrorKind,
is_comment: bool,
+ is_string: bool,
line_buffer: String,
}
}
}
- fn msg_suffix(&self) -> String {
- match self.kind {
- ErrorKind::LineOverflow(..) if self.is_comment => format!(
- "use `error_on_line_overflow_comments = false` to suppress \
- the warning against line comments\n",
- ),
- _ => String::from(""),
+ fn msg_suffix(&self) -> &str {
+ if self.is_comment || self.is_string {
+ "set `error_on_unformatted = false` to suppress \
+ the warning against comments or string literals\n"
+ } else {
+ ""
}
}
.count();
(self.line_buffer.len() - trailing_ws_len, trailing_ws_len)
}
- _ => (0, 0), // unreachable
+ _ => unreachable!(),
}
}
}
pub struct FormatReport {
// Maps stringified file paths to their associated formatting errors.
- file_error_map: HashMap<String, Vec<FormattingError>>,
+ file_error_map: HashMap<FileName, Vec<FormattingError>>,
}
impl FormatReport {
fn format_ast<F>(
krate: &ast::Crate,
parse_session: &mut ParseSess,
- main_file: &Path,
+ main_file: &FileName,
config: &Config,
mut after_file: F,
) -> Result<(FileMap, bool), io::Error>
where
- F: FnMut(&str, &mut StringBuffer) -> Result<bool, io::Error>,
+ F: FnMut(&FileName, &mut String, &[(usize, usize)]) -> Result<bool, io::Error>,
{
let mut result = FileMap::new();
// diff mode: check if any files are differing
// 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;
- for (path, module) in modules::list_files(krate, parse_session.codemap()) {
- if skip_children && path.as_path() != main_file {
+ for (path, module) in modules::list_files(krate, parse_session.codemap())? {
+ if skip_children && path != *main_file {
continue;
}
- let path_str = path.to_str().unwrap();
if config.verbose() {
- println!("Formatting {}", path_str);
- }
- let mut visitor = FmtVisitor::from_codemap(parse_session, config);
- let filemap = visitor.codemap.lookup_char_pos(module.inner.lo()).file;
+ println!("Formatting {}", path);
+ }
+ let filemap = parse_session
+ .codemap()
+ .lookup_char_pos(module.inner.lo())
+ .file;
+ let big_snippet = filemap.src.as_ref().unwrap();
+ let snippet_provider = SnippetProvider::new(filemap.start_pos, big_snippet);
+ let mut visitor = FmtVisitor::from_codemap(parse_session, config, &snippet_provider);
// Format inner attributes if available.
- if !krate.attrs.is_empty() && path == main_file {
- visitor.visit_attrs(&krate.attrs, ast::AttrStyle::Inner);
+ if !krate.attrs.is_empty() && path == *main_file {
+ visitor.skip_empty_lines(filemap.end_pos);
+ if visitor.visit_attrs(&krate.attrs, ast::AttrStyle::Inner) {
+ visitor.push_rewrite(module.inner, None);
+ } else {
+ visitor.format_separate_mod(module, &*filemap);
+ }
} else {
visitor.last_pos = filemap.start_pos;
- }
- visitor.format_separate_mod(module, &*filemap);
+ visitor.skip_empty_lines(filemap.end_pos);
+ visitor.format_separate_mod(module, &*filemap);
+ };
- has_diff |= after_file(path_str, &mut visitor.buffer)?;
+ assert_eq!(
+ visitor.line_number,
+ ::utils::count_newlines(&format!("{}", visitor.buffer))
+ );
+
+ let filename = path.clone();
+ has_diff |= match after_file(&filename, &mut visitor.buffer, &visitor.skipped_range) {
+ Ok(result) => result,
+ Err(e) => {
+ // Create a new error with path_str to help users see which files failed
+ let err_msg = format!("{}: {}", path, e);
+ return Err(io::Error::new(e.kind(), err_msg));
+ }
+ };
- result.push((path_str.to_owned(), visitor.buffer));
+ result.push((filename, visitor.buffer));
}
Ok((result, has_diff))
}
+/// 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()
+ .any(|&(lo, hi)| lo <= line_number && line_number <= hi)
+}
+
+fn should_report_error(
+ config: &Config,
+ char_kind: FullCodeCharKind,
+ is_string: bool,
+ error_kind: ErrorKind,
+) -> bool {
+ let allow_error_report = if char_kind.is_comment() || is_string {
+ config.error_on_unformatted()
+ } else {
+ true
+ };
+
+ match error_kind {
+ ErrorKind::LineOverflow(..) => config.error_on_line_overflow() && allow_error_report,
+ ErrorKind::TrailingWhitespace => allow_error_report,
+ _ => true,
+ }
+}
+
// Formatting done on a char by char or line by line basis.
// FIXME(#209) warn on bad license
// FIXME(#20) other stuff for parity with make tidy
-fn format_lines(text: &mut StringBuffer, name: &str, config: &Config, report: &mut FormatReport) {
+fn format_lines(
+ text: &mut String,
+ name: &FileName,
+ skipped_range: &[(usize, usize)],
+ config: &Config,
+ report: &mut FormatReport,
+) {
// Iterate over the chars in the file map.
let mut trims = vec![];
let mut last_wspace: Option<usize> = None;
let mut newline_count = 0;
let mut errors = vec![];
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);
+ let mut is_string = false; // true if the current line contains a string literal.
+ let mut format_line = config.file_lines().contains_line(name, cur_line);
- for (c, b) in text.chars() {
+ for (kind, (b, c)) in CharClasses::new(text.chars().enumerate()) {
if c == '\r' {
continue;
}
- let format_line = config.file_lines().contains_line(name, cur_line as usize);
-
if format_line {
// Add warnings for bad todos/ fixmes
if let Some(issue) = issue_seeker.inspect(c) {
line: cur_line,
kind: ErrorKind::BadIssue(issue),
is_comment: false,
+ is_string: false,
line_buffer: String::new(),
});
}
if c == '\n' {
if format_line {
// Check for (and record) trailing whitespace.
- if let Some(lw) = last_wspace {
- trims.push((cur_line, lw, b, line_buffer.clone()));
+ if let Some(..) = last_wspace {
+ if should_report_error(config, kind, is_string, ErrorKind::TrailingWhitespace) {
+ trims.push((cur_line, kind, line_buffer.clone()));
+ }
line_len -= 1;
}
// Check for any line width errors we couldn't correct.
- let report_error_on_line_overflow = config.error_on_line_overflow() &&
- (config.error_on_line_overflow_comments() || !is_comment);
- if report_error_on_line_overflow && line_len > config.max_width() {
+ let error_kind = ErrorKind::LineOverflow(line_len, config.max_width());
+ 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 {
line: cur_line,
- kind: ErrorKind::LineOverflow(line_len, config.max_width()),
- is_comment: is_comment,
+ kind: error_kind,
+ is_comment: kind.is_comment(),
+ is_string,
line_buffer: line_buffer.clone(),
});
}
line_len = 0;
cur_line += 1;
+ format_line = config.file_lines().contains_line(name, cur_line);
newline_count += 1;
last_wspace = None;
- prev_char = None;
- is_comment = false;
line_buffer.clear();
+ is_string = false;
} else {
newline_count = 0;
- line_len += 1;
+ line_len += if c == '\t' { config.tab_spaces() } else { 1 };
if c.is_whitespace() {
if last_wspace.is_none() {
last_wspace = Some(b);
}
- } else if c == '/' {
- match prev_char {
- Some('/') => is_comment = true,
- _ => (),
- }
- last_wspace = None;
} else {
last_wspace = None;
}
- prev_char = Some(c);
line_buffer.push(c);
+ if kind.is_string() {
+ is_string = true;
+ }
}
}
if newline_count > 1 {
- debug!("track truncate: {} {}", text.len, newline_count);
- let line = text.len - newline_count + 1;
+ debug!("track truncate: {} {}", text.len(), newline_count);
+ let line = text.len() - newline_count + 1;
text.truncate(line);
}
- for &(l, _, _, ref b) in &trims {
- errors.push(FormattingError {
- line: l,
- kind: ErrorKind::TrailingWhitespace,
- is_comment: false,
- line_buffer: b.clone(),
- });
+ for &(l, kind, ref b) in &trims {
+ if !is_skipped_line(l, skipped_range) {
+ errors.push(FormattingError {
+ line: l,
+ kind: ErrorKind::TrailingWhitespace,
+ is_comment: kind.is_comment(),
+ is_string: kind.is_string(),
+ line_buffer: b.clone(),
+ });
+ }
}
- report.file_error_map.insert(name.to_owned(), errors);
+ report.file_error_map.insert(name.clone(), errors);
}
fn parse_input(
parser.parse_crate_mod()
}
Input::Text(text) => {
- let mut parser =
- parse::new_parser_from_source_str(parse_session, "stdin".to_owned(), text);
+ let mut parser = parse::new_parser_from_source_str(
+ parse_session,
+ FileName::Custom("stdin".to_owned()),
+ text,
+ );
parser.cfg_mods = false;
parser.parse_crate_mod()
}
}
}
+/// 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().hide_parse_errors(true);
+ match format_input(input, &config, Some(&mut out)) {
+ // `format_input()` returns an empty string on parsing error.
+ Ok(..) if out.is_empty() && !snippet.is_empty() => None,
+ Ok(..) => String::from_utf8(out).ok(),
+ Err(..) => None,
+ }
+}
+
+/// 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}";
+
+ // Trim "fn main() {" on the first line and "}" on the last line,
+ // then unindent the whole code block.
+ format_snippet(&snippet, config).map(|s| {
+ // 2 = "}\n"
+ s[fn_main_prefix.len()..s.len().checked_sub(2).unwrap_or(0)]
+ .lines()
+ .map(|line| {
+ if line.len() > config.tab_spaces() {
+ // Make sure that the line has leading whitespaces.
+ let indent_str =
+ Indent::from_width(config, config.tab_spaces()).to_string(config);
+ if line.starts_with(indent_str.as_ref()) {
+ &line[config.tab_spaces()..]
+ } else {
+ line
+ }
+ } else {
+ line
+ }
+ })
+ .collect::<Vec<_>>()
+ .join("\n")
+ })
+}
+
pub fn format_input<T: Write>(
input: Input,
config: &Config,
mut out: Option<&mut T>,
) -> Result<(Summary, FileMap, FormatReport), (io::Error, Summary)> {
- let mut summary = Summary::new();
+ let mut summary = Summary::default();
if config.disable_all_formatting() {
+ // When the input is from stdin, echo back the input.
+ if let Input::Text(ref buf) = input {
+ if let Err(e) = io::stdout().write_all(buf.as_bytes()) {
+ return Err((e, summary));
+ }
+ }
return Ok((summary, FileMap::new(), FormatReport::new()));
}
let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
- let tty_handler =
- Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(codemap.clone()));
+ let tty_handler = if config.hide_parse_errors() {
+ let silent_emitter = Box::new(EmitterWriter::new(
+ Box::new(Vec::new()),
+ Some(codemap.clone()),
+ false,
+ ));
+ Handler::with_emitter(true, false, silent_emitter)
+ } else {
+ Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(codemap.clone()))
+ };
let mut parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone());
let main_file = match input {
- Input::File(ref file) => file.clone(),
- Input::Text(..) => PathBuf::from("stdin"),
+ Input::File(ref file) => FileName::Real(file.clone()),
+ Input::Text(..) => FileName::Custom("stdin".to_owned()),
};
let krate = match parse_input(input, &parse_session) {
}
};
+ summary.mark_parse_time();
+
if parse_session.span_diagnostic.has_errors() {
summary.add_parsing_error();
}
let silent_emitter = Box::new(EmitterWriter::new(
Box::new(Vec::new()),
Some(codemap.clone()),
+ false,
));
parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
let mut report = FormatReport::new();
- match format_ast(
+ let format_result = format_ast(
&krate,
&mut parse_session,
&main_file,
config,
- |file_name, file| {
+ |file_name, file, skipped_range| {
// For some reason, the codemap does not include terminating
// newlines so we must add one on for each file. This is sad.
filemap::append_newline(file);
- format_lines(file, file_name, config, &mut report);
+ format_lines(file, file_name, skipped_range, config, &mut report);
if let Some(ref mut out) = out {
return filemap::write_file(file, file_name, out, config);
}
Ok(false)
},
- ) {
+ );
+
+ summary.mark_format_time();
+
+ if config.verbose() {
+ fn duration_to_f32(d: Duration) -> f32 {
+ d.as_secs() as f32 + d.subsec_nanos() as f32 / 1_000_000_000f32
+ }
+
+ println!(
+ "Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase",
+ duration_to_f32(summary.get_parse_time().unwrap()),
+ duration_to_f32(summary.get_format_time().unwrap()),
+ );
+ }
+
+ match format_result {
Ok((file_map, has_diff)) => {
if report.has_warnings() {
summary.add_formatting_error();
if report.has_warnings() {
match term::stderr() {
- Some(ref t) if isatty() && t.supports_color() => {
+ Some(ref t)
+ if use_colored_tty(config.color()) && t.supports_color()
+ && t.supports_attr(term::Attr::Bold) =>
+ {
match report.print_warnings_fancy(term::stderr().unwrap()) {
Ok(..) => (),
Err(..) => panic!("Unable to write to stderr: {}", report),
#[cfg(test)]
mod test {
- use super::*;
-
- #[test]
- fn indent_add_sub() {
- let indent = Indent::new(4, 8) + Indent::new(8, 12);
- assert_eq!(12, indent.block_indent);
- assert_eq!(20, indent.alignment);
-
- let indent = indent - Indent::new(4, 4);
- assert_eq!(8, indent.block_indent);
- assert_eq!(16, indent.alignment);
- }
+ use super::{format_code_block, format_snippet, Config};
#[test]
- fn indent_add_sub_alignment() {
- let indent = Indent::new(4, 8) + 4;
- assert_eq!(4, indent.block_indent);
- assert_eq!(12, indent.alignment);
-
- let indent = indent - 4;
- assert_eq!(4, indent.block_indent);
- assert_eq!(8, indent.alignment);
+ fn test_no_panic_on_format_snippet_and_format_code_block() {
+ // `format_snippet()` and `format_code_block()` should not panic
+ // even when we cannot parse the given snippet.
+ let snippet = "let";
+ assert!(format_snippet(snippet, &Config::default()).is_none());
+ assert!(format_code_block(snippet, &Config::default()).is_none());
}
- #[test]
- fn indent_to_string_spaces() {
- let config = Config::default();
- let indent = Indent::new(4, 8);
-
- // 12 spaces
- assert_eq!(" ", indent.to_string(&config));
- }
-
- #[test]
- fn indent_to_string_hard_tabs() {
- let mut config = Config::default();
- config.set().hard_tabs(true);
- let indent = Indent::new(8, 4);
-
- // 2 tabs + 4 spaces
- assert_eq!("\t\t ", indent.to_string(&config));
- }
-
- #[test]
- fn shape_visual_indent() {
- let config = Config::default();
- let indent = Indent::new(4, 8);
- let shape = Shape::legacy(config.max_width(), indent);
- let shape = shape.visual_indent(20);
-
- assert_eq!(config.max_width(), shape.width);
- assert_eq!(4, shape.indent.block_indent);
- assert_eq!(28, shape.indent.alignment);
- assert_eq!(28, shape.offset);
+ fn test_format_inner<F>(formatter: F, input: &str, expected: &str) -> bool
+ where
+ F: Fn(&str, &Config) -> Option<String>,
+ {
+ let output = formatter(input, &Config::default());
+ output.is_some() && output.unwrap() == expected
}
#[test]
- fn shape_block_indent_without_alignment() {
- let config = Config::default();
- let indent = Indent::new(4, 0);
- let shape = Shape::legacy(config.max_width(), indent);
- let shape = shape.block_indent(20);
-
- assert_eq!(config.max_width(), shape.width);
- assert_eq!(24, shape.indent.block_indent);
- assert_eq!(0, shape.indent.alignment);
- assert_eq!(0, shape.offset);
+ fn test_format_snippet() {
+ let snippet = "fn main() { println!(\"hello, world\"); }";
+ let expected = "fn main() {\n \
+ println!(\"hello, world\");\n\
+ }\n";
+ assert!(test_format_inner(format_snippet, snippet, expected));
}
#[test]
- fn shape_block_indent_with_alignment() {
- let config = Config::default();
- let indent = Indent::new(4, 8);
- let shape = Shape::legacy(config.max_width(), indent);
- let shape = shape.block_indent(20);
-
- assert_eq!(config.max_width(), shape.width);
- assert_eq!(4, shape.indent.block_indent);
- assert_eq!(28, shape.indent.alignment);
- assert_eq!(28, shape.offset);
+ fn test_format_code_block() {
+ // simple code block
+ let code_block = "let x=3;";
+ let expected = "let x = 3;";
+ assert!(test_format_inner(format_code_block, code_block, expected));
+
+ // more complex code block, taken from chains.rs.
+ let code_block =
+"let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) {
+(
+chain_indent(context, shape.add_offset(parent_rewrite.len())),
+context.config.indent_style() == IndentStyle::Visual || is_small_parent,
+)
+} else if is_block_expr(context, &parent, &parent_rewrite) {
+match context.config.indent_style() {
+// Try to put the first child on the same line with parent's last line
+IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true),
+// The parent is a block, so align the rest of the chain with the closing
+// brace.
+IndentStyle::Visual => (parent_shape, false),
+}
+} else {
+(
+chain_indent(context, shape.add_offset(parent_rewrite.len())),
+false,
+)
+};
+";
+ let expected =
+"let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) {
+ (
+ chain_indent(context, shape.add_offset(parent_rewrite.len())),
+ context.config.indent_style() == IndentStyle::Visual || is_small_parent,
+ )
+} else if is_block_expr(context, &parent, &parent_rewrite) {
+ match context.config.indent_style() {
+ // Try to put the first child on the same line with parent's last line
+ IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true),
+ // The parent is a block, so align the rest of the chain with the closing
+ // brace.
+ IndentStyle::Visual => (parent_shape, false),
+ }
+} else {
+ (
+ chain_indent(context, shape.add_offset(parent_rewrite.len())),
+ false,
+ )
+};";
+ assert!(test_format_inner(format_code_block, code_block, expected));
}
}