diff options
| author | topecongiro <seuchida@gmail.com> | 2018-02-07 22:48:05 +0900 |
|---|---|---|
| committer | topecongiro <seuchida@gmail.com> | 2018-02-07 22:48:05 +0900 |
| commit | 4af2aa3a9e2ab32e584c3d7bc97d74bdc8b35836 (patch) | |
| tree | ab74c699216d5b6bcafb21dcf6b09a2878efa05d /src | |
| parent | c9e250a1ab4983875cba2dcd5f083a6694d70e8f (diff) | |
Create rustfmt_core crate
Diffstat (limited to 'src')
| -rw-r--r-- | src/chains.rs | 499 | ||||
| -rw-r--r-- | src/checkstyle.rs | 85 | ||||
| -rw-r--r-- | src/closures.rs | 392 | ||||
| -rw-r--r-- | src/codemap.rs | 100 | ||||
| -rw-r--r-- | src/comment.rs | 1234 | ||||
| -rw-r--r-- | src/expr.rs | 2930 | ||||
| -rw-r--r-- | src/filemap.rs | 179 | ||||
| -rw-r--r-- | src/imports.rs | 600 | ||||
| -rw-r--r-- | src/issues.rs | 325 | ||||
| -rw-r--r-- | src/items.rs | 2845 | ||||
| -rw-r--r-- | src/lib.rs | 828 | ||||
| -rw-r--r-- | src/lists.rs | 857 | ||||
| -rw-r--r-- | src/macros.rs | 871 | ||||
| -rw-r--r-- | src/missed_spans.rs | 312 | ||||
| -rw-r--r-- | src/modules.rs | 86 | ||||
| -rw-r--r-- | src/patterns.rs | 379 | ||||
| -rw-r--r-- | src/rewrite.rs | 54 | ||||
| -rw-r--r-- | src/rustfmt_diff.rs | 247 | ||||
| -rw-r--r-- | src/shape.rs | 353 | ||||
| -rw-r--r-- | src/spanned.rs | 187 | ||||
| -rw-r--r-- | src/string.rs | 163 | ||||
| -rw-r--r-- | src/types.rs | 820 | ||||
| -rw-r--r-- | src/utils.rs | 480 | ||||
| -rw-r--r-- | src/vertical.rs | 290 | ||||
| -rw-r--r-- | src/visitor.rs | 1113 |
25 files changed, 0 insertions, 16229 deletions
diff --git a/src/chains.rs b/src/chains.rs deleted file mode 100644 index 383e7077d66..00000000000 --- a/src/chains.rs +++ /dev/null @@ -1,499 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Formatting of chained expressions, i.e. expressions which are chained by -//! dots: struct and enum field access, method calls, and try shorthand (?). -//! -//! Instead of walking these subexpressions one-by-one, as is our usual strategy -//! for expression formatting, we collect maximal sequences of these expressions -//! and handle them simultaneously. -//! -//! Whenever possible, the entire chain is put on a single line. If that fails, -//! we put each subexpression on a separate, much like the (default) function -//! argument function argument strategy. -//! -//! Depends on config options: `chain_indent` is the indent to use for -//! blocks in the parent/root/base of the chain (and the rest of the chain's -//! alignment). -//! E.g., `let foo = { aaaa; bbb; ccc }.bar.baz();`, we would layout for the -//! following values of `chain_indent`: -//! Block: -//! ``` -//! let foo = { -//! aaaa; -//! bbb; -//! ccc -//! }.bar -//! .baz(); -//! ``` -//! Visual: -//! ``` -//! let foo = { -//! aaaa; -//! bbb; -//! ccc -//! } -//! .bar -//! .baz(); -//! ``` -//! -//! If the first item in the chain is a block expression, we align the dots with -//! the braces. -//! Block: -//! ``` -//! let a = foo.bar -//! .baz() -//! .qux -//! ``` -//! Visual: -//! ``` -//! let a = foo.bar -//! .baz() -//! .qux -//! ``` - -use config::IndentStyle; -use expr::rewrite_call; -use macros::convert_try_mac; -use rewrite::{Rewrite, RewriteContext}; -use shape::Shape; -use utils::{first_line_width, last_line_extendable, last_line_width, mk_sp, - trimmed_last_line_width, wrap_str}; - -use std::cmp::min; -use std::iter; -use syntax::{ast, ptr}; -use syntax::codemap::Span; - -pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) -> Option<String> { - debug!("rewrite_chain {:?}", shape); - let total_span = expr.span; - let (parent, subexpr_list) = make_subexpr_list(expr, context); - - // Bail out if the chain is just try sugar, i.e., an expression followed by - // any number of `?`s. - if chain_only_try(&subexpr_list) { - return rewrite_try(&parent, subexpr_list.len(), context, shape); - } - let suffix_try_num = subexpr_list.iter().take_while(|e| is_try(e)).count(); - let prefix_try_num = subexpr_list.iter().rev().take_while(|e| is_try(e)).count(); - - // Parent is the first item in the chain, e.g., `foo` in `foo.bar.baz()`. - let parent_shape = if is_block_expr(context, &parent, "\n") { - match context.config.indent_style() { - IndentStyle::Visual => shape.visual_indent(0), - IndentStyle::Block => shape, - } - } else { - shape - }; - let parent_rewrite = parent - .rewrite(context, parent_shape) - .map(|parent_rw| parent_rw + &repeat_try(prefix_try_num))?; - let parent_rewrite_contains_newline = parent_rewrite.contains('\n'); - let is_small_parent = parent_rewrite.len() <= context.config.tab_spaces(); - - // Decide how to layout the rest of the chain. `extend` is true if we can - // put the first non-parent item on the same line as the parent. - 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 other_child_shape = nested_shape.with_max_width(context.config); - - let first_child_shape = if extend { - let overhead = last_line_width(&parent_rewrite); - let offset = trimmed_last_line_width(&parent_rewrite) + prefix_try_num; - match context.config.indent_style() { - IndentStyle::Visual => parent_shape.offset_left(overhead)?, - IndentStyle::Block => parent_shape.offset_left(offset)?, - } - } else { - other_child_shape - }; - debug!( - "child_shapes {:?} {:?}", - first_child_shape, other_child_shape - ); - - let child_shape_iter = Some(first_child_shape) - .into_iter() - .chain(iter::repeat(other_child_shape)); - let subexpr_num = subexpr_list.len(); - let last_subexpr = &subexpr_list[suffix_try_num]; - let subexpr_list = &subexpr_list[suffix_try_num..subexpr_num - prefix_try_num]; - let iter = subexpr_list.iter().skip(1).rev().zip(child_shape_iter); - let mut rewrites = iter.map(|(e, shape)| rewrite_chain_subexpr(e, total_span, context, shape)) - .collect::<Option<Vec<_>>>()?; - - // Total of all items excluding the last. - let extend_last_subexpr = if is_small_parent { - rewrites.len() == 1 && last_line_extendable(&rewrites[0]) - } else { - rewrites.is_empty() && last_line_extendable(&parent_rewrite) - }; - let almost_total = if extend_last_subexpr { - last_line_width(&parent_rewrite) - } else { - rewrites.iter().fold(0, |a, b| a + b.len()) + parent_rewrite.len() - } + suffix_try_num; - let one_line_budget = if rewrites.is_empty() { - shape.width - } else { - min(shape.width, context.config.width_heuristics().chain_width) - }; - let all_in_one_line = !parent_rewrite_contains_newline - && rewrites.iter().all(|s| !s.contains('\n')) - && almost_total < one_line_budget; - let last_shape = if rewrites.is_empty() { - first_child_shape - } else { - other_child_shape - }.sub_width(shape.rhs_overhead(context.config) + suffix_try_num)?; - - // Rewrite the last child. The last child of a chain requires special treatment. We need to - // know whether 'overflowing' the last child make a better formatting: - // - // A chain with overflowing the last child: - // ``` - // parent.child1.child2.last_child( - // a, - // b, - // c, - // ) - // ``` - // - // A chain without overflowing the last child (in vertical layout): - // ``` - // parent - // .child1 - // .child2 - // .last_child(a, b, c) - // ``` - // - // In particular, overflowing is effective when the last child is a method with a multi-lined - // block-like argument (e.g. closure): - // ``` - // parent.child1.child2.last_child(|a, b, c| { - // let x = foo(a, b, c); - // let y = bar(a, b, c); - // - // // ... - // - // result - // }) - // ``` - - // `rewrite_last` rewrites the last child on its own line. We use a closure here instead of - // directly calling `rewrite_chain_subexpr()` to avoid exponential blowup. - let rewrite_last = || rewrite_chain_subexpr(last_subexpr, total_span, context, last_shape); - let (last_subexpr_str, fits_single_line) = if all_in_one_line || extend_last_subexpr { - // First we try to 'overflow' the last child and see if it looks better than using - // vertical layout. - parent_shape.offset_left(almost_total).map(|shape| { - if let Some(rw) = rewrite_chain_subexpr(last_subexpr, total_span, context, shape) { - // We allow overflowing here only if both of the following conditions match: - // 1. The entire chain fits in a single line expect the last child. - // 2. `last_child_str.lines().count() >= 5`. - let line_count = rw.lines().count(); - let fits_single_line = almost_total + first_line_width(&rw) <= one_line_budget; - if fits_single_line && line_count >= 5 { - (Some(rw), true) - } else { - // We could not know whether overflowing is better than using vertical layout, - // just by looking at the overflowed rewrite. Now we rewrite the last child - // on its own line, and compare two rewrites to choose which is better. - match rewrite_last() { - Some(ref new_rw) if !fits_single_line => (Some(new_rw.clone()), false), - Some(ref new_rw) if new_rw.lines().count() >= line_count => { - (Some(rw), fits_single_line) - } - new_rw @ Some(..) => (new_rw, false), - _ => (Some(rw), fits_single_line), - } - } - } else { - (rewrite_last(), false) - } - })? - } else { - (rewrite_last(), false) - }; - rewrites.push(last_subexpr_str?); - - let connector = if fits_single_line && !parent_rewrite_contains_newline { - // Yay, we can put everything on one line. - String::new() - } else { - // Use new lines. - if context.force_one_line_chain { - return None; - } - format!("\n{}", nested_shape.indent.to_string(context.config)) - }; - - let first_connector = if is_small_parent || fits_single_line - || last_line_extendable(&parent_rewrite) - || context.config.indent_style() == IndentStyle::Visual - { - "" - } else { - connector.as_str() - }; - - let result = if is_small_parent && rewrites.len() > 1 { - let second_connector = if fits_single_line || rewrites[1] == "?" - || last_line_extendable(&rewrites[0]) - || context.config.indent_style() == IndentStyle::Visual - { - "" - } else { - &connector - }; - format!( - "{}{}{}{}{}", - parent_rewrite, - first_connector, - rewrites[0], - second_connector, - join_rewrites(&rewrites[1..], &connector) - ) - } else { - format!( - "{}{}{}", - parent_rewrite, - first_connector, - join_rewrites(&rewrites, &connector) - ) - }; - let result = format!("{}{}", result, repeat_try(suffix_try_num)); - if context.config.indent_style() == IndentStyle::Visual { - wrap_str(result, context.config.max_width(), shape) - } else { - Some(result) - } -} - -// True if the chain is only `?`s. -fn chain_only_try(exprs: &[ast::Expr]) -> bool { - exprs.iter().all(|e| { - if let ast::ExprKind::Try(_) = e.node { - true - } else { - false - } - }) -} - -// Try to rewrite and replace the last non-try child. Return `true` if -// replacing succeeds. -fn repeat_try(try_count: usize) -> String { - iter::repeat("?").take(try_count).collect::<String>() -} - -fn rewrite_try( - expr: &ast::Expr, - try_count: usize, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - let sub_expr = expr.rewrite(context, shape.sub_width(try_count)?)?; - Some(format!("{}{}", sub_expr, repeat_try(try_count))) -} - -fn join_rewrites(rewrites: &[String], connector: &str) -> String { - let mut rewrite_iter = rewrites.iter(); - let mut result = rewrite_iter.next().unwrap().clone(); - - for rewrite in rewrite_iter { - if rewrite != "?" { - result.push_str(connector); - } - result.push_str(&rewrite[..]); - } - - result -} - -// States whether an expression's last line exclusively consists of closing -// parens, braces, and brackets in its idiomatic formatting. -fn is_block_expr(context: &RewriteContext, expr: &ast::Expr, repr: &str) -> bool { - match expr.node { - ast::ExprKind::Mac(..) | ast::ExprKind::Call(..) => { - context.use_block_indent() && repr.contains('\n') - } - ast::ExprKind::Struct(..) - | ast::ExprKind::While(..) - | ast::ExprKind::WhileLet(..) - | ast::ExprKind::If(..) - | ast::ExprKind::IfLet(..) - | ast::ExprKind::Block(..) - | ast::ExprKind::Loop(..) - | ast::ExprKind::ForLoop(..) - | ast::ExprKind::Match(..) => repr.contains('\n'), - ast::ExprKind::Paren(ref expr) - | ast::ExprKind::Binary(_, _, ref expr) - | ast::ExprKind::Index(_, ref expr) - | ast::ExprKind::Unary(_, ref expr) => is_block_expr(context, expr, repr), - _ => false, - } -} - -// Returns the root of the chain and a Vec of the prefixes of the rest of the chain. -// E.g., for input `a.b.c` we return (`a`, [`a.b.c`, `a.b`]) -fn make_subexpr_list(expr: &ast::Expr, context: &RewriteContext) -> (ast::Expr, Vec<ast::Expr>) { - let mut subexpr_list = vec![expr.clone()]; - - while let Some(subexpr) = pop_expr_chain(subexpr_list.last().unwrap(), context) { - subexpr_list.push(subexpr.clone()); - } - - let parent = subexpr_list.pop().unwrap(); - (parent, subexpr_list) -} - -fn chain_indent(context: &RewriteContext, shape: Shape) -> Shape { - match context.config.indent_style() { - IndentStyle::Visual => shape.visual_indent(0), - IndentStyle::Block => shape - .block_indent(context.config.tab_spaces()) - .with_max_width(context.config), - } -} - -// Returns the expression's subexpression, if it exists. When the subexpr -// is a try! macro, we'll convert it to shorthand when the option is set. -fn pop_expr_chain(expr: &ast::Expr, context: &RewriteContext) -> Option<ast::Expr> { - match expr.node { - ast::ExprKind::MethodCall(_, ref expressions) => { - Some(convert_try(&expressions[0], context)) - } - ast::ExprKind::TupField(ref subexpr, _) - | ast::ExprKind::Field(ref subexpr, _) - | ast::ExprKind::Try(ref subexpr) => Some(convert_try(subexpr, context)), - _ => None, - } -} - -fn convert_try(expr: &ast::Expr, context: &RewriteContext) -> ast::Expr { - match expr.node { - ast::ExprKind::Mac(ref mac) if context.config.use_try_shorthand() => { - if let Some(subexpr) = convert_try_mac(mac, context) { - subexpr - } else { - expr.clone() - } - } - _ => expr.clone(), - } -} - -// Rewrite the last element in the chain `expr`. E.g., given `a.b.c` we rewrite -// `.c`. -fn rewrite_chain_subexpr( - expr: &ast::Expr, - span: Span, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - let rewrite_element = |expr_str: String| { - if expr_str.len() <= shape.width { - Some(expr_str) - } else { - None - } - }; - - match expr.node { - ast::ExprKind::MethodCall(ref segment, ref expressions) => { - let types = match segment.parameters { - Some(ref params) => match **params { - ast::PathParameters::AngleBracketed(ref data) => &data.types[..], - _ => &[], - }, - _ => &[], - }; - rewrite_method_call(segment.identifier, types, expressions, span, context, shape) - } - ast::ExprKind::Field(_, ref field) => rewrite_element(format!(".{}", field.node)), - ast::ExprKind::TupField(ref expr, ref field) => { - let space = match expr.node { - ast::ExprKind::TupField(..) => " ", - _ => "", - }; - rewrite_element(format!("{}.{}", space, field.node)) - } - ast::ExprKind::Try(_) => rewrite_element(String::from("?")), - _ => unreachable!(), - } -} - -// Determines if we can continue formatting a given expression on the same line. -fn is_continuable(expr: &ast::Expr) -> bool { - match expr.node { - ast::ExprKind::Path(..) => true, - _ => false, - } -} - -fn is_try(expr: &ast::Expr) -> bool { - match expr.node { - ast::ExprKind::Try(..) => true, - _ => false, - } -} - -fn rewrite_method_call( - method_name: ast::Ident, - types: &[ptr::P<ast::Ty>], - args: &[ptr::P<ast::Expr>], - span: Span, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - let (lo, type_str) = if types.is_empty() { - (args[0].span.hi(), String::new()) - } else { - let type_list = types - .iter() - .map(|ty| ty.rewrite(context, shape)) - .collect::<Option<Vec<_>>>()?; - - let type_str = - if context.config.spaces_within_parens_and_brackets() && !type_list.is_empty() { - format!("::< {} >", type_list.join(", ")) - } else { - format!("::<{}>", type_list.join(", ")) - }; - - (types.last().unwrap().span.hi(), type_str) - }; - - let callee_str = format!(".{}{}", method_name, type_str); - let span = mk_sp(lo, span.hi()); - - rewrite_call(context, &callee_str, &args[1..], span, shape) -} diff --git a/src/checkstyle.rs b/src/checkstyle.rs deleted file mode 100644 index 7f6e650ad22..00000000000 --- a/src/checkstyle.rs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::io::{self, Write}; -use std::path::Path; - -use config::WriteMode; -use rustfmt_diff::{DiffLine, Mismatch}; - -pub fn output_header<T>(out: &mut T, mode: WriteMode) -> Result<(), io::Error> -where - T: Write, -{ - if mode == WriteMode::Checkstyle { - let mut xml_heading = String::new(); - xml_heading.push_str("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); - xml_heading.push_str("\n"); - xml_heading.push_str("<checkstyle version=\"4.3\">"); - write!(out, "{}", xml_heading)?; - } - Ok(()) -} - -pub fn output_footer<T>(out: &mut T, mode: WriteMode) -> Result<(), io::Error> -where - T: Write, -{ - if mode == WriteMode::Checkstyle { - let mut xml_tail = String::new(); - xml_tail.push_str("</checkstyle>\n"); - write!(out, "{}", xml_tail)?; - } - Ok(()) -} - -pub fn output_checkstyle_file<T>( - mut writer: T, - filename: &Path, - diff: Vec<Mismatch>, -) -> Result<(), io::Error> -where - T: Write, -{ - write!(writer, "<file name=\"{}\">", filename.display())?; - for mismatch in diff { - for line in mismatch.lines { - // Do nothing with `DiffLine::Context` and `DiffLine::Resulting`. - if let DiffLine::Expected(ref str) = line { - let message = xml_escape_str(str); - write!( - writer, - "<error line=\"{}\" severity=\"warning\" message=\"Should be `{}`\" \ - />", - mismatch.line_number, message - )?; - } - } - } - write!(writer, "</file>")?; - Ok(()) -} - -// Convert special characters into XML entities. -// This is needed for checkstyle output. -fn xml_escape_str(string: &str) -> String { - let mut out = String::new(); - for c in string.chars() { - match c { - '<' => out.push_str("<"), - '>' => out.push_str(">"), - '"' => out.push_str("""), - '\'' => out.push_str("'"), - '&' => out.push_str("&"), - _ => out.push(c), - } - } - out -} diff --git a/src/closures.rs b/src/closures.rs deleted file mode 100644 index 61287543da9..00000000000 --- a/src/closures.rs +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright 2017 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use syntax::{ast, ptr}; -use syntax::codemap::Span; -use syntax::parse::classify; - -use codemap::SpanUtils; -use expr::{block_contains_comment, is_simple_block, is_unsafe_block, rewrite_cond, ToExpr}; -use items::{span_hi_for_arg, span_lo_for_arg}; -use lists::{definitive_tactic, itemize_list, write_list, DefinitiveListTactic, ListFormatting, - ListTactic, Separator, SeparatorPlace, SeparatorTactic}; -use rewrite::{Rewrite, RewriteContext}; -use shape::Shape; -use utils::{last_line_width, left_most_sub_expr, stmt_expr}; - -// This module is pretty messy because of the rules around closures and blocks: -// FIXME - the below is probably no longer true in full. -// * if there is a return type, then there must be braces, -// * given a closure with braces, whether that is parsed to give an inner block -// or not depends on if there is a return type and if there are statements -// in that block, -// * if the first expression in the body ends with a block (i.e., is a -// statement without needing a semi-colon), then adding or removing braces -// can change whether it is treated as an expression or statement. - -pub fn rewrite_closure( - capture: ast::CaptureBy, - movability: ast::Movability, - fn_decl: &ast::FnDecl, - body: &ast::Expr, - span: Span, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - debug!("rewrite_closure {:?}", body); - - let (prefix, extra_offset) = - rewrite_closure_fn_decl(capture, movability, fn_decl, body, span, context, shape)?; - // 1 = space between `|...|` and body. - let body_shape = shape.offset_left(extra_offset)?; - - if let ast::ExprKind::Block(ref block) = body.node { - // The body of the closure is an empty block. - if block.stmts.is_empty() && !block_contains_comment(block, context.codemap) { - return Some(format!("{} {{}}", prefix)); - } - - let result = match fn_decl.output { - ast::FunctionRetTy::Default(_) => { - try_rewrite_without_block(body, &prefix, context, shape, body_shape) - } - _ => None, - }; - - result.or_else(|| { - // Either we require a block, or tried without and failed. - rewrite_closure_block(block, &prefix, context, body_shape) - }) - } else { - rewrite_closure_expr(body, &prefix, context, body_shape).or_else(|| { - // The closure originally had a non-block expression, but we can't fit on - // one line, so we'll insert a block. - rewrite_closure_with_block(body, &prefix, context, body_shape) - }) - } -} - -fn try_rewrite_without_block( - expr: &ast::Expr, - prefix: &str, - context: &RewriteContext, - shape: Shape, - body_shape: Shape, -) -> Option<String> { - let expr = get_inner_expr(expr, prefix, context); - - if is_block_closure_forced(context, expr) { - rewrite_closure_with_block(expr, prefix, context, shape) - } else { - rewrite_closure_expr(expr, prefix, context, body_shape) - } -} - -fn get_inner_expr<'a>( - expr: &'a ast::Expr, - prefix: &str, - context: &RewriteContext, -) -> &'a ast::Expr { - if let ast::ExprKind::Block(ref block) = expr.node { - if !needs_block(block, prefix, context) { - // block.stmts.len() == 1 - if let Some(expr) = stmt_expr(&block.stmts[0]) { - return get_inner_expr(expr, prefix, context); - } - } - } - - expr -} - -// Figure out if a block is necessary. -fn needs_block(block: &ast::Block, prefix: &str, context: &RewriteContext) -> bool { - is_unsafe_block(block) || block.stmts.len() > 1 - || block_contains_comment(block, context.codemap) || prefix.contains('\n') -} - -// Rewrite closure with a single expression wrapping its body with block. -fn rewrite_closure_with_block( - body: &ast::Expr, - prefix: &str, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - let left_most = left_most_sub_expr(body); - let veto_block = left_most != body && !classify::expr_requires_semi_to_be_stmt(left_most); - if veto_block { - return None; - } - - let block = ast::Block { - stmts: vec![ - ast::Stmt { - id: ast::NodeId::new(0), - node: ast::StmtKind::Expr(ptr::P(body.clone())), - span: body.span, - }, - ], - id: ast::NodeId::new(0), - rules: ast::BlockCheckMode::Default, - span: body.span, - recovered: false, - }; - let block = ::expr::rewrite_block_with_visitor(context, "", &block, shape, false)?; - Some(format!("{} {}", prefix, block)) -} - -// Rewrite closure with a single expression without wrapping its body with block. -fn rewrite_closure_expr( - expr: &ast::Expr, - prefix: &str, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - fn allow_multi_line(expr: &ast::Expr) -> bool { - match expr.node { - ast::ExprKind::Match(..) - | ast::ExprKind::Block(..) - | ast::ExprKind::Catch(..) - | ast::ExprKind::Loop(..) - | ast::ExprKind::Struct(..) => true, - - ast::ExprKind::AddrOf(_, ref expr) - | ast::ExprKind::Box(ref expr) - | ast::ExprKind::Try(ref expr) - | ast::ExprKind::Unary(_, ref expr) - | ast::ExprKind::Cast(ref expr, _) => allow_multi_line(expr), - - _ => false, - } - } - - // When rewriting closure's body without block, we require it to fit in a single line - // unless it is a block-like expression or we are inside macro call. - let veto_multiline = (!allow_multi_line(expr) && !context.inside_macro) - || context.config.force_multiline_blocks(); - expr.rewrite(context, shape) - .and_then(|rw| { - if veto_multiline && rw.contains('\n') { - None - } else { - Some(rw) - } - }) - .map(|rw| format!("{} {}", prefix, rw)) -} - -// Rewrite closure whose body is block. -fn rewrite_closure_block( - block: &ast::Block, - prefix: &str, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - Some(format!("{} {}", prefix, block.rewrite(context, shape)?)) -} - -// Return type is (prefix, extra_offset) -fn rewrite_closure_fn_decl( - capture: ast::CaptureBy, - movability: ast::Movability, - fn_decl: &ast::FnDecl, - body: &ast::Expr, - span: Span, - context: &RewriteContext, - shape: Shape, -) -> Option<(String, usize)> { - let mover = if capture == ast::CaptureBy::Value { - "move " - } else { - "" - }; - - let immovable = if movability == ast::Movability::Static { - "static " - } else { - "" - }; - // 4 = "|| {".len(), which is overconservative when the closure consists of - // a single expression. - let nested_shape = shape - .shrink_left(mover.len() + immovable.len())? - .sub_width(4)?; - - // 1 = | - let argument_offset = nested_shape.indent + 1; - let arg_shape = nested_shape.offset_left(1)?.visual_indent(0); - let ret_str = fn_decl.output.rewrite(context, arg_shape)?; - - let arg_items = itemize_list( - context.codemap, - fn_decl.inputs.iter(), - "|", - ",", - |arg| span_lo_for_arg(arg), - |arg| span_hi_for_arg(context, arg), - |arg| arg.rewrite(context, arg_shape), - context.codemap.span_after(span, "|"), - body.span.lo(), - false, - ); - let item_vec = arg_items.collect::<Vec<_>>(); - // 1 = space between arguments and return type. - let horizontal_budget = nested_shape - .width - .checked_sub(ret_str.len() + 1) - .unwrap_or(0); - let tactic = definitive_tactic( - &item_vec, - ListTactic::HorizontalVertical, - Separator::Comma, - horizontal_budget, - ); - let arg_shape = match tactic { - DefinitiveListTactic::Horizontal => arg_shape.sub_width(ret_str.len() + 1)?, - _ => arg_shape, - }; - - let fmt = ListFormatting { - tactic, - separator: ",", - trailing_separator: SeparatorTactic::Never, - separator_place: SeparatorPlace::Back, - shape: arg_shape, - ends_with_newline: false, - preserve_newline: true, - config: context.config, - }; - let list_str = write_list(&item_vec, &fmt)?; - let mut prefix = format!("{}{}|{}|", immovable, mover, list_str); - - if !ret_str.is_empty() { - if prefix.contains('\n') { - prefix.push('\n'); - prefix.push_str(&argument_offset.to_string(context.config)); - } else { - prefix.push(' '); - } - prefix.push_str(&ret_str); - } - // 1 = space between `|...|` and body. - let extra_offset = last_line_width(&prefix) + 1; - - Some((prefix, extra_offset)) -} - -// Rewriting closure which is placed at the end of the function call's arg. -// Returns `None` if the reformatted closure 'looks bad'. -pub fn rewrite_last_closure( - context: &RewriteContext, - expr: &ast::Expr, - shape: Shape, -) -> Option<String> { - if let ast::ExprKind::Closure(capture, movability, ref fn_decl, ref body, _) = expr.node { - let body = match body.node { - ast::ExprKind::Block(ref block) - if !is_unsafe_block(block) && is_simple_block(block, context.codemap) => - { - stmt_expr(&block.stmts[0]).unwrap_or(body) - } - _ => body, - }; - let (prefix, extra_offset) = rewrite_closure_fn_decl( - capture, - movability, - fn_decl, - body, - expr.span, - context, - shape, - )?; - // If the closure goes multi line before its body, do not overflow the closure. - if prefix.contains('\n') { - return None; - } - - let body_shape = shape.offset_left(extra_offset)?; - - // We force to use block for the body of the closure for certain kinds of expressions. - if is_block_closure_forced(context, body) { - return rewrite_closure_with_block(body, &prefix, context, body_shape).and_then( - |body_str| { - // If the expression can fit in a single line, we need not force block closure. - if body_str.lines().count() <= 7 { - match rewrite_closure_expr(body, &prefix, context, shape) { - Some(ref single_line_body_str) - if !single_line_body_str.contains('\n') => - { - Some(single_line_body_str.clone()) - } - _ => Some(body_str), - } - } else { - Some(body_str) - } - }, - ); - } - - // When overflowing the closure which consists of a single control flow expression, - // force to use block if its condition uses multi line. - let is_multi_lined_cond = rewrite_cond(context, body, body_shape) - .map(|cond| cond.contains('\n') || cond.len() > body_shape.width) - .unwrap_or(false); - if is_multi_lined_cond { - return rewrite_closure_with_block(body, &prefix, context, body_shape); - } - - // Seems fine, just format the closure in usual manner. - return expr.rewrite(context, shape); - } - None -} - -/// Returns true if the given vector of arguments has more than one `ast::ExprKind::Closure`. -pub fn args_have_many_closure<T>(args: &[&T]) -> bool -where - T: ToExpr, -{ - args.iter() - .filter(|arg| { - arg.to_expr() - .map(|e| match e.node { - ast::ExprKind::Closure(..) => true, - _ => false, - }) - .unwrap_or(false) - }) - .count() > 1 -} - -fn is_block_closure_forced(context: &RewriteContext, expr: &ast::Expr) -> bool { - // If we are inside macro, we do not want to add or remove block from closure body. - if context.inside_macro { - false - } else { - is_block_closure_forced_inner(expr) - } -} - -fn is_block_closure_forced_inner(expr: &ast::Expr) -> bool { - match expr.node { - ast::ExprKind::If(..) - | ast::ExprKind::IfLet(..) - | ast::ExprKind::While(..) - | ast::ExprKind::WhileLet(..) - | ast::ExprKind::ForLoop(..) => true, - ast::ExprKind::AddrOf(_, ref expr) - | ast::ExprKind::Box(ref expr) - | ast::ExprKind::Try(ref expr) - | ast::ExprKind::Unary(_, ref expr) - | ast::ExprKind::Cast(ref expr, _) => is_block_closure_forced_inner(expr), - _ => false, - } -} diff --git a/src/codemap.rs b/src/codemap.rs deleted file mode 100644 index d74d24439d9..00000000000 --- a/src/codemap.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2016 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! This module contains utilities that work with the `CodeMap` from `libsyntax` / `syntex_syntax`. -//! This includes extension traits and methods for looking up spans and line ranges for AST nodes. - -use std::rc::Rc; - -use syntax::codemap::{BytePos, CodeMap, FileMap, FileName, Span}; - -use comment::FindUncommented; - -/// A range of lines in a file, inclusive of both ends. -pub struct LineRange { - pub file: Rc<FileMap>, - pub lo: usize, - pub hi: usize, -} - -impl LineRange { - pub fn file_name(&self) -> &FileName { - &self.file.name - } -} - -pub trait SpanUtils { - fn span_after(&self, original: Span, needle: &str) -> BytePos; - fn span_after_last(&self, original: Span, needle: &str) -> BytePos; - fn span_before(&self, original: Span, needle: &str) -> BytePos; - fn opt_span_after(&self, original: Span, needle: &str) -> Option<BytePos>; -} - -pub trait LineRangeUtils { - /// Returns the `LineRange` that corresponds to `span` in `self`. - /// - /// # Panics - /// - /// Panics if `span` crosses a file boundary, which shouldn't happen. - fn lookup_line_range(&self, span: Span) -> LineRange; -} - -impl SpanUtils for CodeMap { - fn span_after(&self, original: Span, needle: &str) -> BytePos { - let snippet = self.span_to_snippet(original).expect("Bad snippet"); - let offset = snippet.find_uncommented(needle).expect("Bad offset") + needle.len(); - - original.lo() + BytePos(offset as u32) - } - - fn span_after_last(&self, original: Span, needle: &str) -> BytePos { - let snippet = self.span_to_snippet(original).unwrap(); - let mut offset = 0; - - while let Some(additional_offset) = snippet[offset..].find_uncommented(needle) { - offset += additional_offset + needle.len(); - } - - original.lo() + BytePos(offset as u32) - } - - fn span_before(&self, original: Span, needle: &str) -> BytePos { - let snippet = self.span_to_snippet(original).unwrap(); - let offset = snippet.find_uncommented(needle).unwrap(); - - original.lo() + BytePos(offset as u32) - } - - fn opt_span_after(&self, original: Span, needle: &str) -> Option<BytePos> { - let snippet = self.span_to_snippet(original).ok()?; - let offset = snippet.find_uncommented(needle)? + needle.len(); - - Some(original.lo() + BytePos(offset as u32)) - } -} - -impl LineRangeUtils for CodeMap { - fn lookup_line_range(&self, span: Span) -> LineRange { - let lo = self.lookup_char_pos(span.lo()); - let hi = self.lookup_char_pos(span.hi()); - - assert_eq!( - lo.file.name, hi.file.name, - "span crossed file boundary: lo: {:?}, hi: {:?}", - lo, hi - ); - - LineRange { - file: lo.file.clone(), - lo: lo.line, - hi: hi.line, - } - } -} diff --git a/src/comment.rs b/src/comment.rs deleted file mode 100644 index 9c0322bcaeb..00000000000 --- a/src/comment.rs +++ /dev/null @@ -1,1234 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -// Formatting and tools for comments. - -use std::{self, iter}; - -use syntax::codemap::Span; - -use config::Config; -use rewrite::RewriteContext; -use shape::{Indent, Shape}; -use string::{rewrite_string, StringFormat}; -use utils::{count_newlines, first_line_width, last_line_width}; - -fn is_custom_comment(comment: &str) -> bool { - if !comment.starts_with("//") { - false - } else if let Some(c) = comment.chars().nth(2) { - !c.is_alphanumeric() && !c.is_whitespace() - } else { - false - } -} - -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum CommentStyle<'a> { - DoubleSlash, - TripleSlash, - Doc, - SingleBullet, - DoubleBullet, - Exclamation, - Custom(&'a str), -} - -fn custom_opener(s: &str) -> &str { - s.lines().next().map_or("", |first_line| { - first_line - .find(' ') - .map_or(first_line, |space_index| &first_line[0..space_index + 1]) - }) -} - -impl<'a> CommentStyle<'a> { - pub fn opener(&self) -> &'a str { - match *self { - CommentStyle::DoubleSlash => "// ", - CommentStyle::TripleSlash => "/// ", - CommentStyle::Doc => "//! ", - CommentStyle::SingleBullet => "/* ", - CommentStyle::DoubleBullet => "/** ", - CommentStyle::Exclamation => "/*! ", - CommentStyle::Custom(opener) => opener, - } - } - - pub fn closer(&self) -> &'a str { - match *self { - CommentStyle::DoubleSlash - | CommentStyle::TripleSlash - | CommentStyle::Custom(..) - | CommentStyle::Doc => "", - CommentStyle::DoubleBullet => " **/", - CommentStyle::SingleBullet | CommentStyle::Exclamation => " */", - } - } - - pub fn line_start(&self) -> &'a str { - match *self { - CommentStyle::DoubleSlash => "// ", - CommentStyle::TripleSlash => "/// ", - CommentStyle::Doc => "//! ", - CommentStyle::SingleBullet | CommentStyle::Exclamation => " * ", - CommentStyle::DoubleBullet => " ** ", - CommentStyle::Custom(opener) => opener, - } - } - - pub fn to_str_tuplet(&self) -> (&'a str, &'a str, &'a str) { - (self.opener(), self.closer(), self.line_start()) - } - - pub fn line_with_same_comment_style(&self, line: &str, normalize_comments: bool) -> bool { - match *self { - CommentStyle::DoubleSlash | CommentStyle::TripleSlash | CommentStyle::Doc => { - line.trim_left().starts_with(self.line_start().trim_left()) - || comment_style(line, normalize_comments) == *self - } - CommentStyle::DoubleBullet | CommentStyle::SingleBullet | CommentStyle::Exclamation => { - line.trim_left().starts_with(self.closer().trim_left()) - || line.trim_left().starts_with(self.line_start().trim_left()) - || comment_style(line, normalize_comments) == *self - } - CommentStyle::Custom(opener) => line.trim_left().starts_with(opener.trim_right()), - } - } -} - -fn comment_style(orig: &str, normalize_comments: bool) -> CommentStyle { - if !normalize_comments { - if orig.starts_with("/**") && !orig.starts_with("/**/") { - CommentStyle::DoubleBullet - } else if orig.starts_with("/*!") { - CommentStyle::Exclamation - } else if orig.starts_with("/*") { - CommentStyle::SingleBullet - } else if orig.starts_with("///") && orig.chars().nth(3).map_or(true, |c| c != '/') { - CommentStyle::TripleSlash - } else if orig.starts_with("//!") { - CommentStyle::Doc - } else if is_custom_comment(orig) { - CommentStyle::Custom(custom_opener(orig)) - } else { - CommentStyle::DoubleSlash - } - } else if (orig.starts_with("///") && orig.chars().nth(3).map_or(true, |c| c != '/')) - || (orig.starts_with("/**") && !orig.starts_with("/**/")) - { - CommentStyle::TripleSlash - } else if orig.starts_with("//!") || orig.starts_with("/*!") { - CommentStyle::Doc - } else if is_custom_comment(orig) { - CommentStyle::Custom(custom_opener(orig)) - } else { - CommentStyle::DoubleSlash - } -} - -/// Combine `prev_str` and `next_str` into a single `String`. `span` may contain -/// comments between two strings. If there are such comments, then that will be -/// recovered. If `allow_extend` is true and there is no comment between the two -/// strings, then they will be put on a single line as long as doing so does not -/// exceed max width. -pub fn combine_strs_with_missing_comments( - context: &RewriteContext, - prev_str: &str, - next_str: &str, - span: Span, - shape: Shape, - allow_extend: bool, -) -> Option<String> { - let mut allow_one_line = !prev_str.contains('\n') && !next_str.contains('\n'); - let first_sep = if prev_str.is_empty() || next_str.is_empty() { - "" - } else { - " " - }; - let mut one_line_width = - last_line_width(prev_str) + first_line_width(next_str) + first_sep.len(); - - let indent_str = shape.indent.to_string(context.config); - let missing_comment = rewrite_missing_comment(span, shape, context)?; - - if missing_comment.is_empty() { - if allow_extend && prev_str.len() + first_sep.len() + next_str.len() <= shape.width { - return Some(format!("{}{}{}", prev_str, first_sep, next_str)); - } else { - let sep = if prev_str.is_empty() { - String::new() - } else { - String::from("\n") + &indent_str - }; - return Some(format!("{}{}{}", prev_str, sep, next_str)); - } - } - - // We have a missing comment between the first expression and the second expression. - - // Peek the the original source code and find out whether there is a newline between the first - // expression and the second expression or the missing comment. We will preserve the original - // layout whenever possible. - let original_snippet = context.snippet(span); - let prefer_same_line = if let Some(pos) = original_snippet.chars().position(|c| c == '/') { - !original_snippet[..pos].contains('\n') - } else { - !original_snippet.contains('\n') - }; - - one_line_width -= first_sep.len(); - let first_sep = if prev_str.is_empty() || missing_comment.is_empty() { - String::new() - } else { - let one_line_width = last_line_width(prev_str) + first_line_width(&missing_comment) + 1; - if prefer_same_line && one_line_width <= shape.width { - String::from(" ") - } else { - format!("\n{}", indent_str) - } - }; - let second_sep = if missing_comment.is_empty() || next_str.is_empty() { - String::new() - } else if missing_comment.starts_with("//") { - format!("\n{}", indent_str) - } else { - one_line_width += missing_comment.len() + first_sep.len() + 1; - allow_one_line &= !missing_comment.starts_with("//") && !missing_comment.contains('\n'); - if prefer_same_line && allow_one_line && one_line_width <= shape.width { - String::from(" ") - } else { - format!("\n{}", indent_str) - } - }; - Some(format!( - "{}{}{}{}{}", - prev_str, first_sep, missing_comment, second_sep, next_str, - )) -} - -pub fn rewrite_comment( - orig: &str, - block_style: bool, - shape: Shape, - config: &Config, -) -> Option<String> { - // If there are lines without a starting sigil, we won't format them correctly - // so in that case we won't even re-align (if !config.normalize_comments()) and - // we should stop now. - let num_bare_lines = orig.lines() - .map(|line| line.trim()) - .filter(|l| !(l.starts_with('*') || l.starts_with("//") || l.starts_with("/*"))) - .count(); - if num_bare_lines > 0 && !config.normalize_comments() { - return Some(orig.to_owned()); - } - if !config.normalize_comments() && !config.wrap_comments() { - return light_rewrite_comment(orig, shape.indent, config); - } - - identify_comment(orig, block_style, shape, config) -} - -fn identify_comment( - orig: &str, - block_style: bool, - shape: Shape, - config: &Config, -) -> Option<String> { - let style = comment_style(orig, false); - let first_group = orig.lines() - .take_while(|l| style.line_with_same_comment_style(l, false)) - .collect::<Vec<_>>() - .join("\n"); - let rest = orig.lines() - .skip(first_group.lines().count()) - .collect::<Vec<_>>() - .join("\n"); - - let first_group_str = rewrite_comment_inner(&first_group, block_style, style, shape, config)?; - if rest.is_empty() { - Some(first_group_str) - } else { - identify_comment(&rest, block_style, shape, config).map(|rest_str| { - format!( - "{}\n{}{}", - first_group_str, - shape.indent.to_string(config), - rest_str - ) - }) - } -} - -fn rewrite_comment_inner( - orig: &str, - block_style: bool, - style: CommentStyle, - shape: Shape, - config: &Config, -) -> Option<String> { - let (opener, closer, line_start) = if block_style { - CommentStyle::SingleBullet.to_str_tuplet() - } else { - comment_style(orig, config.normalize_comments()).to_str_tuplet() - }; - - let max_chars = shape - .width - .checked_sub(closer.len() + opener.len()) - .unwrap_or(1); - let indent_str = shape.indent.to_string(config); - let fmt_indent = shape.indent + (opener.len() - line_start.len()); - let mut fmt = StringFormat { - opener: "", - closer: "", - line_start, - line_end: "", - shape: Shape::legacy(max_chars, fmt_indent), - trim_end: true, - config, - }; - - let line_breaks = count_newlines(orig.trim_right()); - let lines = orig.lines() - .enumerate() - .map(|(i, mut line)| { - line = line.trim(); - // Drop old closer. - if i == line_breaks && line.ends_with("*/") && !line.starts_with("//") { - line = line[..(line.len() - 2)].trim_right(); - } - - line - }) - .map(|s| left_trim_comment_line(s, &style)) - .map(|(line, has_leading_whitespace)| { - if orig.starts_with("/*") && line_breaks == 0 { - ( - line.trim_left(), - has_leading_whitespace || config.normalize_comments(), - ) - } else { - (line, has_leading_whitespace || config.normalize_comments()) - } - }); - - let mut result = String::with_capacity(orig.len() * 2); - result.push_str(opener); - let mut code_block_buffer = String::with_capacity(128); - let mut is_prev_line_multi_line = false; - let mut inside_code_block = false; - let comment_line_separator = format!("\n{}{}", indent_str, line_start); - let join_code_block_with_comment_line_separator = |s: &str| { - let mut result = String::with_capacity(s.len() + 128); - let mut iter = s.lines().peekable(); - while let Some(line) = iter.next() { - result.push_str(line); - result.push_str(match iter.peek() { - Some(next_line) if next_line.is_empty() => comment_line_separator.trim_right(), - Some(..) => &comment_line_separator, - None => "", - }); - } - result - }; - - for (i, (line, has_leading_whitespace)) in lines.enumerate() { - let is_last = i == count_newlines(orig); - - if inside_code_block { - if line.starts_with("```") { - inside_code_block = false; - result.push_str(&comment_line_separator); - let code_block = ::format_code_block(&code_block_buffer, config) - .unwrap_or_else(|| code_block_buffer.to_owned()); - result.push_str(&join_code_block_with_comment_line_separator(&code_block)); - code_block_buffer.clear(); - result.push_str(&comment_line_separator); - result.push_str(line); - } else { - code_block_buffer.push_str(line); - code_block_buffer.push('\n'); - } - - continue; - } else { - inside_code_block = line.starts_with("```"); - - if result == opener { - let force_leading_whitespace = opener == "/* " && count_newlines(orig) == 0; - if !has_leading_whitespace && !force_leading_whitespace && result.ends_with(' ') { - result.pop(); - } - if line.is_empty() { - continue; - } - } else if is_prev_line_multi_line && !line.is_empty() { - result.push(' ') - } else if is_last && !closer.is_empty() && line.is_empty() { - result.push('\n'); - result.push_str(&indent_str); - } else { - result.push_str(&comment_line_separator); - if !has_leading_whitespace && result.ends_with(' ') { - result.pop(); - } - } - } - - if config.wrap_comments() && line.len() > fmt.shape.width && !has_url(line) { - match rewrite_string(line, &fmt, Some(max_chars)) { - Some(ref s) => { - is_prev_line_multi_line = s.contains('\n'); - result.push_str(s); - } - None if is_prev_line_multi_line => { - // We failed to put the current `line` next to the previous `line`. - // Remove the trailing space, then start rewrite on the next line. - result.pop(); - result.push_str(&comment_line_separator); - fmt.shape = Shape::legacy(max_chars, fmt_indent); - match rewrite_string(line, &fmt, Some(max_chars)) { - Some(ref s) => { - is_prev_line_multi_line = s.contains('\n'); - result.push_str(s); - } - None => { - is_prev_line_multi_line = false; - result.push_str(line); - } - } - } - None => { - is_prev_line_multi_line = false; - result.push_str(line); - } - } - - fmt.shape = if is_prev_line_multi_line { - // 1 = " " - let offset = 1 + last_line_width(&result) - line_start.len(); - Shape { - width: max_chars.checked_sub(offset).unwrap_or(0), - indent: fmt_indent, - offset: fmt.shape.offset + offset, - } - } else { - Shape::legacy(max_chars, fmt_indent) - }; - } else { - if line.is_empty() && result.ends_with(' ') && !is_last { - // Remove space if this is an empty comment or a doc comment. - result.pop(); - } - result.push_str(line); - fmt.shape = Shape::legacy(max_chars, fmt_indent); - is_prev_line_multi_line = false; - } - } - - result.push_str(closer); - if result == opener && result.ends_with(' ') { - // Trailing space. - result.pop(); - } - - Some(result) -} - -/// Returns true if the given string MAY include URLs or alike. -fn has_url(s: &str) -> bool { - // This function may return false positive, but should get its job done in most cases. - s.contains("https://") || s.contains("http://") || s.contains("ftp://") || s.contains("file://") -} - -/// Given the span, rewrite the missing comment inside it if available. -/// Note that the given span must only include comments (or leading/trailing whitespaces). -pub fn rewrite_missing_comment( - span: Span, - shape: Shape, - context: &RewriteContext, -) -> Option<String> { - let missing_snippet = context.snippet(span); - let trimmed_snippet = missing_snippet.trim(); - if !trimmed_snippet.is_empty() { - rewrite_comment(trimmed_snippet, false, shape, context.config) - } else { - Some(String::new()) - } -} - -/// Recover the missing comments in the specified span, if available. -/// The layout of the comments will be preserved as long as it does not break the code -/// and its total width does not exceed the max width. -pub fn recover_missing_comment_in_span( - span: Span, - shape: Shape, - context: &RewriteContext, - used_width: usize, -) -> Option<String> { - let missing_comment = rewrite_missing_comment(span, shape, context)?; - if missing_comment.is_empty() { - Some(String::new()) - } else { - let missing_snippet = context.snippet(span); - let pos = missing_snippet.chars().position(|c| c == '/').unwrap_or(0); - // 1 = ` ` - let total_width = missing_comment.len() + used_width + 1; - let force_new_line_before_comment = - missing_snippet[..pos].contains('\n') || total_width > context.config.max_width(); - let sep = if force_new_line_before_comment { - format!("\n{}", shape.indent.to_string(context.config)) - } else { - String::from(" ") - }; - Some(format!("{}{}", sep, missing_comment)) - } -} - -/// Trims whitespace and aligns to indent, but otherwise does not change comments. -fn light_rewrite_comment(orig: &str, offset: Indent, config: &Config) -> Option<String> { - let lines: Vec<&str> = orig.lines() - .map(|l| { - // This is basically just l.trim(), but in the case that a line starts - // with `*` we want to leave one space before it, so it aligns with the - // `*` in `/*`. - let first_non_whitespace = l.find(|c| !char::is_whitespace(c)); - if let Some(fnw) = first_non_whitespace { - if l.as_bytes()[fnw] == b'*' && fnw > 0 { - &l[fnw - 1..] - } else { - &l[fnw..] - } - } else { - "" - }.trim_right() - }) - .collect(); - Some(lines.join(&format!("\n{}", offset.to_string(config)))) -} - -/// Trims comment characters and possibly a single space from the left of a string. -/// Does not trim all whitespace. If a single space is trimmed from the left of the string, -/// this function returns true. -fn left_trim_comment_line<'a>(line: &'a str, style: &CommentStyle) -> (&'a str, bool) { - if line.starts_with("//! ") || line.starts_with("/// ") || line.starts_with("/*! ") - || line.starts_with("/** ") - { - (&line[4..], true) - } else if let CommentStyle::Custom(opener) = *style { - if line.starts_with(opener) { - (&line[opener.len()..], true) - } else { - (&line[opener.trim_right().len()..], false) - } - } else if line.starts_with("/* ") || line.starts_with("// ") || line.starts_with("//!") - || line.starts_with("///") || line.starts_with("** ") - || line.starts_with("/*!") - || (line.starts_with("/**") && !line.starts_with("/**/")) - { - (&line[3..], line.chars().nth(2).unwrap() == ' ') - } else if line.starts_with("/*") || line.starts_with("* ") || line.starts_with("//") - || line.starts_with("**") - { - (&line[2..], line.chars().nth(1).unwrap() == ' ') - } else if line.starts_with('*') { - (&line[1..], false) - } else { - (line, line.starts_with(' ')) - } -} - -pub trait FindUncommented { - fn find_uncommented(&self, pat: &str) -> Option<usize>; -} - -impl FindUncommented for str { - fn find_uncommented(&self, pat: &str) -> Option<usize> { - let mut needle_iter = pat.chars(); - for (kind, (i, b)) in CharClasses::new(self.char_indices()) { - match needle_iter.next() { - None => { - return Some(i - pat.len()); - } - Some(c) => match kind { - FullCodeCharKind::Normal | FullCodeCharKind::InString if b == c => {} - _ => { - needle_iter = pat.chars(); - } - }, - } - } - - // Handle case where the pattern is a suffix of the search string - match needle_iter.next() { - Some(_) => None, - None => Some(self.len() - pat.len()), - } - } -} - -// Returns the first byte position after the first comment. The given string -// is expected to be prefixed by a comment, including delimiters. -// Good: "/* /* inner */ outer */ code();" -// Bad: "code(); // hello\n world!" -pub fn find_comment_end(s: &str) -> Option<usize> { - let mut iter = CharClasses::new(s.char_indices()); - for (kind, (i, _c)) in &mut iter { - if kind == FullCodeCharKind::Normal || kind == FullCodeCharKind::InString { - return Some(i); - } - } - - // Handle case where the comment ends at the end of s. - if iter.status == CharClassesStatus::Normal { - Some(s.len()) - } else { - None - } -} - -/// Returns true if text contains any comment. -pub fn contains_comment(text: &str) -> bool { - CharClasses::new(text.chars()).any(|(kind, _)| kind.is_comment()) -} - -/// Remove trailing spaces from the specified snippet. We do not remove spaces -/// inside strings or comments. -pub fn remove_trailing_white_spaces(text: &str) -> String { - let mut buffer = String::with_capacity(text.len()); - let mut space_buffer = String::with_capacity(128); - for (char_kind, c) in CharClasses::new(text.chars()) { - match c { - '\n' => { - if char_kind == FullCodeCharKind::InString { - buffer.push_str(&space_buffer); - } - space_buffer.clear(); - buffer.push('\n'); - } - _ if c.is_whitespace() => { - space_buffer.push(c); - } - _ => { - if !space_buffer.is_empty() { - buffer.push_str(&space_buffer); - space_buffer.clear(); - } - buffer.push(c); - } - } - } - buffer -} - -pub struct CharClasses<T> -where - T: Iterator, - T::Item: RichChar, -{ - base: iter::Peekable<T>, - status: CharClassesStatus, -} - -pub trait RichChar { - fn get_char(&self) -> char; -} - -impl RichChar for char { - fn get_char(&self) -> char { - *self - } -} - -impl RichChar for (usize, char) { - fn get_char(&self) -> char { - self.1 - } -} - -impl RichChar for (char, usize) { - fn get_char(&self) -> char { - self.0 - } -} - -#[derive(PartialEq, Eq, Debug, Clone, Copy)] -enum CharClassesStatus { - Normal, - LitString, - LitStringEscape, - LitChar, - LitCharEscape, - // The u32 is the nesting deepness of the comment - BlockComment(u32), - // Status when the '/' has been consumed, but not yet the '*', deepness is - // the new deepness (after the comment opening). - BlockCommentOpening(u32), - // Status when the '*' has been consumed, but not yet the '/', deepness is - // the new deepness (after the comment closing). - BlockCommentClosing(u32), - LineComment, -} - -/// Distinguish between functional part of code and comments -#[derive(PartialEq, Eq, Debug, Clone, Copy)] -pub enum CodeCharKind { - Normal, - Comment, -} - -/// Distinguish between functional part of code and comments, -/// describing opening and closing of comments for ease when chunking -/// code from tagged characters -#[derive(PartialEq, Eq, Debug, Clone, Copy)] -pub enum FullCodeCharKind { - Normal, - /// The first character of a comment, there is only one for a comment (always '/') - StartComment, - /// Any character inside a comment including the second character of comment - /// marks ("//", "/*") - InComment, - /// Last character of a comment, '\n' for a line comment, '/' for a block comment. - EndComment, - /// Inside a string. - InString, -} - -impl FullCodeCharKind { - pub fn is_comment(&self) -> bool { - match *self { - FullCodeCharKind::StartComment - | FullCodeCharKind::InComment - | FullCodeCharKind::EndComment => true, - _ => false, - } - } - - pub fn is_string(&self) -> bool { - *self == FullCodeCharKind::InString - } - - fn to_codecharkind(&self) -> CodeCharKind { - if self.is_comment() { - CodeCharKind::Comment - } else { - CodeCharKind::Normal - } - } -} - -impl<T> CharClasses<T> -where - T: Iterator, - T::Item: RichChar, -{ - pub fn new(base: T) -> CharClasses<T> { - CharClasses { - base: base.peekable(), - status: CharClassesStatus::Normal, - } - } -} - -impl<T> Iterator for CharClasses<T> -where - T: Iterator, - T::Item: RichChar, -{ - type Item = (FullCodeCharKind, T::Item); - - fn next(&mut self) -> Option<(FullCodeCharKind, T::Item)> { - let item = self.base.next()?; - let chr = item.get_char(); - let mut char_kind = FullCodeCharKind::Normal; - self.status = match self.status { - CharClassesStatus::LitString => match chr { - '"' => CharClassesStatus::Normal, - '\\' => { - char_kind = FullCodeCharKind::InString; - CharClassesStatus::LitStringEscape - } - _ => { - char_kind = FullCodeCharKind::InString; - CharClassesStatus::LitString - } - }, - CharClassesStatus::LitStringEscape => { - char_kind = FullCodeCharKind::InString; - CharClassesStatus::LitString - } - CharClassesStatus::LitChar => match chr { - '\\' => CharClassesStatus::LitCharEscape, - '\'' => CharClassesStatus::Normal, - _ => CharClassesStatus::LitChar, - }, - CharClassesStatus::LitCharEscape => CharClassesStatus::LitChar, - CharClassesStatus::Normal => match chr { - '"' => { - char_kind = FullCodeCharKind::InString; - CharClassesStatus::LitString - } - '\'' => CharClassesStatus::LitChar, - '/' => match self.base.peek() { - Some(next) if next.get_char() == '*' => { - self.status = CharClassesStatus::BlockCommentOpening(1); - return Some((FullCodeCharKind::StartComment, item)); - } - Some(next) if next.get_char() == '/' => { - self.status = CharClassesStatus::LineComment; - return Some((FullCodeCharKind::StartComment, item)); - } - _ => CharClassesStatus::Normal, - }, - _ => CharClassesStatus::Normal, - }, - CharClassesStatus::BlockComment(deepness) => { - assert_ne!(deepness, 0); - self.status = match self.base.peek() { - Some(next) if next.get_char() == '/' && chr == '*' => { - CharClassesStatus::BlockCommentClosing(deepness - 1) - } - Some(next) if next.get_char() == '*' && chr == '/' => { - CharClassesStatus::BlockCommentOpening(deepness + 1) - } - _ => CharClassesStatus::BlockComment(deepness), - }; - return Some((FullCodeCharKind::InComment, item)); - } - CharClassesStatus::BlockCommentOpening(deepness) => { - assert_eq!(chr, '*'); - self.status = CharClassesStatus::BlockComment(deepness); - return Some((FullCodeCharKind::InComment, item)); - } - CharClassesStatus::BlockCommentClosing(deepness) => { - assert_eq!(chr, '/'); - if deepness == 0 { - self.status = CharClassesStatus::Normal; - return Some((FullCodeCharKind::EndComment, item)); - } else { - self.status = CharClassesStatus::BlockComment(deepness); - return Some((FullCodeCharKind::InComment, item)); - } - } - CharClassesStatus::LineComment => match chr { - '\n' => { - self.status = CharClassesStatus::Normal; - return Some((FullCodeCharKind::EndComment, item)); - } - _ => { - self.status = CharClassesStatus::LineComment; - return Some((FullCodeCharKind::InComment, item)); - } - }, - }; - Some((char_kind, item)) - } -} - -/// Iterator over functional and commented parts of a string. Any part of a string is either -/// functional code, either *one* block comment, either *one* line comment. Whitespace between -/// comments is functional code. Line comments contain their ending newlines. -struct UngroupedCommentCodeSlices<'a> { - slice: &'a str, - iter: iter::Peekable<CharClasses<std::str::CharIndices<'a>>>, -} - -impl<'a> UngroupedCommentCodeSlices<'a> { - fn new(code: &'a str) -> UngroupedCommentCodeSlices<'a> { - UngroupedCommentCodeSlices { - slice: code, - iter: CharClasses::new(code.char_indices()).peekable(), - } - } -} - -impl<'a> Iterator for UngroupedCommentCodeSlices<'a> { - type Item = (CodeCharKind, usize, &'a str); - - fn next(&mut self) -> Option<Self::Item> { - let (kind, (start_idx, _)) = self.iter.next()?; - match kind { - FullCodeCharKind::Normal | FullCodeCharKind::InString => { - // Consume all the Normal code - while let Some(&(char_kind, _)) = self.iter.peek() { - if char_kind.is_comment() { - break; - } - let _ = self.iter.next(); - } - } - FullCodeCharKind::StartComment => { - // Consume the whole comment - while let Some((FullCodeCharKind::InComment, (_, _))) = self.iter.next() {} - } - _ => panic!(), - } - let slice = match self.iter.peek() { - Some(&(_, (end_idx, _))) => &self.slice[start_idx..end_idx], - None => &self.slice[start_idx..], - }; - Some(( - if kind.is_comment() { - CodeCharKind::Comment - } else { - CodeCharKind::Normal - }, - start_idx, - slice, - )) - } -} - -/// Iterator over an alternating sequence of functional and commented parts of -/// a string. The first item is always a, possibly zero length, subslice of -/// functional text. Line style comments contain their ending newlines. -pub struct CommentCodeSlices<'a> { - slice: &'a str, - last_slice_kind: CodeCharKind, - last_slice_end: usize, -} - -impl<'a> CommentCodeSlices<'a> { - pub fn new(slice: &'a str) -> CommentCodeSlices<'a> { - CommentCodeSlices { - slice, - last_slice_kind: CodeCharKind::Comment, - last_slice_end: 0, - } - } -} - -impl<'a> Iterator for CommentCodeSlices<'a> { - type Item = (CodeCharKind, usize, &'a str); - - fn next(&mut self) -> Option<Self::Item> { - if self.last_slice_end == self.slice.len() { - return None; - } - - let mut sub_slice_end = self.last_slice_end; - let mut first_whitespace = None; - let subslice = &self.slice[self.last_slice_end..]; - let mut iter = CharClasses::new(subslice.char_indices()); - - for (kind, (i, c)) in &mut iter { - let is_comment_connector = self.last_slice_kind == CodeCharKind::Normal - && &subslice[..2] == "//" - && [' ', '\t'].contains(&c); - - if is_comment_connector && first_whitespace.is_none() { - first_whitespace = Some(i); - } - - if kind.to_codecharkind() == self.last_slice_kind && !is_comment_connector { - let last_index = match first_whitespace { - Some(j) => j, - None => i, - }; - sub_slice_end = self.last_slice_end + last_index; - break; - } - - if !is_comment_connector { - first_whitespace = None; - } - } - - if let (None, true) = (iter.next(), sub_slice_end == self.last_slice_end) { - // This was the last subslice. - sub_slice_end = match first_whitespace { - Some(i) => self.last_slice_end + i, - None => self.slice.len(), - }; - } - - let kind = match self.last_slice_kind { - CodeCharKind::Comment => CodeCharKind::Normal, - CodeCharKind::Normal => CodeCharKind::Comment, - }; - let res = ( - kind, - self.last_slice_end, - &self.slice[self.last_slice_end..sub_slice_end], - ); - self.last_slice_end = sub_slice_end; - self.last_slice_kind = kind; - - Some(res) - } -} - -/// Checks is `new` didn't miss any comment from `span`, if it removed any, return previous text -/// (if it fits in the width/offset, else return None), else return `new` -pub fn recover_comment_removed( - new: String, - span: Span, - context: &RewriteContext, -) -> Option<String> { - let snippet = context.snippet(span); - if snippet != new && changed_comment_content(snippet, &new) { - // We missed some comments. Keep the original text. - Some(snippet.to_owned()) - } else { - Some(new) - } -} - -/// Return true if the two strings of code have the same payload of comments. -/// The payload of comments is everything in the string except: -/// - actual code (not comments) -/// - comment start/end marks -/// - whitespace -/// - '*' at the beginning of lines in block comments -fn changed_comment_content(orig: &str, new: &str) -> bool { - // Cannot write this as a fn since we cannot return types containing closures - let code_comment_content = |code| { - let slices = UngroupedCommentCodeSlices::new(code); - slices - .filter(|&(ref kind, _, _)| *kind == CodeCharKind::Comment) - .flat_map(|(_, _, s)| CommentReducer::new(s)) - }; - let res = code_comment_content(orig).ne(code_comment_content(new)); - debug!( - "comment::changed_comment_content: {}\norig: '{}'\nnew: '{}'\nraw_old: {}\nraw_new: {}", - res, - orig, - new, - code_comment_content(orig).collect::<String>(), - code_comment_content(new).collect::<String>() - ); - res -} - -/// Iterator over the 'payload' characters of a comment. -/// It skips whitespace, comment start/end marks, and '*' at the beginning of lines. -/// The comment must be one comment, ie not more than one start mark (no multiple line comments, -/// for example). -struct CommentReducer<'a> { - is_block: bool, - at_start_line: bool, - iter: std::str::Chars<'a>, -} - -impl<'a> CommentReducer<'a> { - fn new(comment: &'a str) -> CommentReducer<'a> { - let is_block = comment.starts_with("/*"); - let comment = remove_comment_header(comment); - CommentReducer { - is_block, - at_start_line: false, // There are no supplementary '*' on the first line - iter: comment.chars(), - } - } -} - -impl<'a> Iterator for CommentReducer<'a> { - type Item = char; - fn next(&mut self) -> Option<Self::Item> { - loop { - let mut c = self.iter.next()?; - if self.is_block && self.at_start_line { - while c.is_whitespace() { - c = self.iter.next()?; - } - // Ignore leading '*' - if c == '*' { - c = self.iter.next()?; - } - } else if c == '\n' { - self.at_start_line = true; - } - if !c.is_whitespace() { - return Some(c); - } - } - } -} - -fn remove_comment_header(comment: &str) -> &str { - if comment.starts_with("///") || comment.starts_with("//!") { - &comment[3..] - } else if comment.starts_with("//") { - &comment[2..] - } else if (comment.starts_with("/**") && !comment.starts_with("/**/")) - || comment.starts_with("/*!") - { - &comment[3..comment.len() - 2] - } else { - assert!( - comment.starts_with("/*"), - format!("string '{}' is not a comment", comment) - ); - &comment[2..comment.len() - 2] - } -} - -#[cfg(test)] -mod test { - use super::{contains_comment, rewrite_comment, CharClasses, CodeCharKind, CommentCodeSlices, - FindUncommented, FullCodeCharKind}; - use shape::{Indent, Shape}; - - #[test] - fn char_classes() { - let mut iter = CharClasses::new("//\n\n".chars()); - - assert_eq!((FullCodeCharKind::StartComment, '/'), iter.next().unwrap()); - assert_eq!((FullCodeCharKind::InComment, '/'), iter.next().unwrap()); - assert_eq!((FullCodeCharKind::EndComment, '\n'), iter.next().unwrap()); - assert_eq!((FullCodeCharKind::Normal, '\n'), iter.next().unwrap()); - assert_eq!(None, iter.next()); - } - - #[test] - fn comment_code_slices() { - let input = "code(); /* test */ 1 + 1"; - let mut iter = CommentCodeSlices::new(input); - - assert_eq!((CodeCharKind::Normal, 0, "code(); "), iter.next().unwrap()); - assert_eq!( - (CodeCharKind::Comment, 8, "/* test */"), - iter.next().unwrap() - ); - assert_eq!((CodeCharKind::Normal, 18, " 1 + 1"), iter.next().unwrap()); - assert_eq!(None, iter.next()); - } - - #[test] - fn comment_code_slices_two() { - let input = "// comment\n test();"; - let mut iter = CommentCodeSlices::new(input); - - assert_eq!((CodeCharKind::Normal, 0, ""), iter.next().unwrap()); - assert_eq!( - (CodeCharKind::Comment, 0, "// comment\n"), - iter.next().unwrap() - ); - assert_eq!( - (CodeCharKind::Normal, 11, " test();"), - iter.next().unwrap() - ); - assert_eq!(None, iter.next()); - } - - #[test] - fn comment_code_slices_three() { - let input = "1 // comment\n // comment2\n\n"; - let mut iter = CommentCodeSlices::new(input); - - assert_eq!((CodeCharKind::Normal, 0, "1 "), iter.next().unwrap()); - assert_eq!( - (CodeCharKind::Comment, 2, "// comment\n // comment2\n"), - iter.next().unwrap() - ); - assert_eq!((CodeCharKind::Normal, 29, "\n"), iter.next().unwrap()); - assert_eq!(None, iter.next()); - } - - #[test] - #[cfg_attr(rustfmt, rustfmt_skip)] - fn format_comments() { - let mut config: ::config::Config = Default::default(); - config.set().wrap_comments(true); - config.set().normalize_comments(true); - - let comment = rewrite_comment(" //test", - true, - Shape::legacy(100, Indent::new(0, 100)), - &config).unwrap(); - assert_eq!("/* test */", comment); - - let comment = rewrite_comment("// comment on a", - false, - Shape::legacy(10, Indent::empty()), - &config).unwrap(); - assert_eq!("// comment\n// on a", comment); - - let comment = rewrite_comment("// A multi line comment\n // between args.", - false, - Shape::legacy(60, Indent::new(0, 12)), - &config).unwrap(); - assert_eq!("// A multi line comment\n // between args.", comment); - - let input = "// comment"; - let expected = - "/* comment */"; - let comment = rewrite_comment(input, - true, - Shape::legacy(9, Indent::new(0, 69)), - &config).unwrap(); - assert_eq!(expected, comment); - - let comment = rewrite_comment("/* trimmed */", - true, - Shape::legacy(100, Indent::new(0, 100)), - &config).unwrap(); - assert_eq!("/* trimmed */", comment); - } - - // This is probably intended to be a non-test fn, but it is not used. I'm - // keeping it around unless it helps us test stuff. - fn uncommented(text: &str) -> String { - CharClasses::new(text.chars()) - .filter_map(|(s, c)| match s { - FullCodeCharKind::Normal | FullCodeCharKind::InString => Some(c), - _ => None, - }) - .collect() - } - - #[test] - fn test_uncommented() { - assert_eq!(&uncommented("abc/*...*/"), "abc"); - assert_eq!( - &uncommented("// .... /* \n../* /* *** / */ */a/* // */c\n"), - "..ac\n" - ); - assert_eq!(&uncommented("abc \" /* */\" qsdf"), "abc \" /* */\" qsdf"); - } - - #[test] - fn test_contains_comment() { - assert_eq!(contains_comment("abc"), false); - assert_eq!(contains_comment("abc // qsdf"), true); - assert_eq!(contains_comment("abc /* kqsdf"), true); - assert_eq!(contains_comment("abc \" /* */\" qsdf"), false); - } - - #[test] - fn test_find_uncommented() { - fn check(haystack: &str, needle: &str, expected: Option<usize>) { - assert_eq!(expected, haystack.find_uncommented(needle)); - } - - check("/*/ */test", "test", Some(6)); - check("//test\ntest", "test", Some(7)); - check("/* comment only */", "whatever", None); - check( - "/* comment */ some text /* more commentary */ result", - "result", - Some(46), - ); - check("sup // sup", "p", Some(2)); - check("sup", "x", None); - check(r#"π? /**/ π is nice!"#, r#"π is nice"#, Some(9)); - check("/*sup yo? \n sup*/ sup", "p", Some(20)); - check("hel/*lohello*/lo", "hello", None); - check("acb", "ab", None); - check(",/*A*/ ", ",", Some(0)); - check("abc", "abc", Some(0)); - check("/* abc */", "abc", None); - check("/**/abc/* */", "abc", Some(4)); - check("\"/* abc */\"", "abc", Some(4)); - check("\"/* abc", "abc", Some(4)); - } -} diff --git a/src/expr.rs b/src/expr.rs deleted file mode 100644 index fb873ee26d3..00000000000 --- a/src/expr.rs +++ /dev/null @@ -1,2930 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::borrow::Cow; -use std::cmp::min; -use std::iter::repeat; - -use syntax::{ast, ptr}; -use syntax::codemap::{BytePos, CodeMap, Span}; - -use chains::rewrite_chain; -use closures; -use codemap::{LineRangeUtils, SpanUtils}; -use comment::{combine_strs_with_missing_comments, contains_comment, recover_comment_removed, - rewrite_comment, rewrite_missing_comment, FindUncommented}; -use config::{Config, ControlBraceStyle, IndentStyle}; -use lists::{definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, - struct_lit_shape, struct_lit_tactic, write_list, DefinitiveListTactic, ListFormatting, - ListItem, ListTactic, Separator, SeparatorPlace, SeparatorTactic}; -use macros::{rewrite_macro, MacroArg, MacroPosition}; -use patterns::{can_be_overflowed_pat, TuplePatField}; -use rewrite::{Rewrite, RewriteContext}; -use shape::{Indent, Shape}; -use spanned::Spanned; -use string::{rewrite_string, StringFormat}; -use types::{can_be_overflowed_type, rewrite_path, PathContext}; -use utils::{colon_spaces, contains_skip, count_newlines, extra_offset, first_line_width, - inner_attributes, last_line_extendable, last_line_width, mk_sp, outer_attributes, - paren_overhead, ptr_vec_to_ref_vec, semicolon_for_stmt, trimmed_last_line_width, - wrap_str}; -use vertical::rewrite_with_alignment; -use visitor::FmtVisitor; - -impl Rewrite for ast::Expr { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - format_expr(self, ExprType::SubExpression, context, shape) - } -} - -#[derive(Copy, Clone, PartialEq)] -pub enum ExprType { - Statement, - SubExpression, -} - -pub fn format_expr( - expr: &ast::Expr, - expr_type: ExprType, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - skip_out_of_file_lines_range!(context, expr.span); - - if contains_skip(&*expr.attrs) { - return Some(context.snippet(expr.span()).to_owned()); - } - - let expr_rw = match expr.node { - ast::ExprKind::Array(ref expr_vec) => rewrite_array( - &ptr_vec_to_ref_vec(expr_vec), - mk_sp(context.codemap.span_after(expr.span, "["), expr.span.hi()), - context, - shape, - false, - ), - ast::ExprKind::Lit(ref l) => rewrite_literal(context, l, shape), - ast::ExprKind::Call(ref callee, ref args) => { - let inner_span = mk_sp(callee.span.hi(), expr.span.hi()); - let callee_str = callee.rewrite(context, shape)?; - rewrite_call(context, &callee_str, args, inner_span, shape) - } - ast::ExprKind::Paren(ref subexpr) => rewrite_paren(context, subexpr, shape), - ast::ExprKind::Binary(ref op, ref lhs, ref rhs) => { - // FIXME: format comments between operands and operator - rewrite_pair( - &**lhs, - &**rhs, - PairParts::new("", &format!(" {} ", context.snippet(op.span)), ""), - context, - shape, - context.config.binop_separator(), - ) - } - ast::ExprKind::Unary(ref op, ref subexpr) => rewrite_unary_op(context, op, subexpr, shape), - ast::ExprKind::Struct(ref path, ref fields, ref base) => rewrite_struct_lit( - context, - path, - fields, - base.as_ref().map(|e| &**e), - expr.span, - shape, - ), - ast::ExprKind::Tup(ref items) => { - rewrite_tuple(context, &ptr_vec_to_ref_vec(items), expr.span, shape) - } - ast::ExprKind::If(..) - | ast::ExprKind::IfLet(..) - | ast::ExprKind::ForLoop(..) - | ast::ExprKind::Loop(..) - | ast::ExprKind::While(..) - | ast::ExprKind::WhileLet(..) => to_control_flow(expr, expr_type) - .and_then(|control_flow| control_flow.rewrite(context, shape)), - ast::ExprKind::Block(ref block) => { - match expr_type { - ExprType::Statement => { - if is_unsafe_block(block) { - block.rewrite(context, shape) - } else if let rw @ Some(_) = rewrite_empty_block(context, block, shape) { - // Rewrite block without trying to put it in a single line. - rw - } else { - let prefix = block_prefix(context, block, shape)?; - rewrite_block_with_visitor(context, &prefix, block, shape, true) - } - } - ExprType::SubExpression => block.rewrite(context, shape), - } - } - ast::ExprKind::Match(ref cond, ref arms) => { - rewrite_match(context, cond, arms, shape, expr.span, &expr.attrs) - } - ast::ExprKind::Path(ref qself, ref path) => { - rewrite_path(context, PathContext::Expr, qself.as_ref(), path, shape) - } - ast::ExprKind::Assign(ref lhs, ref rhs) => { - rewrite_assignment(context, lhs, rhs, None, shape) - } - ast::ExprKind::AssignOp(ref op, ref lhs, ref rhs) => { - rewrite_assignment(context, lhs, rhs, Some(op), shape) - } - ast::ExprKind::Continue(ref opt_label) => { - let id_str = match *opt_label { - Some(label) => format!(" {}", label.ident), - None => String::new(), - }; - Some(format!("continue{}", id_str)) - } - ast::ExprKind::Break(ref opt_label, ref opt_expr) => { - let id_str = match *opt_label { - Some(label) => format!(" {}", label.ident), - None => String::new(), - }; - - if let Some(ref expr) = *opt_expr { - rewrite_unary_prefix(context, &format!("break{} ", id_str), &**expr, shape) - } else { - Some(format!("break{}", id_str)) - } - } - ast::ExprKind::Yield(ref opt_expr) => if let Some(ref expr) = *opt_expr { - rewrite_unary_prefix(context, "yield ", &**expr, shape) - } else { - Some("yield".to_string()) - }, - ast::ExprKind::Closure(capture, movability, ref fn_decl, ref body, _) => { - closures::rewrite_closure( - capture, - movability, - fn_decl, - body, - expr.span, - context, - shape, - ) - } - ast::ExprKind::Try(..) - | ast::ExprKind::Field(..) - | ast::ExprKind::TupField(..) - | ast::ExprKind::MethodCall(..) => rewrite_chain(expr, context, shape), - ast::ExprKind::Mac(ref mac) => { - rewrite_macro(mac, None, context, shape, MacroPosition::Expression).or_else(|| { - wrap_str( - context.snippet(expr.span).to_owned(), - context.config.max_width(), - shape, - ) - }) - } - ast::ExprKind::Ret(None) => Some("return".to_owned()), - ast::ExprKind::Ret(Some(ref expr)) => { - rewrite_unary_prefix(context, "return ", &**expr, shape) - } - ast::ExprKind::Box(ref expr) => rewrite_unary_prefix(context, "box ", &**expr, shape), - ast::ExprKind::AddrOf(mutability, ref expr) => { - rewrite_expr_addrof(context, mutability, expr, shape) - } - ast::ExprKind::Cast(ref expr, ref ty) => rewrite_pair( - &**expr, - &**ty, - PairParts::new("", " as ", ""), - context, - shape, - SeparatorPlace::Front, - ), - ast::ExprKind::Type(ref expr, ref ty) => rewrite_pair( - &**expr, - &**ty, - PairParts::new("", ": ", ""), - context, - shape, - SeparatorPlace::Back, - ), - ast::ExprKind::Index(ref expr, ref index) => { - rewrite_index(&**expr, &**index, context, shape) - } - ast::ExprKind::Repeat(ref expr, ref repeats) => { - let (lbr, rbr) = if context.config.spaces_within_parens_and_brackets() { - ("[ ", " ]") - } else { - ("[", "]") - }; - rewrite_pair( - &**expr, - &**repeats, - PairParts::new(lbr, "; ", rbr), - context, - shape, - SeparatorPlace::Back, - ) - } - ast::ExprKind::Range(ref lhs, ref rhs, limits) => { - let delim = match limits { - ast::RangeLimits::HalfOpen => "..", - ast::RangeLimits::Closed => "..=", - }; - - fn needs_space_before_range(context: &RewriteContext, lhs: &ast::Expr) -> bool { - match lhs.node { - ast::ExprKind::Lit(ref lit) => match lit.node { - ast::LitKind::FloatUnsuffixed(..) => { - context.snippet(lit.span).ends_with('.') - } - _ => false, - }, - _ => false, - } - } - - match (lhs.as_ref().map(|x| &**x), rhs.as_ref().map(|x| &**x)) { - (Some(lhs), Some(rhs)) => { - let sp_delim = if context.config.spaces_around_ranges() { - format!(" {} ", delim) - } else if needs_space_before_range(context, lhs) { - format!(" {}", delim) - } else { - delim.to_owned() - }; - rewrite_pair( - &*lhs, - &*rhs, - PairParts::new("", &sp_delim, ""), - context, - shape, - context.config.binop_separator(), - ) - } - (None, Some(rhs)) => { - let sp_delim = if context.config.spaces_around_ranges() { - format!("{} ", delim) - } else { - delim.to_owned() - }; - rewrite_unary_prefix(context, &sp_delim, &*rhs, shape) - } - (Some(lhs), None) => { - let sp_delim = if context.config.spaces_around_ranges() { - format!(" {}", delim) - } else { - delim.to_owned() - }; - rewrite_unary_suffix(context, &sp_delim, &*lhs, shape) - } - (None, None) => Some(delim.to_owned()), - } - } - // We do not format these expressions yet, but they should still - // satisfy our width restrictions. - ast::ExprKind::InPlace(..) | ast::ExprKind::InlineAsm(..) => { - Some(context.snippet(expr.span).to_owned()) - } - ast::ExprKind::Catch(ref block) => { - if let rw @ Some(_) = rewrite_single_line_block(context, "do catch ", block, shape) { - rw - } else { - // 9 = `do catch ` - let budget = shape.width.checked_sub(9).unwrap_or(0); - Some(format!( - "{}{}", - "do catch ", - block.rewrite(context, Shape::legacy(budget, shape.indent))? - )) - } - } - }; - - expr_rw - .and_then(|expr_str| recover_comment_removed(expr_str, expr.span, context)) - .and_then(|expr_str| { - let attrs = outer_attributes(&expr.attrs); - let attrs_str = attrs.rewrite(context, shape)?; - let span = mk_sp( - attrs.last().map_or(expr.span.lo(), |attr| attr.span.hi()), - expr.span.lo(), - ); - combine_strs_with_missing_comments(context, &attrs_str, &expr_str, span, shape, false) - }) -} - -#[derive(new, Clone, Copy)] -pub struct PairParts<'a> { - prefix: &'a str, - infix: &'a str, - suffix: &'a str, -} - -pub fn rewrite_pair<LHS, RHS>( - lhs: &LHS, - rhs: &RHS, - pp: PairParts, - context: &RewriteContext, - shape: Shape, - separator_place: SeparatorPlace, -) -> Option<String> -where - LHS: Rewrite, - RHS: Rewrite, -{ - let lhs_overhead = match separator_place { - SeparatorPlace::Back => shape.used_width() + pp.prefix.len() + pp.infix.trim_right().len(), - SeparatorPlace::Front => shape.used_width(), - }; - let lhs_shape = Shape { - width: context.budget(lhs_overhead), - ..shape - }; - let lhs_result = lhs.rewrite(context, lhs_shape) - .map(|lhs_str| format!("{}{}", pp.prefix, lhs_str))?; - - // Try to the both lhs and rhs on the same line. - let rhs_orig_result = shape - .offset_left(last_line_width(&lhs_result) + pp.infix.len()) - .and_then(|s| s.sub_width(pp.suffix.len())) - .and_then(|rhs_shape| rhs.rewrite(context, rhs_shape)); - if let Some(ref rhs_result) = rhs_orig_result { - // If the rhs looks like block expression, we allow it to stay on the same line - // with the lhs even if it is multi-lined. - let allow_same_line = rhs_result - .lines() - .next() - .map(|first_line| first_line.ends_with('{')) - .unwrap_or(false); - if !rhs_result.contains('\n') || allow_same_line { - let one_line_width = last_line_width(&lhs_result) + pp.infix.len() - + first_line_width(rhs_result) + pp.suffix.len(); - if one_line_width <= shape.width { - return Some(format!( - "{}{}{}{}", - lhs_result, pp.infix, rhs_result, pp.suffix - )); - } - } - } - - // We have to use multiple lines. - // Re-evaluate the rhs because we have more space now: - let mut rhs_shape = match context.config.indent_style() { - IndentStyle::Visual => shape - .sub_width(pp.suffix.len() + pp.prefix.len())? - .visual_indent(pp.prefix.len()), - IndentStyle::Block => { - // Try to calculate the initial constraint on the right hand side. - let rhs_overhead = shape.rhs_overhead(context.config); - Shape::indented(shape.indent.block_indent(context.config), context.config) - .sub_width(rhs_overhead)? - } - }; - let infix = match separator_place { - SeparatorPlace::Back => pp.infix.trim_right(), - SeparatorPlace::Front => pp.infix.trim_left(), - }; - if separator_place == SeparatorPlace::Front { - rhs_shape = rhs_shape.offset_left(infix.len())?; - } - let rhs_result = rhs.rewrite(context, rhs_shape)?; - let indent_str = rhs_shape.indent.to_string(context.config); - let infix_with_sep = match separator_place { - SeparatorPlace::Back => format!("{}\n{}", infix, indent_str), - SeparatorPlace::Front => format!("\n{}{}", indent_str, infix), - }; - Some(format!( - "{}{}{}{}", - lhs_result, infix_with_sep, rhs_result, pp.suffix - )) -} - -pub fn rewrite_array<T: Rewrite + Spanned + ToExpr>( - exprs: &[&T], - span: Span, - context: &RewriteContext, - shape: Shape, - trailing_comma: bool, -) -> Option<String> { - let bracket_size = if context.config.spaces_within_parens_and_brackets() { - 2 // "[ " - } else { - 1 // "[" - }; - - let nested_shape = match context.config.indent_style() { - IndentStyle::Block => shape - .block() - .block_indent(context.config.tab_spaces()) - .with_max_width(context.config) - .sub_width(1)?, - IndentStyle::Visual => shape - .visual_indent(bracket_size) - .sub_width(bracket_size * 2)?, - }; - - let items = itemize_list( - context.codemap, - exprs.iter(), - "]", - ",", - |item| item.span().lo(), - |item| item.span().hi(), - |item| item.rewrite(context, nested_shape), - span.lo(), - span.hi(), - false, - ).collect::<Vec<_>>(); - - if items.is_empty() { - if context.config.spaces_within_parens_and_brackets() { - return Some("[ ]".to_string()); - } else { - return Some("[]".to_string()); - } - } - - let tactic = array_tactic(context, shape, nested_shape, exprs, &items, bracket_size); - let ends_with_newline = tactic.ends_with_newline(context.config.indent_style()); - - let fmt = ListFormatting { - tactic, - separator: ",", - trailing_separator: if trailing_comma { - SeparatorTactic::Always - } else if context.inside_macro && !exprs.is_empty() { - let ends_with_bracket = context.snippet(span).ends_with(']'); - let bracket_offset = if ends_with_bracket { 1 } else { 0 }; - let snippet = context.snippet(mk_sp(span.lo(), span.hi() - BytePos(bracket_offset))); - let last_char_index = snippet.rfind(|c: char| !c.is_whitespace())?; - if &snippet[last_char_index..last_char_index + 1] == "," { - SeparatorTactic::Always - } else { - SeparatorTactic::Never - } - } else if context.config.indent_style() == IndentStyle::Visual { - SeparatorTactic::Never - } else { - SeparatorTactic::Vertical - }, - separator_place: SeparatorPlace::Back, - shape: nested_shape, - ends_with_newline, - preserve_newline: false, - config: context.config, - }; - let list_str = write_list(&items, &fmt)?; - - let result = if context.config.indent_style() == IndentStyle::Visual - || tactic == DefinitiveListTactic::Horizontal - { - if context.config.spaces_within_parens_and_brackets() && !list_str.is_empty() { - format!("[ {} ]", list_str) - } else { - format!("[{}]", list_str) - } - } else { - format!( - "[\n{}{}\n{}]", - nested_shape.indent.to_string(context.config), - list_str, - shape.block().indent.to_string(context.config) - ) - }; - - Some(result) -} - -fn array_tactic<T: Rewrite + Spanned + ToExpr>( - context: &RewriteContext, - shape: Shape, - nested_shape: Shape, - exprs: &[&T], - items: &[ListItem], - bracket_size: usize, -) -> DefinitiveListTactic { - let has_long_item = items - .iter() - .any(|li| li.item.as_ref().map(|s| s.len() > 10).unwrap_or(false)); - - match context.config.indent_style() { - IndentStyle::Block => { - let tactic = match shape.width.checked_sub(2 * bracket_size) { - Some(width) => { - let tactic = ListTactic::LimitedHorizontalVertical( - context.config.width_heuristics().array_width, - ); - definitive_tactic(items, tactic, Separator::Comma, width) - } - None => DefinitiveListTactic::Vertical, - }; - if tactic == DefinitiveListTactic::Vertical && !has_long_item - && is_every_args_simple(exprs) - { - DefinitiveListTactic::Mixed - } else { - tactic - } - } - IndentStyle::Visual => { - if has_long_item || items.iter().any(ListItem::is_multiline) { - definitive_tactic( - items, - ListTactic::LimitedHorizontalVertical( - context.config.width_heuristics().array_width, - ), - Separator::Comma, - nested_shape.width, - ) - } else { - DefinitiveListTactic::Mixed - } - } - } -} - -fn nop_block_collapse(block_str: Option<String>, budget: usize) -> Option<String> { - debug!("nop_block_collapse {:?} {}", block_str, budget); - block_str.map(|block_str| { - if block_str.starts_with('{') && budget >= 2 - && (block_str[1..].find(|c: char| !c.is_whitespace()).unwrap() == block_str.len() - 2) - { - "{}".to_owned() - } else { - block_str.to_owned() - } - }) -} - -fn rewrite_empty_block( - context: &RewriteContext, - block: &ast::Block, - shape: Shape, -) -> Option<String> { - if block.stmts.is_empty() && !block_contains_comment(block, context.codemap) && shape.width >= 2 - { - return Some("{}".to_owned()); - } - - // If a block contains only a single-line comment, then leave it on one line. - let user_str = context.snippet(block.span); - let user_str = user_str.trim(); - if user_str.starts_with('{') && user_str.ends_with('}') { - let comment_str = user_str[1..user_str.len() - 1].trim(); - if block.stmts.is_empty() && !comment_str.contains('\n') && !comment_str.starts_with("//") - && comment_str.len() + 4 <= shape.width - { - return Some(format!("{{ {} }}", comment_str)); - } - } - - None -} - -fn block_prefix(context: &RewriteContext, block: &ast::Block, shape: Shape) -> Option<String> { - Some(match block.rules { - ast::BlockCheckMode::Unsafe(..) => { - let snippet = context.snippet(block.span); - let open_pos = snippet.find_uncommented("{")?; - // Extract comment between unsafe and block start. - let trimmed = &snippet[6..open_pos].trim(); - - if !trimmed.is_empty() { - // 9 = "unsafe {".len(), 7 = "unsafe ".len() - let budget = shape.width.checked_sub(9)?; - format!( - "unsafe {} ", - rewrite_comment( - trimmed, - true, - Shape::legacy(budget, shape.indent + 7), - context.config, - )? - ) - } else { - "unsafe ".to_owned() - } - } - ast::BlockCheckMode::Default => String::new(), - }) -} - -fn rewrite_single_line_block( - context: &RewriteContext, - prefix: &str, - block: &ast::Block, - shape: Shape, -) -> Option<String> { - if is_simple_block(block, context.codemap) { - let expr_shape = shape.offset_left(last_line_width(prefix))?; - let expr_str = block.stmts[0].rewrite(context, expr_shape)?; - let result = format!("{}{{ {} }}", prefix, expr_str); - if result.len() <= shape.width && !result.contains('\n') { - return Some(result); - } - } - None -} - -pub fn rewrite_block_with_visitor( - context: &RewriteContext, - prefix: &str, - block: &ast::Block, - shape: Shape, - has_braces: bool, -) -> Option<String> { - if let rw @ Some(_) = rewrite_empty_block(context, block, shape) { - return rw; - } - - let mut visitor = FmtVisitor::from_context(context); - visitor.block_indent = shape.indent; - visitor.is_if_else_block = context.is_if_else_block; - match block.rules { - ast::BlockCheckMode::Unsafe(..) => { - let snippet = context.snippet(block.span); - let open_pos = snippet.find_uncommented("{")?; - visitor.last_pos = block.span.lo() + BytePos(open_pos as u32) - } - ast::BlockCheckMode::Default => visitor.last_pos = block.span.lo(), - } - - visitor.visit_block(block, None, has_braces); - Some(format!("{}{}", prefix, visitor.buffer)) -} - -impl Rewrite for ast::Block { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - // shape.width is used only for the single line case: either the empty block `{}`, - // or an unsafe expression `unsafe { e }`. - if let rw @ Some(_) = rewrite_empty_block(context, self, shape) { - return rw; - } - - let prefix = block_prefix(context, self, shape)?; - - let result = rewrite_block_with_visitor(context, &prefix, self, shape, true); - if let Some(ref result_str) = result { - if result_str.lines().count() <= 3 { - if let rw @ Some(_) = rewrite_single_line_block(context, &prefix, self, shape) { - return rw; - } - } - } - - result - } -} - -impl Rewrite for ast::Stmt { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - skip_out_of_file_lines_range!(context, self.span()); - - let result = match self.node { - ast::StmtKind::Local(ref local) => local.rewrite(context, shape), - ast::StmtKind::Expr(ref ex) | ast::StmtKind::Semi(ref ex) => { - let suffix = if semicolon_for_stmt(context, self) { - ";" - } else { - "" - }; - - let shape = shape.sub_width(suffix.len())?; - format_expr(ex, ExprType::Statement, context, shape).map(|s| s + suffix) - } - ast::StmtKind::Mac(..) | ast::StmtKind::Item(..) => None, - }; - result.and_then(|res| recover_comment_removed(res, self.span(), context)) - } -} - -// Rewrite condition if the given expression has one. -pub fn rewrite_cond(context: &RewriteContext, expr: &ast::Expr, shape: Shape) -> Option<String> { - match expr.node { - ast::ExprKind::Match(ref cond, _) => { - // `match `cond` {` - let cond_shape = match context.config.indent_style() { - IndentStyle::Visual => shape.shrink_left(6).and_then(|s| s.sub_width(2))?, - IndentStyle::Block => shape.offset_left(8)?, - }; - cond.rewrite(context, cond_shape) - } - _ => to_control_flow(expr, ExprType::SubExpression).and_then(|control_flow| { - let alt_block_sep = - String::from("\n") + &shape.indent.block_only().to_string(context.config); - control_flow - .rewrite_cond(context, shape, &alt_block_sep) - .and_then(|rw| Some(rw.0)) - }), - } -} - -// Abstraction over control flow expressions -#[derive(Debug)] -struct ControlFlow<'a> { - cond: Option<&'a ast::Expr>, - block: &'a ast::Block, - else_block: Option<&'a ast::Expr>, - label: Option<ast::Label>, - pat: Option<&'a ast::Pat>, - keyword: &'a str, - matcher: &'a str, - connector: &'a str, - allow_single_line: bool, - // True if this is an `if` expression in an `else if` :-( hacky - nested_if: bool, - span: Span, -} - -fn to_control_flow(expr: &ast::Expr, expr_type: ExprType) -> Option<ControlFlow> { - match expr.node { - ast::ExprKind::If(ref cond, ref if_block, ref else_block) => Some(ControlFlow::new_if( - cond, - None, - if_block, - else_block.as_ref().map(|e| &**e), - expr_type == ExprType::SubExpression, - false, - expr.span, - )), - ast::ExprKind::IfLet(ref pat, ref cond, ref if_block, ref else_block) => { - Some(ControlFlow::new_if( - cond, - Some(pat), - if_block, - else_block.as_ref().map(|e| &**e), - expr_type == ExprType::SubExpression, - false, - expr.span, - )) - } - ast::ExprKind::ForLoop(ref pat, ref cond, ref block, label) => { - Some(ControlFlow::new_for(pat, cond, block, label, expr.span)) - } - ast::ExprKind::Loop(ref block, label) => { - Some(ControlFlow::new_loop(block, label, expr.span)) - } - ast::ExprKind::While(ref cond, ref block, label) => { - Some(ControlFlow::new_while(None, cond, block, label, expr.span)) - } - ast::ExprKind::WhileLet(ref pat, ref cond, ref block, label) => Some( - ControlFlow::new_while(Some(pat), cond, block, label, expr.span), - ), - _ => None, - } -} - -impl<'a> ControlFlow<'a> { - fn new_if( - cond: &'a ast::Expr, - pat: Option<&'a ast::Pat>, - block: &'a ast::Block, - else_block: Option<&'a ast::Expr>, - allow_single_line: bool, - nested_if: bool, - span: Span, - ) -> ControlFlow<'a> { - ControlFlow { - cond: Some(cond), - block, - else_block, - label: None, - pat, - keyword: "if", - matcher: match pat { - Some(..) => "let", - None => "", - }, - connector: " =", - allow_single_line, - nested_if, - span, - } - } - - fn new_loop(block: &'a ast::Block, label: Option<ast::Label>, span: Span) -> ControlFlow<'a> { - ControlFlow { - cond: None, - block, - else_block: None, - label, - pat: None, - keyword: "loop", - matcher: "", - connector: "", - allow_single_line: false, - nested_if: false, - span, - } - } - - fn new_while( - pat: Option<&'a ast::Pat>, - cond: &'a ast::Expr, - block: &'a ast::Block, - label: Option<ast::Label>, - span: Span, - ) -> ControlFlow<'a> { - ControlFlow { - cond: Some(cond), - block, - else_block: None, - label, - pat, - keyword: "while", - matcher: match pat { - Some(..) => "let", - None => "", - }, - connector: " =", - allow_single_line: false, - nested_if: false, - span, - } - } - - fn new_for( - pat: &'a ast::Pat, - cond: &'a ast::Expr, - block: &'a ast::Block, - label: Option<ast::Label>, - span: Span, - ) -> ControlFlow<'a> { - ControlFlow { - cond: Some(cond), - block, - else_block: None, - label, - pat: Some(pat), - keyword: "for", - matcher: "", - connector: " in", - allow_single_line: false, - nested_if: false, - span, - } - } - - fn rewrite_single_line( - &self, - pat_expr_str: &str, - context: &RewriteContext, - width: usize, - ) -> Option<String> { - assert!(self.allow_single_line); - let else_block = self.else_block?; - let fixed_cost = self.keyword.len() + " { } else { }".len(); - - if let ast::ExprKind::Block(ref else_node) = else_block.node { - if !is_simple_block(self.block, context.codemap) - || !is_simple_block(else_node, context.codemap) - || pat_expr_str.contains('\n') - { - return None; - } - - let new_width = width.checked_sub(pat_expr_str.len() + fixed_cost)?; - let expr = &self.block.stmts[0]; - let if_str = expr.rewrite(context, Shape::legacy(new_width, Indent::empty()))?; - - let new_width = new_width.checked_sub(if_str.len())?; - let else_expr = &else_node.stmts[0]; - let else_str = else_expr.rewrite(context, Shape::legacy(new_width, Indent::empty()))?; - - if if_str.contains('\n') || else_str.contains('\n') { - return None; - } - - let result = format!( - "{} {} {{ {} }} else {{ {} }}", - self.keyword, pat_expr_str, if_str, else_str - ); - - if result.len() <= width { - return Some(result); - } - } - - None - } -} - -impl<'a> ControlFlow<'a> { - fn rewrite_cond( - &self, - context: &RewriteContext, - shape: Shape, - alt_block_sep: &str, - ) -> Option<(String, usize)> { - // Do not take the rhs overhead from the upper expressions into account - // when rewriting pattern. - let new_width = context - .config - .max_width() - .checked_sub(shape.used_width()) - .unwrap_or(0); - let fresh_shape = Shape { - width: new_width, - ..shape - }; - let constr_shape = if self.nested_if { - // We are part of an if-elseif-else chain. Our constraints are tightened. - // 7 = "} else " .len() - fresh_shape.offset_left(7)? - } else { - fresh_shape - }; - - let label_string = rewrite_label(self.label); - // 1 = space after keyword. - let offset = self.keyword.len() + label_string.len() + 1; - - let pat_expr_string = match self.cond { - Some(cond) => rewrite_pat_expr( - context, - self.pat, - cond, - self.matcher, - self.connector, - self.keyword, - constr_shape, - offset, - )?, - None => String::new(), - }; - - let brace_overhead = - if context.config.control_brace_style() != ControlBraceStyle::AlwaysNextLine { - // 2 = ` {` - 2 - } else { - 0 - }; - let one_line_budget = context - .config - .max_width() - .checked_sub(constr_shape.used_width() + offset + brace_overhead) - .unwrap_or(0); - let force_newline_brace = (pat_expr_string.contains('\n') - || pat_expr_string.len() > one_line_budget) - && !last_line_extendable(&pat_expr_string); - - // Try to format if-else on single line. - if self.allow_single_line - && context - .config - .width_heuristics() - .single_line_if_else_max_width > 0 - { - let trial = self.rewrite_single_line(&pat_expr_string, context, shape.width); - - if let Some(cond_str) = trial { - if cond_str.len() - <= context - .config - .width_heuristics() - .single_line_if_else_max_width - { - return Some((cond_str, 0)); - } - } - } - - let cond_span = if let Some(cond) = self.cond { - cond.span - } else { - mk_sp(self.block.span.lo(), self.block.span.lo()) - }; - - // `for event in event` - // Do not include label in the span. - let lo = self.label.map_or(self.span.lo(), |label| label.span.hi()); - let between_kwd_cond = mk_sp( - context - .codemap - .span_after(mk_sp(lo, self.span.hi()), self.keyword.trim()), - self.pat.map_or(cond_span.lo(), |p| { - if self.matcher.is_empty() { - p.span.lo() - } else { - context.codemap.span_before(self.span, self.matcher.trim()) - } - }), - ); - - let between_kwd_cond_comment = extract_comment(between_kwd_cond, context, shape); - - let after_cond_comment = - extract_comment(mk_sp(cond_span.hi(), self.block.span.lo()), context, shape); - - let block_sep = if self.cond.is_none() && between_kwd_cond_comment.is_some() { - "" - } else if context.config.control_brace_style() == ControlBraceStyle::AlwaysNextLine - || force_newline_brace - { - alt_block_sep - } else { - " " - }; - - let used_width = if pat_expr_string.contains('\n') { - last_line_width(&pat_expr_string) - } else { - // 2 = spaces after keyword and condition. - label_string.len() + self.keyword.len() + pat_expr_string.len() + 2 - }; - - Some(( - format!( - "{}{}{}{}{}", - label_string, - self.keyword, - between_kwd_cond_comment.as_ref().map_or( - if pat_expr_string.is_empty() || pat_expr_string.starts_with('\n') { - "" - } else { - " " - }, - |s| &**s, - ), - pat_expr_string, - after_cond_comment.as_ref().map_or(block_sep, |s| &**s) - ), - used_width, - )) - } -} - -impl<'a> Rewrite for ControlFlow<'a> { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - debug!("ControlFlow::rewrite {:?} {:?}", self, shape); - - let alt_block_sep = String::from("\n") + &shape.indent.to_string(context.config); - let (cond_str, used_width) = self.rewrite_cond(context, shape, &alt_block_sep)?; - // If `used_width` is 0, it indicates that whole control flow is written in a single line. - if used_width == 0 { - return Some(cond_str); - } - - let block_width = shape.width.checked_sub(used_width).unwrap_or(0); - // This is used only for the empty block case: `{}`. So, we use 1 if we know - // we should avoid the single line case. - let block_width = if self.else_block.is_some() || self.nested_if { - min(1, block_width) - } else { - block_width - }; - let block_shape = Shape { - width: block_width, - ..shape - }; - let mut block_context = context.clone(); - block_context.is_if_else_block = self.else_block.is_some(); - let block_str = - rewrite_block_with_visitor(&block_context, "", self.block, block_shape, true)?; - - let mut result = format!("{}{}", cond_str, block_str); - - if let Some(else_block) = self.else_block { - let shape = Shape::indented(shape.indent, context.config); - let mut last_in_chain = false; - let rewrite = match else_block.node { - // If the else expression is another if-else expression, prevent it - // from being formatted on a single line. - // Note how we're passing the original shape, as the - // cost of "else" should not cascade. - ast::ExprKind::IfLet(ref pat, ref cond, ref if_block, ref next_else_block) => { - ControlFlow::new_if( - cond, - Some(pat), - if_block, - next_else_block.as_ref().map(|e| &**e), - false, - true, - mk_sp(else_block.span.lo(), self.span.hi()), - ).rewrite(context, shape) - } - ast::ExprKind::If(ref cond, ref if_block, ref next_else_block) => { - ControlFlow::new_if( - cond, - None, - if_block, - next_else_block.as_ref().map(|e| &**e), - false, - true, - mk_sp(else_block.span.lo(), self.span.hi()), - ).rewrite(context, shape) - } - _ => { - last_in_chain = true; - // When rewriting a block, the width is only used for single line - // blocks, passing 1 lets us avoid that. - let else_shape = Shape { - width: min(1, shape.width), - ..shape - }; - format_expr(else_block, ExprType::Statement, context, else_shape) - } - }; - - let between_kwd_else_block = mk_sp( - self.block.span.hi(), - context - .codemap - .span_before(mk_sp(self.block.span.hi(), else_block.span.lo()), "else"), - ); - let between_kwd_else_block_comment = - extract_comment(between_kwd_else_block, context, shape); - - let after_else = mk_sp( - context - .codemap - .span_after(mk_sp(self.block.span.hi(), else_block.span.lo()), "else"), - else_block.span.lo(), - ); - let after_else_comment = extract_comment(after_else, context, shape); - - let between_sep = match context.config.control_brace_style() { - ControlBraceStyle::AlwaysNextLine | ControlBraceStyle::ClosingNextLine => { - &*alt_block_sep - } - ControlBraceStyle::AlwaysSameLine => " ", - }; - let after_sep = match context.config.control_brace_style() { - ControlBraceStyle::AlwaysNextLine if last_in_chain => &*alt_block_sep, - _ => " ", - }; - - result.push_str(&format!( - "{}else{}", - between_kwd_else_block_comment - .as_ref() - .map_or(between_sep, |s| &**s), - after_else_comment.as_ref().map_or(after_sep, |s| &**s), - )); - result.push_str(&rewrite?); - } - - Some(result) - } -} - -fn rewrite_label(opt_label: Option<ast::Label>) -> Cow<'static, str> { - match opt_label { - Some(label) => Cow::from(format!("{}: ", label.ident)), - None => Cow::from(""), - } -} - -fn extract_comment(span: Span, context: &RewriteContext, shape: Shape) -> Option<String> { - match rewrite_missing_comment(span, shape, context) { - Some(ref comment) if !comment.is_empty() => Some(format!( - "\n{indent}{}\n{indent}", - comment, - indent = shape.indent.to_string(context.config) - )), - _ => None, - } -} - -pub fn block_contains_comment(block: &ast::Block, codemap: &CodeMap) -> bool { - let snippet = codemap.span_to_snippet(block.span).unwrap(); - contains_comment(&snippet) -} - -// Checks that a block contains no statements, an expression and no comments. -// FIXME: incorrectly returns false when comment is contained completely within -// the expression. -pub fn is_simple_block(block: &ast::Block, codemap: &CodeMap) -> bool { - (block.stmts.len() == 1 && stmt_is_expr(&block.stmts[0]) - && !block_contains_comment(block, codemap)) -} - -/// Checks whether a block contains at most one statement or expression, and no comments. -pub fn is_simple_block_stmt(block: &ast::Block, codemap: &CodeMap) -> bool { - block.stmts.len() <= 1 && !block_contains_comment(block, codemap) -} - -/// Checks whether a block contains no statements, expressions, or comments. -pub fn is_empty_block(block: &ast::Block, codemap: &CodeMap) -> bool { - block.stmts.is_empty() && !block_contains_comment(block, codemap) -} - -pub fn stmt_is_expr(stmt: &ast::Stmt) -> bool { - match stmt.node { - ast::StmtKind::Expr(..) => true, - _ => false, - } -} - -pub fn is_unsafe_block(block: &ast::Block) -> bool { - if let ast::BlockCheckMode::Unsafe(..) = block.rules { - true - } else { - false - } -} - -// A simple wrapper type against ast::Arm. Used inside write_list(). -struct ArmWrapper<'a> { - pub arm: &'a ast::Arm, - // True if the arm is the last one in match expression. Used to decide on whether we should add - // trailing comma to the match arm when `config.trailing_comma() == Never`. - pub is_last: bool, -} - -impl<'a> ArmWrapper<'a> { - pub fn new(arm: &'a ast::Arm, is_last: bool) -> ArmWrapper<'a> { - ArmWrapper { arm, is_last } - } -} - -impl<'a> Rewrite for ArmWrapper<'a> { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - rewrite_match_arm(context, self.arm, shape, self.is_last) - } -} - -fn rewrite_match( - context: &RewriteContext, - cond: &ast::Expr, - arms: &[ast::Arm], - shape: Shape, - span: Span, - attrs: &[ast::Attribute], -) -> Option<String> { - // Do not take the rhs overhead from the upper expressions into account - // when rewriting match condition. - let cond_shape = Shape { - width: context.budget(shape.used_width()), - ..shape - }; - // 6 = `match ` - let cond_shape = match context.config.indent_style() { - IndentStyle::Visual => cond_shape.shrink_left(6)?, - IndentStyle::Block => cond_shape.offset_left(6)?, - }; - let cond_str = cond.rewrite(context, cond_shape)?; - let alt_block_sep = String::from("\n") + &shape.indent.to_string(context.config); - let block_sep = match context.config.control_brace_style() { - ControlBraceStyle::AlwaysNextLine => &alt_block_sep, - _ if last_line_extendable(&cond_str) => " ", - // 2 = ` {` - _ if cond_str.contains('\n') || cond_str.len() + 2 > cond_shape.width => &alt_block_sep, - _ => " ", - }; - - let nested_indent_str = shape - .indent - .block_indent(context.config) - .to_string(context.config); - // Inner attributes. - let inner_attrs = &inner_attributes(attrs); - let inner_attrs_str = if inner_attrs.is_empty() { - String::new() - } else { - inner_attrs - .rewrite(context, shape) - .map(|s| format!("{}{}\n", nested_indent_str, s))? - }; - - let open_brace_pos = if inner_attrs.is_empty() { - let hi = if arms.is_empty() { - span.hi() - } else { - arms[0].span().lo() - }; - context.codemap.span_after(mk_sp(cond.span.hi(), hi), "{") - } else { - inner_attrs[inner_attrs.len() - 1].span().hi() - }; - - if arms.is_empty() { - let snippet = context.snippet(mk_sp(open_brace_pos, span.hi() - BytePos(1))); - if snippet.trim().is_empty() { - Some(format!("match {} {{}}", cond_str)) - } else { - // Empty match with comments or inner attributes? We are not going to bother, sorry ;) - Some(context.snippet(span).to_owned()) - } - } else { - Some(format!( - "match {}{}{{\n{}{}{}\n{}}}", - cond_str, - block_sep, - inner_attrs_str, - nested_indent_str, - rewrite_match_arms(context, arms, shape, span, open_brace_pos)?, - shape.indent.to_string(context.config), - )) - } -} - -fn arm_comma(config: &Config, body: &ast::Expr, is_last: bool) -> &'static str { - if is_last && config.trailing_comma() == SeparatorTactic::Never { - "" - } else if config.match_block_trailing_comma() { - "," - } else if let ast::ExprKind::Block(ref block) = body.node { - if let ast::BlockCheckMode::Default = block.rules { - "" - } else { - "," - } - } else { - "," - } -} - -fn rewrite_match_arms( - context: &RewriteContext, - arms: &[ast::Arm], - shape: Shape, - span: Span, - open_brace_pos: BytePos, -) -> Option<String> { - let arm_shape = shape - .block_indent(context.config.tab_spaces()) - .with_max_width(context.config); - - let arm_len = arms.len(); - let is_last_iter = repeat(false) - .take(arm_len.checked_sub(1).unwrap_or(0)) - .chain(repeat(true)); - let items = itemize_list( - context.codemap, - arms.iter() - .zip(is_last_iter) - .map(|(arm, is_last)| ArmWrapper::new(arm, is_last)), - "}", - "|", - |arm| arm.arm.span().lo(), - |arm| arm.arm.span().hi(), - |arm| arm.rewrite(context, arm_shape), - open_brace_pos, - span.hi(), - false, - ); - let arms_vec: Vec<_> = items.collect(); - let fmt = ListFormatting { - tactic: DefinitiveListTactic::Vertical, - // We will add/remove commas inside `arm.rewrite()`, and hence no separator here. - separator: "", - trailing_separator: SeparatorTactic::Never, - separator_place: SeparatorPlace::Back, - shape: arm_shape, - ends_with_newline: true, - preserve_newline: true, - config: context.config, - }; - - write_list(&arms_vec, &fmt) -} - -fn rewrite_match_arm( - context: &RewriteContext, - arm: &ast::Arm, - shape: Shape, - is_last: bool, -) -> Option<String> { - let (missing_span, attrs_str) = if !arm.attrs.is_empty() { - if contains_skip(&arm.attrs) { - let (_, body) = flatten_arm_body(context, &arm.body); - // `arm.span()` does not include trailing comma, add it manually. - return Some(format!( - "{}{}", - context.snippet(arm.span()), - arm_comma(context.config, body, is_last), - )); - } - let missing_span = mk_sp( - arm.attrs[arm.attrs.len() - 1].span.hi(), - arm.pats[0].span.lo(), - ); - (missing_span, arm.attrs.rewrite(context, shape)?) - } else { - (mk_sp(arm.span().lo(), arm.span().lo()), String::new()) - }; - let pats_str = - rewrite_match_pattern(context, &arm.pats, &arm.guard, shape).and_then(|pats_str| { - combine_strs_with_missing_comments( - context, - &attrs_str, - &pats_str, - missing_span, - shape, - false, - ) - })?; - rewrite_match_body( - context, - &arm.body, - &pats_str, - shape, - arm.guard.is_some(), - is_last, - ) -} - -/// Returns true if the given pattern is short. A short pattern is defined by the following grammer: -/// -/// [small, ntp]: -/// - single token -/// - `&[single-line, ntp]` -/// -/// [small]: -/// - `[small, ntp]` -/// - unary tuple constructor `([small, ntp])` -/// - `&[small]` -fn is_short_pattern(pat: &ast::Pat, pat_str: &str) -> bool { - // We also require that the pattern is reasonably 'small' with its literal width. - pat_str.len() <= 20 && !pat_str.contains('\n') && is_short_pattern_inner(pat) -} - -fn is_short_pattern_inner(pat: &ast::Pat) -> bool { - match pat.node { - ast::PatKind::Wild | ast::PatKind::Lit(_) => true, - ast::PatKind::Ident(_, _, ref pat) => pat.is_none(), - ast::PatKind::Struct(..) - | ast::PatKind::Mac(..) - | ast::PatKind::Slice(..) - | ast::PatKind::Path(..) - | ast::PatKind::Range(..) => false, - ast::PatKind::Tuple(ref subpats, _) => subpats.len() <= 1, - ast::PatKind::TupleStruct(ref path, ref subpats, _) => { - path.segments.len() <= 1 && subpats.len() <= 1 - } - ast::PatKind::Box(ref p) | ast::PatKind::Ref(ref p, _) => is_short_pattern_inner(&*p), - } -} - -fn rewrite_match_pattern( - context: &RewriteContext, - pats: &[ptr::P<ast::Pat>], - guard: &Option<ptr::P<ast::Expr>>, - shape: Shape, -) -> Option<String> { - // Patterns - // 5 = ` => {` - let pat_shape = shape.sub_width(5)?; - - let pat_strs = pats.iter() - .map(|p| p.rewrite(context, pat_shape)) - .collect::<Option<Vec<_>>>()?; - - let use_mixed_layout = pats.iter() - .zip(pat_strs.iter()) - .all(|(pat, pat_str)| is_short_pattern(pat, pat_str)); - let items: Vec<_> = pat_strs.into_iter().map(ListItem::from_str).collect(); - let tactic = if use_mixed_layout { - DefinitiveListTactic::Mixed - } else { - definitive_tactic( - &items, - ListTactic::HorizontalVertical, - Separator::VerticalBar, - pat_shape.width, - ) - }; - let fmt = ListFormatting { - tactic, - separator: " |", - trailing_separator: SeparatorTactic::Never, - separator_place: context.config.binop_separator(), - shape: pat_shape, - ends_with_newline: false, - preserve_newline: false, - config: context.config, - }; - let pats_str = write_list(&items, &fmt)?; - - // Guard - let guard_str = rewrite_guard(context, guard, shape, trimmed_last_line_width(&pats_str))?; - - Some(format!("{}{}", pats_str, guard_str)) -} - -// (extend, body) -// @extend: true if the arm body can be put next to `=>` -// @body: flattened body, if the body is block with a single expression -fn flatten_arm_body<'a>(context: &'a RewriteContext, body: &'a ast::Expr) -> (bool, &'a ast::Expr) { - match body.node { - ast::ExprKind::Block(ref block) - if !is_unsafe_block(block) && is_simple_block(block, context.codemap) => - { - if let ast::StmtKind::Expr(ref expr) = block.stmts[0].node { - ( - !context.config.force_multiline_blocks() && can_extend_match_arm_body(expr), - &*expr, - ) - } else { - (false, &*body) - } - } - _ => ( - !context.config.force_multiline_blocks() && body.can_be_overflowed(context, 1), - &*body, - ), - } -} - -fn rewrite_match_body( - context: &RewriteContext, - body: &ptr::P<ast::Expr>, - pats_str: &str, - shape: Shape, - has_guard: bool, - is_last: bool, -) -> Option<String> { - let (extend, body) = flatten_arm_body(context, body); - let (is_block, is_empty_block) = if let ast::ExprKind::Block(ref block) = body.node { - (true, is_empty_block(block, context.codemap)) - } else { - (false, false) - }; - - let comma = arm_comma(context.config, body, is_last); - let alt_block_sep = String::from("\n") + &shape.indent.to_string(context.config); - let alt_block_sep = alt_block_sep.as_str(); - - let combine_orig_body = |body_str: &str| { - let block_sep = match context.config.control_brace_style() { - ControlBraceStyle::AlwaysNextLine if is_block => alt_block_sep, - _ => " ", - }; - - Some(format!("{} =>{}{}{}", pats_str, block_sep, body_str, comma)) - }; - - let forbid_same_line = has_guard && pats_str.contains('\n') && !is_empty_block; - let next_line_indent = if !is_block || is_empty_block { - shape.indent.block_indent(context.config) - } else { - shape.indent - }; - let combine_next_line_body = |body_str: &str| { - if is_block { - return Some(format!( - "{} =>\n{}{}", - pats_str, - next_line_indent.to_string(context.config), - body_str - )); - } - - let indent_str = shape.indent.to_string(context.config); - let nested_indent_str = next_line_indent.to_string(context.config); - let (body_prefix, body_suffix) = if context.config.match_arm_blocks() { - let comma = if context.config.match_block_trailing_comma() { - "," - } else { - "" - }; - ("{", format!("\n{}}}{}", indent_str, comma)) - } else { - ("", String::from(",")) - }; - - let block_sep = match context.config.control_brace_style() { - ControlBraceStyle::AlwaysNextLine => format!("{}{}\n", alt_block_sep, body_prefix), - _ if body_prefix.is_empty() => "\n".to_owned(), - _ if forbid_same_line => format!("{}{}\n", alt_block_sep, body_prefix), - _ => format!(" {}\n", body_prefix), - } + &nested_indent_str; - - Some(format!( - "{} =>{}{}{}", - pats_str, block_sep, body_str, body_suffix - )) - }; - - // Let's try and get the arm body on the same line as the condition. - // 4 = ` => `.len() - let orig_body_shape = shape - .offset_left(extra_offset(pats_str, shape) + 4) - .and_then(|shape| shape.sub_width(comma.len())); - let orig_body = if let Some(body_shape) = orig_body_shape { - let rewrite = nop_block_collapse( - format_expr(body, ExprType::Statement, context, body_shape), - body_shape.width, - ); - - match rewrite { - Some(ref body_str) - if !forbid_same_line - && (is_block - || (!body_str.contains('\n') && body_str.len() <= body_shape.width)) => - { - return combine_orig_body(body_str); - } - _ => rewrite, - } - } else { - None - }; - let orig_budget = orig_body_shape.map_or(0, |shape| shape.width); - - // Try putting body on the next line and see if it looks better. - let next_line_body_shape = Shape::indented(next_line_indent, context.config); - let next_line_body = nop_block_collapse( - format_expr(body, ExprType::Statement, context, next_line_body_shape), - next_line_body_shape.width, - ); - match (orig_body, next_line_body) { - (Some(ref orig_str), Some(ref next_line_str)) - if forbid_same_line || prefer_next_line(orig_str, next_line_str) => - { - combine_next_line_body(next_line_str) - } - (Some(ref orig_str), _) if extend && first_line_width(orig_str) <= orig_budget => { - combine_orig_body(orig_str) - } - (Some(ref orig_str), Some(ref next_line_str)) if orig_str.contains('\n') => { - combine_next_line_body(next_line_str) - } - (None, Some(ref next_line_str)) => combine_next_line_body(next_line_str), - (None, None) => None, - (Some(ref orig_str), _) => combine_orig_body(orig_str), - } -} - -// The `if ...` guard on a match arm. -fn rewrite_guard( - context: &RewriteContext, - guard: &Option<ptr::P<ast::Expr>>, - shape: Shape, - // The amount of space used up on this line for the pattern in - // the arm (excludes offset). - pattern_width: usize, -) -> Option<String> { - if let Some(ref guard) = *guard { - // First try to fit the guard string on the same line as the pattern. - // 4 = ` if `, 5 = ` => {` - let cond_shape = shape - .offset_left(pattern_width + 4) - .and_then(|s| s.sub_width(5)); - if let Some(cond_shape) = cond_shape { - if let Some(cond_str) = guard.rewrite(context, cond_shape) { - if !cond_str.contains('\n') || pattern_width <= context.config.tab_spaces() { - return Some(format!(" if {}", cond_str)); - } - } - } - - // Not enough space to put the guard after the pattern, try a newline. - // 3 = `if `, 5 = ` => {` - let cond_shape = Shape::indented(shape.indent.block_indent(context.config), context.config) - .offset_left(3) - .and_then(|s| s.sub_width(5)); - if let Some(cond_shape) = cond_shape { - if let Some(cond_str) = guard.rewrite(context, cond_shape) { - return Some(format!( - "\n{}if {}", - cond_shape.indent.to_string(context.config), - cond_str - )); - } - } - - None - } else { - Some(String::new()) - } -} - -fn rewrite_pat_expr( - context: &RewriteContext, - pat: Option<&ast::Pat>, - expr: &ast::Expr, - matcher: &str, - // Connecting piece between pattern and expression, - // *without* trailing space. - connector: &str, - keyword: &str, - shape: Shape, - offset: usize, -) -> Option<String> { - debug!("rewrite_pat_expr {:?} {:?} {:?}", shape, pat, expr); - let cond_shape = shape.offset_left(offset)?; - if let Some(pat) = pat { - let matcher = if matcher.is_empty() { - matcher.to_owned() - } else { - format!("{} ", matcher) - }; - let pat_shape = cond_shape - .offset_left(matcher.len())? - .sub_width(connector.len())?; - let pat_string = pat.rewrite(context, pat_shape)?; - let result = format!("{}{}{}", matcher, pat_string, connector); - return rewrite_assign_rhs(context, result, expr, cond_shape); - } - - let expr_rw = expr.rewrite(context, cond_shape); - // The expression may (partially) fit on the current line. - // We do not allow splitting between `if` and condition. - if keyword == "if" || expr_rw.is_some() { - return expr_rw; - } - - // The expression won't fit on the current line, jump to next. - let nested_shape = shape - .block_indent(context.config.tab_spaces()) - .with_max_width(context.config); - let nested_indent_str = nested_shape.indent.to_string(context.config); - expr.rewrite(context, nested_shape) - .map(|expr_rw| format!("\n{}{}", nested_indent_str, expr_rw)) -} - -fn can_extend_match_arm_body(body: &ast::Expr) -> bool { - match body.node { - // We do not allow `if` to stay on the same line, since we could easily mistake - // `pat => if cond { ... }` and `pat if cond => { ... }`. - ast::ExprKind::If(..) | ast::ExprKind::IfLet(..) => false, - ast::ExprKind::ForLoop(..) - | ast::ExprKind::Loop(..) - | ast::ExprKind::While(..) - | ast::ExprKind::WhileLet(..) - | ast::ExprKind::Match(..) - | ast::ExprKind::Block(..) - | ast::ExprKind::Closure(..) - | ast::ExprKind::Array(..) - | ast::ExprKind::Call(..) - | ast::ExprKind::MethodCall(..) - | ast::ExprKind::Mac(..) - | ast::ExprKind::Struct(..) - | ast::ExprKind::Tup(..) => true, - ast::ExprKind::AddrOf(_, ref expr) - | ast::ExprKind::Box(ref expr) - | ast::ExprKind::Try(ref expr) - | ast::ExprKind::Unary(_, ref expr) - | ast::ExprKind::Cast(ref expr, _) => can_extend_match_arm_body(expr), - _ => false, - } -} - -pub fn rewrite_literal(context: &RewriteContext, l: &ast::Lit, shape: Shape) -> Option<String> { - match l.node { - ast::LitKind::Str(_, ast::StrStyle::Cooked) => rewrite_string_lit(context, l.span, shape), - _ => wrap_str( - context.snippet(l.span).to_owned(), - context.config.max_width(), - shape, - ), - } -} - -fn rewrite_string_lit(context: &RewriteContext, span: Span, shape: Shape) -> Option<String> { - let string_lit = context.snippet(span); - - if !context.config.format_strings() { - if string_lit - .lines() - .rev() - .skip(1) - .all(|line| line.ends_with('\\')) - { - let new_indent = shape.visual_indent(1).indent; - let indented_string_lit = String::from( - string_lit - .lines() - .map(|line| { - format!( - "{}{}", - new_indent.to_string(context.config), - line.trim_left() - ) - }) - .collect::<Vec<_>>() - .join("\n") - .trim_left(), - ); - return wrap_str(indented_string_lit, context.config.max_width(), shape); - } else { - return wrap_str(string_lit.to_owned(), context.config.max_width(), shape); - } - } - - // Remove the quote characters. - let str_lit = &string_lit[1..string_lit.len() - 1]; - - rewrite_string( - str_lit, - &StringFormat::new(shape.visual_indent(0), context.config), - None, - ) -} - -/// A list of `format!`-like macros, that take a long format string and a list of arguments to -/// format. -/// -/// Organized as a list of `(&str, usize)` tuples, giving the name of the macro and the number of -/// arguments before the format string (none for `format!("format", ...)`, one for `assert!(result, -/// "format", ...)`, two for `assert_eq!(left, right, "format", ...)`). -const SPECIAL_MACRO_WHITELIST: &[(&str, usize)] = &[ - // format! like macros - // From the Rust Standard Library. - ("eprint!", 0), - ("eprintln!", 0), - ("format!", 0), - ("format_args!", 0), - ("print!", 0), - ("println!", 0), - ("panic!", 0), - ("unreachable!", 0), - // From the `log` crate. - ("debug!", 0), - ("error!", 0), - ("info!", 0), - ("warn!", 0), - // write! like macros - ("assert!", 1), - ("debug_assert!", 1), - ("write!", 1), - ("writeln!", 1), - // assert_eq! like macros - ("assert_eq!", 2), - ("assert_ne!", 2), - ("debug_assert_eq!", 2), - ("debug_assert_ne!", 2), -]; - -pub fn rewrite_call( - context: &RewriteContext, - callee: &str, - args: &[ptr::P<ast::Expr>], - span: Span, - shape: Shape, -) -> Option<String> { - let force_trailing_comma = if context.inside_macro { - span_ends_with_comma(context, span) - } else { - false - }; - rewrite_call_inner( - context, - callee, - &ptr_vec_to_ref_vec(args), - span, - shape, - context.config.width_heuristics().fn_call_width, - force_trailing_comma, - ) -} - -pub fn rewrite_call_inner<'a, T>( - context: &RewriteContext, - callee_str: &str, - args: &[&T], - span: Span, - shape: Shape, - args_max_width: usize, - force_trailing_comma: bool, -) -> Option<String> -where - T: Rewrite + Spanned + ToExpr + 'a, -{ - // 2 = `( `, 1 = `(` - let paren_overhead = if context.config.spaces_within_parens_and_brackets() { - 2 - } else { - 1 - }; - let used_width = extra_offset(callee_str, shape); - let one_line_width = shape.width.checked_sub(used_width + 2 * paren_overhead)?; - - // 1 = "(" or ")" - let one_line_shape = shape - .offset_left(last_line_width(callee_str) + 1)? - .sub_width(1)?; - let nested_shape = shape_from_indent_style( - context, - shape, - used_width + 2 * paren_overhead, - used_width + paren_overhead, - )?; - - let span_lo = context.codemap.span_after(span, "("); - let args_span = mk_sp(span_lo, span.hi()); - - let (extendable, list_str) = rewrite_call_args( - context, - args, - args_span, - one_line_shape, - nested_shape, - one_line_width, - args_max_width, - force_trailing_comma, - callee_str, - )?; - - if !context.use_block_indent() && need_block_indent(&list_str, nested_shape) && !extendable { - let mut new_context = context.clone(); - new_context.use_block = true; - return rewrite_call_inner( - &new_context, - callee_str, - args, - span, - shape, - args_max_width, - force_trailing_comma, - ); - } - - let args_shape = shape.sub_width(last_line_width(callee_str))?; - Some(format!( - "{}{}", - callee_str, - wrap_args_with_parens(context, &list_str, extendable, args_shape, nested_shape) - )) -} - -fn need_block_indent(s: &str, shape: Shape) -> bool { - s.lines().skip(1).any(|s| { - s.find(|c| !char::is_whitespace(c)) - .map_or(false, |w| w + 1 < shape.indent.width()) - }) -} - -fn rewrite_call_args<'a, T>( - context: &RewriteContext, - args: &[&T], - span: Span, - one_line_shape: Shape, - nested_shape: Shape, - one_line_width: usize, - args_max_width: usize, - force_trailing_comma: bool, - callee_str: &str, -) -> Option<(bool, String)> -where - T: Rewrite + Spanned + ToExpr + 'a, -{ - let items = itemize_list( - context.codemap, - args.iter(), - ")", - ",", - |item| item.span().lo(), - |item| item.span().hi(), - |item| item.rewrite(context, nested_shape), - span.lo(), - span.hi(), - true, - ); - let mut item_vec: Vec<_> = items.collect(); - - // Try letting the last argument overflow to the next line with block - // indentation. If its first line fits on one line with the other arguments, - // we format the function arguments horizontally. - let tactic = try_overflow_last_arg( - context, - &mut item_vec, - &args[..], - one_line_shape, - nested_shape, - one_line_width, - args_max_width, - callee_str, - ); - - let fmt = ListFormatting { - tactic, - separator: ",", - trailing_separator: if force_trailing_comma { - SeparatorTactic::Always - } else if context.inside_macro || !context.use_block_indent() { - SeparatorTactic::Never - } else { - context.config.trailing_comma() - }, - separator_place: SeparatorPlace::Back, - shape: nested_shape, - ends_with_newline: context.use_block_indent() && tactic == DefinitiveListTactic::Vertical, - preserve_newline: false, - config: context.config, - }; - - write_list(&item_vec, &fmt) - .map(|args_str| (tactic == DefinitiveListTactic::Horizontal, args_str)) -} - -fn try_overflow_last_arg<'a, T>( - context: &RewriteContext, - item_vec: &mut Vec<ListItem>, - args: &[&T], - one_line_shape: Shape, - nested_shape: Shape, - one_line_width: usize, - args_max_width: usize, - callee_str: &str, -) -> DefinitiveListTactic -where - T: Rewrite + Spanned + ToExpr + 'a, -{ - // 1 = "(" - let combine_arg_with_callee = - callee_str.len() + 1 <= context.config.tab_spaces() && args.len() == 1; - let overflow_last = combine_arg_with_callee || can_be_overflowed(context, args); - - // Replace the last item with its first line to see if it fits with - // first arguments. - let placeholder = if overflow_last { - let mut context = context.clone(); - if !combine_arg_with_callee { - if let Some(expr) = args[args.len() - 1].to_expr() { - if let ast::ExprKind::MethodCall(..) = expr.node { - context.force_one_line_chain = true; - } - } - } - last_arg_shape(args, item_vec, one_line_shape, args_max_width).and_then(|arg_shape| { - rewrite_last_arg_with_overflow(&context, args, &mut item_vec[args.len() - 1], arg_shape) - }) - } else { - None - }; - - let mut tactic = definitive_tactic( - &*item_vec, - ListTactic::LimitedHorizontalVertical(args_max_width), - Separator::Comma, - one_line_width, - ); - - // Replace the stub with the full overflowing last argument if the rewrite - // succeeded and its first line fits with the other arguments. - match (overflow_last, tactic, placeholder) { - (true, DefinitiveListTactic::Horizontal, Some(ref overflowed)) if args.len() == 1 => { - // When we are rewriting a nested function call, we restrict the - // bugdet for the inner function to avoid them being deeply nested. - // However, when the inner function has a prefix or a suffix - // (e.g. `foo() as u32`), this budget reduction may produce poorly - // formatted code, where a prefix or a suffix being left on its own - // line. Here we explicitlly check those cases. - if count_newlines(overflowed) == 1 { - let rw = args.last() - .and_then(|last_arg| last_arg.rewrite(context, nested_shape)); - let no_newline = rw.as_ref().map_or(false, |s| !s.contains('\n')); - if no_newline { - item_vec[args.len() - 1].item = rw; - } else { - item_vec[args.len() - 1].item = Some(overflowed.to_owned()); - } - } else { - item_vec[args.len() - 1].item = Some(overflowed.to_owned()); - } - } - (true, DefinitiveListTactic::Horizontal, placeholder @ Some(..)) => { - item_vec[args.len() - 1].item = placeholder; - } - _ if args.len() >= 1 => { - item_vec[args.len() - 1].item = args.last() - .and_then(|last_arg| last_arg.rewrite(context, nested_shape)); - - let default_tactic = || { - definitive_tactic( - &*item_vec, - ListTactic::LimitedHorizontalVertical(args_max_width), - Separator::Comma, - one_line_width, - ) - }; - - // Use horizontal layout for a function with a single argument as long as - // everything fits in a single line. - if args.len() == 1 - && args_max_width != 0 // Vertical layout is forced. - && !item_vec[0].has_comment() - && !item_vec[0].inner_as_ref().contains('\n') - && ::lists::total_item_width(&item_vec[0]) <= one_line_width - { - tactic = DefinitiveListTactic::Horizontal; - } else { - tactic = default_tactic(); - - if tactic == DefinitiveListTactic::Vertical { - if let Some((all_simple, num_args_before)) = - maybe_get_args_offset(callee_str, args) - { - let one_line = all_simple - && definitive_tactic( - &item_vec[..num_args_before], - ListTactic::HorizontalVertical, - Separator::Comma, - nested_shape.width, - ) == DefinitiveListTactic::Horizontal - && definitive_tactic( - &item_vec[num_args_before + 1..], - ListTactic::HorizontalVertical, - Separator::Comma, - nested_shape.width, - ) == DefinitiveListTactic::Horizontal; - - if one_line { - tactic = DefinitiveListTactic::SpecialMacro(num_args_before); - }; - } - } - } - } - _ => (), - } - - tactic -} - -fn is_simple_arg(expr: &ast::Expr) -> bool { - match expr.node { - ast::ExprKind::Lit(..) => true, - ast::ExprKind::Path(ref qself, ref path) => qself.is_none() && path.segments.len() <= 1, - ast::ExprKind::AddrOf(_, ref expr) - | ast::ExprKind::Box(ref expr) - | ast::ExprKind::Cast(ref expr, _) - | ast::ExprKind::Field(ref expr, _) - | ast::ExprKind::Try(ref expr) - | ast::ExprKind::TupField(ref expr, _) - | ast::ExprKind::Unary(_, ref expr) => is_simple_arg(expr), - ast::ExprKind::Index(ref lhs, ref rhs) | ast::ExprKind::Repeat(ref lhs, ref rhs) => { - is_simple_arg(lhs) && is_simple_arg(rhs) - } - _ => false, - } -} - -fn is_every_args_simple<T: ToExpr>(lists: &[&T]) -> bool { - lists - .iter() - .all(|arg| arg.to_expr().map_or(false, is_simple_arg)) -} - -/// In case special-case style is required, returns an offset from which we start horizontal layout. -fn maybe_get_args_offset<T: ToExpr>(callee_str: &str, args: &[&T]) -> Option<(bool, usize)> { - if let Some(&(_, num_args_before)) = SPECIAL_MACRO_WHITELIST - .iter() - .find(|&&(s, _)| s == callee_str) - { - let all_simple = args.len() > num_args_before && is_every_args_simple(args); - - Some((all_simple, num_args_before)) - } else { - None - } -} - -/// Returns a shape for the last argument which is going to be overflowed. -fn last_arg_shape<T>( - lists: &[&T], - items: &[ListItem], - shape: Shape, - args_max_width: usize, -) -> Option<Shape> -where - T: Rewrite + Spanned + ToExpr, -{ - let is_nested_call = lists - .iter() - .next() - .and_then(|item| item.to_expr()) - .map_or(false, is_nested_call); - if items.len() == 1 && !is_nested_call { - return Some(shape); - } - let offset = items.iter().rev().skip(1).fold(0, |acc, i| { - // 2 = ", " - acc + 2 + i.inner_as_ref().len() - }); - Shape { - width: min(args_max_width, shape.width), - ..shape - }.offset_left(offset) -} - -fn rewrite_last_arg_with_overflow<'a, T>( - context: &RewriteContext, - args: &[&T], - last_item: &mut ListItem, - shape: Shape, -) -> Option<String> -where - T: Rewrite + Spanned + ToExpr + 'a, -{ - let last_arg = args[args.len() - 1]; - let rewrite = if let Some(expr) = last_arg.to_expr() { - match expr.node { - // When overflowing the closure which consists of a single control flow expression, - // force to use block if its condition uses multi line. - ast::ExprKind::Closure(..) => { - // If the argument consists of multiple closures, we do not overflow - // the last closure. - if closures::args_have_many_closure(args) { - None - } else { - closures::rewrite_last_closure(context, expr, shape) - } - } - _ => expr.rewrite(context, shape), - } - } else { - last_arg.rewrite(context, shape) - }; - - if let Some(rewrite) = rewrite { - let rewrite_first_line = Some(rewrite[..first_line_width(&rewrite)].to_owned()); - last_item.item = rewrite_first_line; - Some(rewrite) - } else { - None - } -} - -fn can_be_overflowed<'a, T>(context: &RewriteContext, args: &[&T]) -> bool -where - T: Rewrite + Spanned + ToExpr + 'a, -{ - args.last() - .map_or(false, |x| x.can_be_overflowed(context, args.len())) -} - -pub fn can_be_overflowed_expr(context: &RewriteContext, expr: &ast::Expr, args_len: usize) -> bool { - match expr.node { - ast::ExprKind::Match(..) => { - (context.use_block_indent() && args_len == 1) - || (context.config.indent_style() == IndentStyle::Visual && args_len > 1) - } - ast::ExprKind::If(..) - | ast::ExprKind::IfLet(..) - | ast::ExprKind::ForLoop(..) - | ast::ExprKind::Loop(..) - | ast::ExprKind::While(..) - | ast::ExprKind::WhileLet(..) => { - context.config.combine_control_expr() && context.use_block_indent() && args_len == 1 - } - ast::ExprKind::Block(..) | ast::ExprKind::Closure(..) => { - context.use_block_indent() - || context.config.indent_style() == IndentStyle::Visual && args_len > 1 - } - ast::ExprKind::Array(..) - | ast::ExprKind::Call(..) - | ast::ExprKind::Mac(..) - | ast::ExprKind::MethodCall(..) - | ast::ExprKind::Struct(..) - | ast::ExprKind::Tup(..) => context.use_block_indent() && args_len == 1, - ast::ExprKind::AddrOf(_, ref expr) - | ast::ExprKind::Box(ref expr) - | ast::ExprKind::Try(ref expr) - | ast::ExprKind::Unary(_, ref expr) - | ast::ExprKind::Cast(ref expr, _) => can_be_overflowed_expr(context, expr, args_len), - _ => false, - } -} - -fn is_nested_call(expr: &ast::Expr) -> bool { - match expr.node { - ast::ExprKind::Call(..) | ast::ExprKind::Mac(..) => true, - ast::ExprKind::AddrOf(_, ref expr) - | ast::ExprKind::Box(ref expr) - | ast::ExprKind::Try(ref expr) - | ast::ExprKind::Unary(_, ref expr) - | ast::ExprKind::Cast(ref expr, _) => is_nested_call(expr), - _ => false, - } -} - -pub fn wrap_args_with_parens( - context: &RewriteContext, - args_str: &str, - is_extendable: bool, - shape: Shape, - nested_shape: Shape, -) -> String { - if !context.use_block_indent() - || (context.inside_macro && !args_str.contains('\n') - && args_str.len() + paren_overhead(context) <= shape.width) || is_extendable - { - if context.config.spaces_within_parens_and_brackets() && !args_str.is_empty() { - format!("( {} )", args_str) - } else { - format!("({})", args_str) - } - } else { - format!( - "(\n{}{}\n{})", - nested_shape.indent.to_string(context.config), - args_str, - shape.block().indent.to_string(context.config) - ) - } -} - -/// Return true if a function call or a method call represented by the given span ends with a -/// trailing comma. This function is used when rewriting macro, as adding or removing a trailing -/// comma from macro can potentially break the code. -fn span_ends_with_comma(context: &RewriteContext, span: Span) -> bool { - let mut encountered_closing_paren = false; - for c in context.snippet(span).chars().rev() { - match c { - ',' => return true, - ')' => if encountered_closing_paren { - return false; - } else { - encountered_closing_paren = true; - }, - _ if c.is_whitespace() => continue, - _ => return false, - } - } - false -} - -fn rewrite_paren(context: &RewriteContext, subexpr: &ast::Expr, shape: Shape) -> Option<String> { - debug!("rewrite_paren, shape: {:?}", shape); - let total_paren_overhead = paren_overhead(context); - let paren_overhead = total_paren_overhead / 2; - let sub_shape = shape - .offset_left(paren_overhead) - .and_then(|s| s.sub_width(paren_overhead))?; - - let paren_wrapper = |s: &str| { - if context.config.spaces_within_parens_and_brackets() && !s.is_empty() { - format!("( {} )", s) - } else { - format!("({})", s) - } - }; - - let subexpr_str = subexpr.rewrite(context, sub_shape)?; - debug!("rewrite_paren, subexpr_str: `{:?}`", subexpr_str); - - if subexpr_str.contains('\n') - || first_line_width(&subexpr_str) + total_paren_overhead <= shape.width - { - Some(paren_wrapper(&subexpr_str)) - } else { - None - } -} - -fn rewrite_index( - expr: &ast::Expr, - index: &ast::Expr, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - let expr_str = expr.rewrite(context, shape)?; - - let (lbr, rbr) = if context.config.spaces_within_parens_and_brackets() { - ("[ ", " ]") - } else { - ("[", "]") - }; - - let offset = last_line_width(&expr_str) + lbr.len(); - let rhs_overhead = shape.rhs_overhead(context.config); - let index_shape = if expr_str.contains('\n') { - Shape::legacy(context.config.max_width(), shape.indent) - .offset_left(offset) - .and_then(|shape| shape.sub_width(rbr.len() + rhs_overhead)) - } else { - shape.visual_indent(offset).sub_width(offset + rbr.len()) - }; - let orig_index_rw = index_shape.and_then(|s| index.rewrite(context, s)); - - // Return if index fits in a single line. - match orig_index_rw { - Some(ref index_str) if !index_str.contains('\n') => { - return Some(format!("{}{}{}{}", expr_str, lbr, index_str, rbr)); - } - _ => (), - } - - // Try putting index on the next line and see if it fits in a single line. - let indent = shape.indent.block_indent(context.config); - let index_shape = Shape::indented(indent, context.config).offset_left(lbr.len())?; - let index_shape = index_shape.sub_width(rbr.len() + rhs_overhead)?; - let new_index_rw = index.rewrite(context, index_shape); - match (orig_index_rw, new_index_rw) { - (_, Some(ref new_index_str)) if !new_index_str.contains('\n') => Some(format!( - "{}\n{}{}{}{}", - expr_str, - indent.to_string(context.config), - lbr, - new_index_str, - rbr - )), - (None, Some(ref new_index_str)) => Some(format!( - "{}\n{}{}{}{}", - expr_str, - indent.to_string(context.config), - lbr, - new_index_str, - rbr - )), - (Some(ref index_str), _) => Some(format!("{}{}{}{}", expr_str, lbr, index_str, rbr)), - _ => None, - } -} - -fn struct_lit_can_be_aligned(fields: &[ast::Field], base: &Option<&ast::Expr>) -> bool { - if base.is_some() { - return false; - } - - fields.iter().all(|field| !field.is_shorthand) -} - -fn rewrite_struct_lit<'a>( - context: &RewriteContext, - path: &ast::Path, - fields: &'a [ast::Field], - base: Option<&'a ast::Expr>, - span: Span, - shape: Shape, -) -> Option<String> { - debug!("rewrite_struct_lit: shape {:?}", shape); - - enum StructLitField<'a> { - Regular(&'a ast::Field), - Base(&'a ast::Expr), - } - - // 2 = " {".len() - let path_shape = shape.sub_width(2)?; - let path_str = rewrite_path(context, PathContext::Expr, None, path, path_shape)?; - - if fields.is_empty() && base.is_none() { - return Some(format!("{} {{}}", path_str)); - } - - // Foo { a: Foo } - indent is +3, width is -5. - let (h_shape, v_shape) = struct_lit_shape(shape, context, path_str.len() + 3, 2)?; - - let one_line_width = h_shape.map_or(0, |shape| shape.width); - let body_lo = context.codemap.span_after(span, "{"); - let fields_str = if struct_lit_can_be_aligned(fields, &base) - && context.config.struct_field_align_threshold() > 0 - { - rewrite_with_alignment( - fields, - context, - shape, - mk_sp(body_lo, span.hi()), - one_line_width, - )? - } else { - let field_iter = fields - .into_iter() - .map(StructLitField::Regular) - .chain(base.into_iter().map(StructLitField::Base)); - - let span_lo = |item: &StructLitField| match *item { - StructLitField::Regular(field) => field.span().lo(), - StructLitField::Base(expr) => { - let last_field_hi = fields.last().map_or(span.lo(), |field| field.span.hi()); - let snippet = context.snippet(mk_sp(last_field_hi, expr.span.lo())); - let pos = snippet.find_uncommented("..").unwrap(); - last_field_hi + BytePos(pos as u32) - } - }; - let span_hi = |item: &StructLitField| match *item { - StructLitField::Regular(field) => field.span().hi(), - StructLitField::Base(expr) => expr.span.hi(), - }; - let rewrite = |item: &StructLitField| match *item { - StructLitField::Regular(field) => { - // The 1 taken from the v_budget is for the comma. - rewrite_field(context, field, v_shape.sub_width(1)?, 0) - } - StructLitField::Base(expr) => { - // 2 = .. - expr.rewrite(context, v_shape.offset_left(2)?) - .map(|s| format!("..{}", s)) - } - }; - - let items = itemize_list( - context.codemap, - field_iter, - "}", - ",", - span_lo, - span_hi, - rewrite, - body_lo, - span.hi(), - false, - ); - let item_vec = items.collect::<Vec<_>>(); - - let tactic = struct_lit_tactic(h_shape, context, &item_vec); - let nested_shape = shape_for_tactic(tactic, h_shape, v_shape); - let fmt = struct_lit_formatting(nested_shape, tactic, context, base.is_some()); - - write_list(&item_vec, &fmt)? - }; - - let fields_str = wrap_struct_field(context, &fields_str, shape, v_shape, one_line_width); - Some(format!("{} {{{}}}", path_str, fields_str)) - - // FIXME if context.config.indent_style() == Visual, but we run out - // of space, we should fall back to BlockIndent. -} - -pub fn wrap_struct_field( - context: &RewriteContext, - fields_str: &str, - shape: Shape, - nested_shape: Shape, - one_line_width: usize, -) -> String { - if context.config.indent_style() == IndentStyle::Block - && (fields_str.contains('\n') || !context.config.struct_lit_single_line() - || fields_str.len() > one_line_width) - { - format!( - "\n{}{}\n{}", - nested_shape.indent.to_string(context.config), - fields_str, - shape.indent.to_string(context.config) - ) - } else { - // One liner or visual indent. - format!(" {} ", fields_str) - } -} - -pub fn struct_lit_field_separator(config: &Config) -> &str { - colon_spaces(config.space_before_colon(), config.space_after_colon()) -} - -pub fn rewrite_field( - context: &RewriteContext, - field: &ast::Field, - shape: Shape, - prefix_max_width: usize, -) -> Option<String> { - if contains_skip(&field.attrs) { - return Some(context.snippet(field.span()).to_owned()); - } - let mut attrs_str = field.attrs.rewrite(context, shape)?; - if !attrs_str.is_empty() { - attrs_str.push_str(&format!("\n{}", shape.indent.to_string(context.config))); - }; - let name = field.ident.node.to_string(); - if field.is_shorthand { - Some(attrs_str + &name) - } else { - let mut separator = String::from(struct_lit_field_separator(context.config)); - for _ in 0..prefix_max_width.checked_sub(name.len()).unwrap_or(0) { - separator.push(' '); - } - let overhead = name.len() + separator.len(); - let expr_shape = shape.offset_left(overhead)?; - let expr = field.expr.rewrite(context, expr_shape); - - match expr { - Some(ref e) if e.as_str() == name && context.config.use_field_init_shorthand() => { - Some(attrs_str + &name) - } - Some(e) => Some(format!("{}{}{}{}", attrs_str, name, separator, e)), - None => { - let expr_offset = shape.indent.block_indent(context.config); - let expr = field - .expr - .rewrite(context, Shape::indented(expr_offset, context.config)); - expr.map(|s| { - format!( - "{}{}:\n{}{}", - attrs_str, - name, - expr_offset.to_string(context.config), - s - ) - }) - } - } - } -} - -fn shape_from_indent_style( - context: &RewriteContext, - shape: Shape, - overhead: usize, - offset: usize, -) -> Option<Shape> { - if context.use_block_indent() { - // 1 = "," - shape - .block() - .block_indent(context.config.tab_spaces()) - .with_max_width(context.config) - .sub_width(1) - } else { - shape.visual_indent(offset).sub_width(overhead) - } -} - -fn rewrite_tuple_in_visual_indent_style<'a, T>( - context: &RewriteContext, - items: &[&T], - span: Span, - shape: Shape, -) -> Option<String> -where - T: Rewrite + Spanned + ToExpr + 'a, -{ - let mut items = items.iter(); - // In case of length 1, need a trailing comma - debug!("rewrite_tuple_in_visual_indent_style {:?}", shape); - if items.len() == 1 { - // 3 = "(" + ",)" - let nested_shape = shape.sub_width(3)?.visual_indent(1); - return items - .next() - .unwrap() - .rewrite(context, nested_shape) - .map(|s| { - if context.config.spaces_within_parens_and_brackets() { - format!("( {}, )", s) - } else { - format!("({},)", s) - } - }); - } - - let list_lo = context.codemap.span_after(span, "("); - let nested_shape = shape.sub_width(2)?.visual_indent(1); - let items = itemize_list( - context.codemap, - items, - ")", - ",", - |item| item.span().lo(), - |item| item.span().hi(), - |item| item.rewrite(context, nested_shape), - list_lo, - span.hi() - BytePos(1), - false, - ); - let item_vec: Vec<_> = items.collect(); - let tactic = definitive_tactic( - &item_vec, - ListTactic::HorizontalVertical, - Separator::Comma, - nested_shape.width, - ); - let fmt = ListFormatting { - tactic, - separator: ",", - trailing_separator: SeparatorTactic::Never, - separator_place: SeparatorPlace::Back, - shape, - ends_with_newline: false, - preserve_newline: false, - config: context.config, - }; - let list_str = write_list(&item_vec, &fmt)?; - - if context.config.spaces_within_parens_and_brackets() && !list_str.is_empty() { - Some(format!("( {} )", list_str)) - } else { - Some(format!("({})", list_str)) - } -} - -pub fn rewrite_tuple<'a, T>( - context: &RewriteContext, - items: &[&T], - span: Span, - shape: Shape, -) -> Option<String> -where - T: Rewrite + Spanned + ToExpr + 'a, -{ - debug!("rewrite_tuple {:?}", shape); - if context.use_block_indent() { - // We use the same rule as function calls for rewriting tuples. - let force_trailing_comma = if context.inside_macro { - span_ends_with_comma(context, span) - } else { - items.len() == 1 - }; - rewrite_call_inner( - context, - &String::new(), - items, - span, - shape, - context.config.width_heuristics().fn_call_width, - force_trailing_comma, - ) - } else { - rewrite_tuple_in_visual_indent_style(context, items, span, shape) - } -} - -pub fn rewrite_unary_prefix<R: Rewrite>( - context: &RewriteContext, - prefix: &str, - rewrite: &R, - shape: Shape, -) -> Option<String> { - rewrite - .rewrite(context, shape.offset_left(prefix.len())?) - .map(|r| format!("{}{}", prefix, r)) -} - -// FIXME: this is probably not correct for multi-line Rewrites. we should -// subtract suffix.len() from the last line budget, not the first! -pub fn rewrite_unary_suffix<R: Rewrite>( - context: &RewriteContext, - suffix: &str, - rewrite: &R, - shape: Shape, -) -> Option<String> { - rewrite - .rewrite(context, shape.sub_width(suffix.len())?) - .map(|mut r| { - r.push_str(suffix); - r - }) -} - -fn rewrite_unary_op( - context: &RewriteContext, - op: &ast::UnOp, - expr: &ast::Expr, - shape: Shape, -) -> Option<String> { - // For some reason, an UnOp is not spanned like BinOp! - let operator_str = match *op { - ast::UnOp::Deref => "*", - ast::UnOp::Not => "!", - ast::UnOp::Neg => "-", - }; - rewrite_unary_prefix(context, operator_str, expr, shape) -} - -fn rewrite_assignment( - context: &RewriteContext, - lhs: &ast::Expr, - rhs: &ast::Expr, - op: Option<&ast::BinOp>, - shape: Shape, -) -> Option<String> { - let operator_str = match op { - Some(op) => context.snippet(op.span), - None => "=", - }; - - // 1 = space between lhs and operator. - let lhs_shape = shape.sub_width(operator_str.len() + 1)?; - let lhs_str = format!("{} {}", lhs.rewrite(context, lhs_shape)?, operator_str); - - rewrite_assign_rhs(context, lhs_str, rhs, shape) -} - -// The left hand side must contain everything up to, and including, the -// assignment operator. -pub fn rewrite_assign_rhs<S: Into<String>, R: Rewrite>( - context: &RewriteContext, - lhs: S, - ex: &R, - shape: Shape, -) -> Option<String> { - let lhs = lhs.into(); - let last_line_width = last_line_width(&lhs) - .checked_sub(if lhs.contains('\n') { - shape.indent.width() - } else { - 0 - }) - .unwrap_or(0); - // 1 = space between operator and rhs. - let orig_shape = shape.offset_left(last_line_width + 1).unwrap_or(Shape { - width: 0, - offset: shape.offset + last_line_width + 1, - ..shape - }); - let rhs = choose_rhs(context, ex, orig_shape, ex.rewrite(context, orig_shape))?; - Some(lhs + &rhs) -} - -pub fn choose_rhs<R: Rewrite>( - context: &RewriteContext, - expr: &R, - shape: Shape, - orig_rhs: Option<String>, -) -> Option<String> { - match orig_rhs { - Some(ref new_str) if !new_str.contains('\n') && new_str.len() <= shape.width => { - Some(format!(" {}", new_str)) - } - _ => { - // Expression did not fit on the same line as the identifier. - // Try splitting the line and see if that works better. - let new_shape = - Shape::indented(shape.indent.block_indent(context.config), context.config) - .sub_width(shape.rhs_overhead(context.config))?; - let new_rhs = expr.rewrite(context, new_shape); - let new_indent_str = &new_shape.indent.to_string(context.config); - - match (orig_rhs, new_rhs) { - (Some(ref orig_rhs), Some(ref new_rhs)) - if wrap_str(new_rhs.clone(), context.config.max_width(), new_shape) - .is_none() => - { - Some(format!(" {}", orig_rhs)) - } - (Some(ref orig_rhs), Some(ref new_rhs)) if prefer_next_line(orig_rhs, new_rhs) => { - Some(format!("\n{}{}", new_indent_str, new_rhs)) - } - (None, Some(ref new_rhs)) => Some(format!("\n{}{}", new_indent_str, new_rhs)), - (None, None) => None, - (Some(ref orig_rhs), _) => Some(format!(" {}", orig_rhs)), - } - } - } -} - -fn prefer_next_line(orig_rhs: &str, next_line_rhs: &str) -> bool { - !next_line_rhs.contains('\n') || count_newlines(orig_rhs) > count_newlines(next_line_rhs) + 1 -} - -fn rewrite_expr_addrof( - context: &RewriteContext, - mutability: ast::Mutability, - expr: &ast::Expr, - shape: Shape, -) -> Option<String> { - let operator_str = match mutability { - ast::Mutability::Immutable => "&", - ast::Mutability::Mutable => "&mut ", - }; - rewrite_unary_prefix(context, operator_str, expr, shape) -} - -pub trait ToExpr { - fn to_expr(&self) -> Option<&ast::Expr>; - fn can_be_overflowed(&self, context: &RewriteContext, len: usize) -> bool; -} - -impl ToExpr for ast::Expr { - fn to_expr(&self) -> Option<&ast::Expr> { - Some(self) - } - - fn can_be_overflowed(&self, context: &RewriteContext, len: usize) -> bool { - can_be_overflowed_expr(context, self, len) - } -} - -impl ToExpr for ast::Ty { - fn to_expr(&self) -> Option<&ast::Expr> { - None - } - - fn can_be_overflowed(&self, context: &RewriteContext, len: usize) -> bool { - can_be_overflowed_type(context, self, len) - } -} - -impl<'a> ToExpr for TuplePatField<'a> { - fn to_expr(&self) -> Option<&ast::Expr> { - None - } - - fn can_be_overflowed(&self, context: &RewriteContext, len: usize) -> bool { - can_be_overflowed_pat(context, self, len) - } -} - -impl<'a> ToExpr for ast::StructField { - fn to_expr(&self) -> Option<&ast::Expr> { - None - } - - fn can_be_overflowed(&self, _: &RewriteContext, _: usize) -> bool { - false - } -} - -impl<'a> ToExpr for MacroArg { - fn to_expr(&self) -> Option<&ast::Expr> { - match *self { - MacroArg::Expr(ref expr) => Some(expr), - _ => None, - } - } - - fn can_be_overflowed(&self, context: &RewriteContext, len: usize) -> bool { - match *self { - MacroArg::Expr(ref expr) => can_be_overflowed_expr(context, expr, len), - MacroArg::Ty(ref ty) => can_be_overflowed_type(context, ty, len), - MacroArg::Pat(..) => false, - } - } -} diff --git a/src/filemap.rs b/src/filemap.rs deleted file mode 100644 index 81f950cfb9b..00000000000 --- a/src/filemap.rs +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -// TODO: add tests - -use std::fs::{self, File}; -use std::io::{self, BufWriter, Read, Write}; -use std::path::Path; - -use checkstyle::{output_checkstyle_file, output_footer, output_header}; -use config::{Config, NewlineStyle, WriteMode}; -use rustfmt_diff::{make_diff, print_diff, Mismatch}; -use syntax::codemap::FileName; - -// A map of the files of a crate, with their new content -pub type FileMap = Vec<FileRecord>; - -pub type FileRecord = (FileName, String); - -// Append a newline to the end of each file. -pub fn append_newline(s: &mut String) { - s.push_str("\n"); -} - -pub fn write_all_files<T>( - file_map: &[FileRecord], - out: &mut T, - config: &Config, -) -> Result<(), io::Error> -where - T: Write, -{ - output_header(out, config.write_mode()).ok(); - for &(ref filename, ref text) in file_map { - write_file(text, filename, out, config)?; - } - output_footer(out, config.write_mode()).ok(); - - Ok(()) -} - -// Prints all newlines either as `\n` or as `\r\n`. -pub fn write_system_newlines<T>(writer: T, text: &str, config: &Config) -> Result<(), io::Error> -where - T: Write, -{ - // Buffer output, since we're writing a since char at a time. - let mut writer = BufWriter::new(writer); - - let style = if config.newline_style() == NewlineStyle::Native { - if cfg!(windows) { - NewlineStyle::Windows - } else { - NewlineStyle::Unix - } - } else { - config.newline_style() - }; - - match style { - NewlineStyle::Unix => write!(writer, "{}", text), - NewlineStyle::Windows => { - for c in text.chars() { - match c { - '\n' => write!(writer, "\r\n")?, - '\r' => continue, - c => write!(writer, "{}", c)?, - } - } - Ok(()) - } - NewlineStyle::Native => unreachable!(), - } -} - -pub fn write_file<T>( - text: &str, - filename: &FileName, - out: &mut T, - config: &Config, -) -> Result<bool, io::Error> -where - T: Write, -{ - fn source_and_formatted_text( - text: &str, - filename: &Path, - config: &Config, - ) -> Result<(String, String), io::Error> { - let mut f = File::open(filename)?; - let mut ori_text = String::new(); - f.read_to_string(&mut ori_text)?; - let mut v = Vec::new(); - write_system_newlines(&mut v, text, config)?; - let fmt_text = String::from_utf8(v).unwrap(); - Ok((ori_text, fmt_text)) - } - - fn create_diff( - filename: &Path, - text: &str, - config: &Config, - ) -> Result<Vec<Mismatch>, io::Error> { - let (ori, fmt) = source_and_formatted_text(text, filename, config)?; - Ok(make_diff(&ori, &fmt, 3)) - } - - let filename_to_path = || match *filename { - FileName::Real(ref path) => path, - _ => panic!("cannot format `{}` with WriteMode::Replace", filename), - }; - - match config.write_mode() { - WriteMode::Replace => { - let filename = filename_to_path(); - if let Ok((ori, fmt)) = source_and_formatted_text(text, filename, config) { - if fmt != ori { - // Do a little dance to make writing safer - write to a temp file - // rename the original to a .bk, then rename the temp file to the - // original. - let tmp_name = filename.with_extension("tmp"); - let bk_name = filename.with_extension("bk"); - { - // Write text to temp file - let tmp_file = File::create(&tmp_name)?; - write_system_newlines(tmp_file, text, config)?; - } - - fs::rename(filename, bk_name)?; - fs::rename(tmp_name, filename)?; - } - } - } - WriteMode::Overwrite => { - // Write text directly over original file if there is a diff. - let filename = filename_to_path(); - let (source, formatted) = source_and_formatted_text(text, filename, config)?; - if source != formatted { - let file = File::create(filename)?; - write_system_newlines(file, text, config)?; - } - } - WriteMode::Plain => { - write_system_newlines(out, text, config)?; - } - WriteMode::Display | WriteMode::Coverage => { - println!("{}:\n", filename); - write_system_newlines(out, text, config)?; - } - WriteMode::Diff => { - let filename = filename_to_path(); - if let Ok((ori, fmt)) = source_and_formatted_text(text, filename, config) { - let mismatch = make_diff(&ori, &fmt, 3); - let has_diff = !mismatch.is_empty(); - print_diff( - mismatch, - |line_num| format!("Diff in {} at line {}:", filename.display(), line_num), - config.color(), - ); - return Ok(has_diff); - } - } - WriteMode::Checkstyle => { - let filename = filename_to_path(); - let diff = create_diff(filename, text, config)?; - output_checkstyle_file(out, filename, diff)?; - } - } - - // when we are not in diff mode, don't indicate differing files - Ok(false) -} diff --git a/src/imports.rs b/src/imports.rs deleted file mode 100644 index 0b65cafe730..00000000000 --- a/src/imports.rs +++ /dev/null @@ -1,600 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::cmp::Ordering; - -use syntax::ast; -use syntax::codemap::{BytePos, Span}; - -use codemap::SpanUtils; -use comment::combine_strs_with_missing_comments; -use config::IndentStyle; -use lists::{definitive_tactic, itemize_list, write_list, DefinitiveListTactic, ListFormatting, - ListItem, Separator, SeparatorPlace, SeparatorTactic}; -use rewrite::{Rewrite, RewriteContext}; -use shape::Shape; -use spanned::Spanned; -use types::{rewrite_path, PathContext}; -use utils::{format_visibility, mk_sp}; -use visitor::{rewrite_extern_crate, FmtVisitor}; - -fn compare_path_segments(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering { - a.identifier.name.as_str().cmp(&b.identifier.name.as_str()) -} - -fn compare_paths(a: &ast::Path, b: &ast::Path) -> Ordering { - for segment in a.segments.iter().zip(b.segments.iter()) { - let ord = compare_path_segments(segment.0, segment.1); - if ord != Ordering::Equal { - return ord; - } - } - a.segments.len().cmp(&b.segments.len()) -} - -fn compare_use_trees(a: &ast::UseTree, b: &ast::UseTree, nested: bool) -> Ordering { - use ast::UseTreeKind::*; - - // `use_nested_groups` is not yet supported, remove the `if !nested` when support will be - // fully added - if !nested { - let paths_cmp = compare_paths(&a.prefix, &b.prefix); - if paths_cmp != Ordering::Equal { - return paths_cmp; - } - } - - match (&a.kind, &b.kind) { - (&Simple(ident_a), &Simple(ident_b)) => { - let name_a = &*path_to_imported_ident(&a.prefix).name.as_str(); - let name_b = &*path_to_imported_ident(&b.prefix).name.as_str(); - let name_ordering = if name_a == "self" { - if name_b == "self" { - Ordering::Equal - } else { - Ordering::Less - } - } else if name_b == "self" { - Ordering::Greater - } else { - name_a.cmp(name_b) - }; - if name_ordering == Ordering::Equal { - if ident_a.name.as_str() != name_a { - if ident_b.name.as_str() != name_b { - ident_a.name.as_str().cmp(&ident_b.name.as_str()) - } else { - Ordering::Greater - } - } else { - Ordering::Less - } - } else { - name_ordering - } - } - (&Glob, &Glob) => Ordering::Equal, - (&Simple(_), _) | (&Glob, &Nested(_)) => Ordering::Less, - (&Nested(ref a_items), &Nested(ref b_items)) => { - let mut a = a_items - .iter() - .map(|&(ref tree, _)| tree.clone()) - .collect::<Vec<_>>(); - let mut b = b_items - .iter() - .map(|&(ref tree, _)| tree.clone()) - .collect::<Vec<_>>(); - a.sort_by(|a, b| compare_use_trees(a, b, true)); - b.sort_by(|a, b| compare_use_trees(a, b, true)); - for comparison_pair in a.iter().zip(b.iter()) { - let ord = compare_use_trees(comparison_pair.0, comparison_pair.1, true); - if ord != Ordering::Equal { - return ord; - } - } - a.len().cmp(&b.len()) - } - (&Glob, &Simple(_)) | (&Nested(_), _) => Ordering::Greater, - } -} - -fn compare_use_items(a: &ast::Item, b: &ast::Item) -> Ordering { - match (&a.node, &b.node) { - (&ast::ItemKind::Mod(..), &ast::ItemKind::Mod(..)) => { - a.ident.name.as_str().cmp(&b.ident.name.as_str()) - } - (&ast::ItemKind::Use(ref a_tree), &ast::ItemKind::Use(ref b_tree)) => { - compare_use_trees(a_tree, b_tree, false) - } - (&ast::ItemKind::ExternCrate(ref a_name), &ast::ItemKind::ExternCrate(ref b_name)) => { - // `extern crate foo as bar;` - // ^^^ Comparing this. - let a_orig_name = - a_name.map_or_else(|| a.ident.name.as_str(), |symbol| symbol.as_str()); - let b_orig_name = - b_name.map_or_else(|| b.ident.name.as_str(), |symbol| symbol.as_str()); - let result = a_orig_name.cmp(&b_orig_name); - if result != Ordering::Equal { - return result; - } - - // `extern crate foo as bar;` - // ^^^ Comparing this. - match (a_name, b_name) { - (Some(..), None) => Ordering::Greater, - (None, Some(..)) => Ordering::Less, - (None, None) => Ordering::Equal, - (Some(..), Some(..)) => a.ident.name.as_str().cmp(&b.ident.name.as_str()), - } - } - _ => unreachable!(), - } -} - -// TODO (some day) remove unused imports, expand globs, compress many single -// imports into a list import. - -fn rewrite_prefix(path: &ast::Path, context: &RewriteContext, shape: Shape) -> Option<String> { - if path.segments.len() > 1 && path_to_imported_ident(path).to_string() == "self" { - let path = &ast::Path { - span: path.span, - segments: path.segments[..path.segments.len() - 1].to_owned(), - }; - rewrite_path(context, PathContext::Import, None, path, shape) - } else { - rewrite_path(context, PathContext::Import, None, path, shape) - } -} - -impl Rewrite for ast::UseTree { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - match self.kind { - ast::UseTreeKind::Nested(ref items) => { - rewrite_nested_use_tree(shape, &self.prefix, items, self.span, context) - } - ast::UseTreeKind::Glob => { - let prefix_shape = shape.sub_width(3)?; - - if !self.prefix.segments.is_empty() { - let path_str = rewrite_prefix(&self.prefix, context, prefix_shape)?; - Some(format!("{}::*", path_str)) - } else { - Some("*".to_owned()) - } - } - ast::UseTreeKind::Simple(ident) => { - let ident_str = ident.to_string(); - - // 4 = " as ".len() - let is_same_name_bind = path_to_imported_ident(&self.prefix) == ident; - let prefix_shape = if is_same_name_bind { - shape - } else { - shape.sub_width(ident_str.len() + 4)? - }; - let path_str = rewrite_prefix(&self.prefix, context, prefix_shape) - .unwrap_or_else(|| context.snippet(self.prefix.span).to_owned()); - - if is_same_name_bind { - Some(path_str) - } else { - Some(format!("{} as {}", path_str, ident_str)) - } - } - } - } -} - -fn is_unused_import(tree: &ast::UseTree, attrs: &[ast::Attribute]) -> bool { - attrs.is_empty() && is_unused_import_inner(tree) -} - -fn is_unused_import_inner(tree: &ast::UseTree) -> bool { - match tree.kind { - ast::UseTreeKind::Nested(ref items) => match items.len() { - 0 => true, - 1 => is_unused_import_inner(&items[0].0), - _ => false, - }, - _ => false, - } -} - -// Rewrite `use foo;` WITHOUT attributes. -fn rewrite_import( - context: &RewriteContext, - vis: &ast::Visibility, - tree: &ast::UseTree, - attrs: &[ast::Attribute], - shape: Shape, -) -> Option<String> { - let vis = format_visibility(vis); - // 4 = `use `, 1 = `;` - let rw = shape - .offset_left(vis.len() + 4) - .and_then(|shape| shape.sub_width(1)) - .and_then(|shape| { - // If we have an empty nested group with no attributes, we erase it - if is_unused_import(tree, attrs) { - Some("".to_owned()) - } else { - tree.rewrite(context, shape) - } - }); - match rw { - Some(ref s) if !s.is_empty() => Some(format!("{}use {};", vis, s)), - _ => rw, - } -} - -/// Rewrite an inline mod. -fn rewrite_mod(item: &ast::Item) -> String { - let mut result = String::with_capacity(32); - result.push_str(&*format_visibility(&item.vis)); - result.push_str("mod "); - result.push_str(&item.ident.to_string()); - result.push(';'); - result -} - -fn rewrite_imports( - context: &RewriteContext, - use_items: &[&ast::Item], - shape: Shape, - span: Span, -) -> Option<String> { - let items = itemize_list( - context.codemap, - use_items.iter(), - "", - ";", - |item| item.span().lo(), - |item| item.span().hi(), - |item| { - let attrs = ::visitor::filter_inline_attrs(&item.attrs, item.span()); - let attrs_str = attrs.rewrite(context, shape)?; - - let missed_span = if attrs.is_empty() { - mk_sp(item.span.lo(), item.span.lo()) - } else { - mk_sp(attrs.last().unwrap().span.hi(), item.span.lo()) - }; - - let item_str = match item.node { - ast::ItemKind::Use(ref tree) => { - rewrite_import(context, &item.vis, tree, &item.attrs, shape)? - } - ast::ItemKind::ExternCrate(..) => rewrite_extern_crate(context, item)?, - ast::ItemKind::Mod(..) => rewrite_mod(item), - _ => return None, - }; - - combine_strs_with_missing_comments( - context, - &attrs_str, - &item_str, - missed_span, - shape, - false, - ) - }, - span.lo(), - span.hi(), - false, - ); - let mut item_pair_vec: Vec<_> = items.zip(use_items.iter()).collect(); - item_pair_vec.sort_by(|a, b| compare_use_items(a.1, b.1)); - let item_vec: Vec<_> = item_pair_vec.into_iter().map(|pair| pair.0).collect(); - - let fmt = ListFormatting { - tactic: DefinitiveListTactic::Vertical, - separator: "", - trailing_separator: SeparatorTactic::Never, - separator_place: SeparatorPlace::Back, - shape, - ends_with_newline: true, - preserve_newline: false, - config: context.config, - }; - - write_list(&item_vec, &fmt) -} - -impl<'a> FmtVisitor<'a> { - pub fn format_imports(&mut self, use_items: &[&ast::Item]) { - if use_items.is_empty() { - return; - } - - let lo = use_items.first().unwrap().span().lo(); - let hi = use_items.last().unwrap().span().hi(); - let span = mk_sp(lo, hi); - let rw = rewrite_imports(&self.get_context(), use_items, self.shape(), span); - self.push_rewrite(span, rw); - } - - pub fn format_import(&mut self, item: &ast::Item, tree: &ast::UseTree) { - let span = item.span; - let shape = self.shape(); - let rw = rewrite_import(&self.get_context(), &item.vis, tree, &item.attrs, shape); - match rw { - Some(ref s) if s.is_empty() => { - // Format up to last newline - let prev_span = mk_sp(self.last_pos, source!(self, span).lo()); - let trimmed_snippet = self.snippet(prev_span).trim_right(); - let span_end = self.last_pos + BytePos(trimmed_snippet.len() as u32); - self.format_missing(span_end); - // We have an excessive newline from the removed import. - if self.buffer.ends_with('\n') { - self.buffer.pop(); - self.line_number -= 1; - } - self.last_pos = source!(self, span).hi(); - } - Some(ref s) => { - self.format_missing_with_indent(source!(self, span).lo()); - self.push_str(s); - self.last_pos = source!(self, span).hi(); - } - None => { - self.format_missing_with_indent(source!(self, span).lo()); - self.format_missing(source!(self, span).hi()); - } - } - } -} - -fn rewrite_nested_use_tree_single( - context: &RewriteContext, - path_str: &str, - tree: &ast::UseTree, - shape: Shape, -) -> Option<String> { - match tree.kind { - ast::UseTreeKind::Simple(rename) => { - let ident = path_to_imported_ident(&tree.prefix); - let mut item_str = rewrite_prefix(&tree.prefix, context, shape)?; - if item_str == "self" { - item_str = "".to_owned(); - } - - let path_item_str = if path_str.is_empty() { - if item_str.is_empty() { - "self".to_owned() - } else { - item_str - } - } else if item_str.is_empty() { - path_str.to_owned() - } else { - format!("{}::{}", path_str, item_str) - }; - - Some(if ident == rename { - path_item_str - } else { - format!("{} as {}", path_item_str, rename) - }) - } - ast::UseTreeKind::Glob | ast::UseTreeKind::Nested(..) => { - // 2 = "::" - let nested_shape = shape.offset_left(path_str.len() + 2)?; - tree.rewrite(context, nested_shape) - .map(|item| format!("{}::{}", path_str, item)) - } - } -} - -#[derive(Eq, PartialEq)] -enum ImportItem<'a> { - // `self` or `self as a` - SelfImport(&'a str), - // name_one, name_two, ... - SnakeCase(&'a str), - // NameOne, NameTwo, ... - CamelCase(&'a str), - // NAME_ONE, NAME_TWO, ... - AllCaps(&'a str), - // Failed to format the import item - Invalid, -} - -impl<'a> ImportItem<'a> { - fn from_str(s: &str) -> ImportItem { - if s == "self" || s.starts_with("self as") { - ImportItem::SelfImport(s) - } else if s.chars().all(|c| c.is_lowercase() || c == '_' || c == ' ') { - ImportItem::SnakeCase(s) - } else if s.chars().all(|c| c.is_uppercase() || c == '_' || c == ' ') { - ImportItem::AllCaps(s) - } else { - ImportItem::CamelCase(s) - } - } - - fn from_opt_str(s: Option<&String>) -> ImportItem { - s.map_or(ImportItem::Invalid, |s| ImportItem::from_str(s)) - } - - fn to_str(&self) -> Option<&str> { - match *self { - ImportItem::SelfImport(s) - | ImportItem::SnakeCase(s) - | ImportItem::CamelCase(s) - | ImportItem::AllCaps(s) => Some(s), - ImportItem::Invalid => None, - } - } - - fn to_u32(&self) -> u32 { - match *self { - ImportItem::SelfImport(..) => 0, - ImportItem::SnakeCase(..) => 1, - ImportItem::CamelCase(..) => 2, - ImportItem::AllCaps(..) => 3, - ImportItem::Invalid => 4, - } - } -} - -impl<'a> PartialOrd for ImportItem<'a> { - fn partial_cmp(&self, other: &ImportItem<'a>) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -impl<'a> Ord for ImportItem<'a> { - fn cmp(&self, other: &ImportItem<'a>) -> Ordering { - let res = self.to_u32().cmp(&other.to_u32()); - if res != Ordering::Equal { - return res; - } - self.to_str().map_or(Ordering::Greater, |self_str| { - other - .to_str() - .map_or(Ordering::Less, |other_str| self_str.cmp(other_str)) - }) - } -} - -// Pretty prints a multi-item import. -// If the path list is empty, it leaves the braces empty. -fn rewrite_nested_use_tree( - shape: Shape, - path: &ast::Path, - trees: &[(ast::UseTree, ast::NodeId)], - span: Span, - context: &RewriteContext, -) -> Option<String> { - // Returns a different option to distinguish `::foo` and `foo` - let path_str = rewrite_path(context, PathContext::Import, None, path, shape)?; - - match trees.len() { - 0 => { - let shape = shape.offset_left(path_str.len() + 3)?; - return rewrite_path(context, PathContext::Import, None, path, shape) - .map(|path_str| format!("{}::{{}}", path_str)); - } - 1 => { - return rewrite_nested_use_tree_single(context, &path_str, &trees[0].0, shape); - } - _ => (), - } - - let path_str = if path_str.is_empty() { - path_str - } else { - format!("{}::", path_str) - }; - - // 2 = "{}" - let remaining_width = shape.width.checked_sub(path_str.len() + 2).unwrap_or(0); - let nested_indent = match context.config.imports_indent() { - IndentStyle::Block => shape.indent.block_indent(context.config), - // 1 = `{` - IndentStyle::Visual => shape.visual_indent(path_str.len() + 1).indent, - }; - - let nested_shape = match context.config.imports_indent() { - IndentStyle::Block => Shape::indented(nested_indent, context.config).sub_width(1)?, - IndentStyle::Visual => Shape::legacy(remaining_width, nested_indent), - }; - - let mut items = { - // Dummy value, see explanation below. - let mut items = vec![ListItem::from_str("")]; - let iter = itemize_list( - context.codemap, - trees.iter().map(|tree| &tree.0), - "}", - ",", - |tree| tree.span.lo(), - |tree| tree.span.hi(), - |tree| tree.rewrite(context, nested_shape), - context.codemap.span_after(span, "{"), - span.hi(), - false, - ); - items.extend(iter); - items - }; - - // We prefixed the item list with a dummy value so that we can - // potentially move "self" to the front of the vector without touching - // the rest of the items. - let has_self = move_self_to_front(&mut items); - let first_index = if has_self { 0 } else { 1 }; - - if context.config.reorder_imported_names() { - items[1..].sort_by(|a, b| { - let a = ImportItem::from_opt_str(a.item.as_ref()); - let b = ImportItem::from_opt_str(b.item.as_ref()); - a.cmp(&b) - }); - } - - let tactic = definitive_tactic( - &items[first_index..], - context.config.imports_layout(), - Separator::Comma, - remaining_width, - ); - - let ends_with_newline = context.config.imports_indent() == IndentStyle::Block - && tactic != DefinitiveListTactic::Horizontal; - - let fmt = ListFormatting { - tactic, - separator: ",", - trailing_separator: if ends_with_newline { - context.config.trailing_comma() - } else { - SeparatorTactic::Never - }, - separator_place: SeparatorPlace::Back, - shape: nested_shape, - ends_with_newline, - preserve_newline: true, - config: context.config, - }; - let list_str = write_list(&items[first_index..], &fmt)?; - - let result = if list_str.contains('\n') && context.config.imports_indent() == IndentStyle::Block - { - format!( - "{}{{\n{}{}\n{}}}", - path_str, - nested_shape.indent.to_string(context.config), - list_str, - shape.indent.to_string(context.config) - ) - } else { - format!("{}{{{}}}", path_str, list_str) - }; - Some(result) -} - -// Returns true when self item was found. -fn move_self_to_front(items: &mut Vec<ListItem>) -> bool { - match items - .iter() - .position(|item| item.item.as_ref().map(|x| &x[..]) == Some("self")) - { - Some(pos) => { - items[0] = items.remove(pos); - true - } - None => false, - } -} - -fn path_to_imported_ident(path: &ast::Path) -> ast::Ident { - path.segments.last().unwrap().identifier -} diff --git a/src/issues.rs b/src/issues.rs deleted file mode 100644 index 2efd61a3d7d..00000000000 --- a/src/issues.rs +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -// Objects for seeking through a char stream for occurrences of TODO and FIXME. -// Depending on the loaded configuration, may also check that these have an -// associated issue number. - -use std::fmt; - -pub use config::ReportTactic; - -const TO_DO_CHARS: &[char] = &['t', 'o', 'd', 'o']; -const FIX_ME_CHARS: &[char] = &['f', 'i', 'x', 'm', 'e']; - -// Enabled implementation detail is here because it is -// irrelevant outside the issues module -impl ReportTactic { - fn is_enabled(&self) -> bool { - *self != ReportTactic::Never - } -} - -#[derive(Clone, Copy)] -enum Seeking { - Issue { todo_idx: usize, fixme_idx: usize }, - Number { issue: Issue, part: NumberPart }, -} - -#[derive(Clone, Copy)] -enum NumberPart { - OpenParen, - Pound, - Number, - CloseParen, -} - -#[derive(PartialEq, Eq, Debug, Clone, Copy)] -pub struct Issue { - issue_type: IssueType, - // Indicates whether we're looking for issues with missing numbers, or - // all issues of this type. - missing_number: bool, -} - -impl fmt::Display for Issue { - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - let msg = match self.issue_type { - IssueType::Todo => "TODO", - IssueType::Fixme => "FIXME", - }; - let details = if self.missing_number { - " without issue number" - } else { - "" - }; - - write!(fmt, "{}{}", msg, details) - } -} - -#[derive(PartialEq, Eq, Debug, Clone, Copy)] -enum IssueType { - Todo, - Fixme, -} - -enum IssueClassification { - Good, - Bad(Issue), - None, -} - -pub struct BadIssueSeeker { - state: Seeking, - report_todo: ReportTactic, - report_fixme: ReportTactic, -} - -impl BadIssueSeeker { - pub fn new(report_todo: ReportTactic, report_fixme: ReportTactic) -> BadIssueSeeker { - BadIssueSeeker { - state: Seeking::Issue { - todo_idx: 0, - fixme_idx: 0, - }, - report_todo, - report_fixme, - } - } - - // Check whether or not the current char is conclusive evidence for an - // unnumbered TO-DO or FIX-ME. - pub fn inspect(&mut self, c: char) -> Option<Issue> { - match self.state { - Seeking::Issue { - todo_idx, - fixme_idx, - } => { - self.state = self.inspect_issue(c, todo_idx, fixme_idx); - } - Seeking::Number { issue, part } => { - let result = self.inspect_number(c, issue, part); - - if let IssueClassification::None = result { - return None; - } - - self.state = Seeking::Issue { - todo_idx: 0, - fixme_idx: 0, - }; - - if let IssueClassification::Bad(issue) = result { - return Some(issue); - } - } - } - - None - } - - fn inspect_issue(&mut self, c: char, mut todo_idx: usize, mut fixme_idx: usize) -> Seeking { - if let Some(lower_case_c) = c.to_lowercase().next() { - if self.report_todo.is_enabled() && lower_case_c == TO_DO_CHARS[todo_idx] { - todo_idx += 1; - if todo_idx == TO_DO_CHARS.len() { - return Seeking::Number { - issue: Issue { - issue_type: IssueType::Todo, - missing_number: if let ReportTactic::Unnumbered = self.report_todo { - true - } else { - false - }, - }, - part: NumberPart::OpenParen, - }; - } - fixme_idx = 0; - } else if self.report_fixme.is_enabled() && lower_case_c == FIX_ME_CHARS[fixme_idx] { - // Exploit the fact that the character sets of todo and fixme - // are disjoint by adding else. - fixme_idx += 1; - if fixme_idx == FIX_ME_CHARS.len() { - return Seeking::Number { - issue: Issue { - issue_type: IssueType::Fixme, - missing_number: if let ReportTactic::Unnumbered = self.report_fixme { - true - } else { - false - }, - }, - part: NumberPart::OpenParen, - }; - } - todo_idx = 0; - } else { - todo_idx = 0; - fixme_idx = 0; - } - } - - Seeking::Issue { - todo_idx, - fixme_idx, - } - } - - fn inspect_number( - &mut self, - c: char, - issue: Issue, - mut part: NumberPart, - ) -> IssueClassification { - if !issue.missing_number || c == '\n' { - return IssueClassification::Bad(issue); - } else if c == ')' { - return if let NumberPart::CloseParen = part { - IssueClassification::Good - } else { - IssueClassification::Bad(issue) - }; - } - - match part { - NumberPart::OpenParen => { - if c != '(' { - return IssueClassification::Bad(issue); - } else { - part = NumberPart::Pound; - } - } - NumberPart::Pound => { - if c == '#' { - part = NumberPart::Number; - } - } - NumberPart::Number => { - if c >= '0' && c <= '9' { - part = NumberPart::CloseParen; - } else { - return IssueClassification::Bad(issue); - } - } - NumberPart::CloseParen => {} - } - - self.state = Seeking::Number { part, issue }; - - IssueClassification::None - } -} - -#[test] -fn find_unnumbered_issue() { - fn check_fail(text: &str, failing_pos: usize) { - let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered); - assert_eq!( - Some(failing_pos), - text.chars().position(|c| seeker.inspect(c).is_some()) - ); - } - - fn check_pass(text: &str) { - let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered); - assert_eq!(None, text.chars().position(|c| seeker.inspect(c).is_some())); - } - - check_fail("TODO\n", 4); - check_pass(" TO FIX DOME\n"); - check_fail(" \n FIXME\n", 8); - check_fail("FIXME(\n", 6); - check_fail("FIXME(#\n", 7); - check_fail("FIXME(#1\n", 8); - check_fail("FIXME(#)1\n", 7); - check_pass("FIXME(#1222)\n"); - check_fail("FIXME(#12\n22)\n", 9); - check_pass("FIXME(@maintainer, #1222, hello)\n"); - check_fail("TODO(#22) FIXME\n", 15); -} - -#[test] -fn find_issue() { - fn is_bad_issue(text: &str, report_todo: ReportTactic, report_fixme: ReportTactic) -> bool { - let mut seeker = BadIssueSeeker::new(report_todo, report_fixme); - text.chars().any(|c| seeker.inspect(c).is_some()) - } - - assert!(is_bad_issue( - "TODO(@maintainer, #1222, hello)\n", - ReportTactic::Always, - ReportTactic::Never, - )); - - assert!(!is_bad_issue( - "TODO: no number\n", - ReportTactic::Never, - ReportTactic::Always, - )); - - assert!(!is_bad_issue( - "Todo: mixed case\n", - ReportTactic::Never, - ReportTactic::Always, - )); - - assert!(is_bad_issue( - "This is a FIXME(#1)\n", - ReportTactic::Never, - ReportTactic::Always, - )); - - assert!(is_bad_issue( - "This is a FixMe(#1) mixed case\n", - ReportTactic::Never, - ReportTactic::Always, - )); - - assert!(!is_bad_issue( - "bad FIXME\n", - ReportTactic::Always, - ReportTactic::Never, - )); -} - -#[test] -fn issue_type() { - let mut seeker = BadIssueSeeker::new(ReportTactic::Always, ReportTactic::Never); - let expected = Some(Issue { - issue_type: IssueType::Todo, - missing_number: false, - }); - - assert_eq!( - expected, - "TODO(#100): more awesomeness" - .chars() - .map(|c| seeker.inspect(c)) - .find(Option::is_some) - .unwrap() - ); - - let mut seeker = BadIssueSeeker::new(ReportTactic::Never, ReportTactic::Unnumbered); - let expected = Some(Issue { - issue_type: IssueType::Fixme, - missing_number: true, - }); - - assert_eq!( - expected, - "Test. FIXME: bad, bad, not good" - .chars() - .map(|c| seeker.inspect(c)) - .find(Option::is_some) - .unwrap() - ); -} diff --git a/src/items.rs b/src/items.rs deleted file mode 100644 index 510c96681b8..00000000000 --- a/src/items.rs +++ /dev/null @@ -1,2845 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -// Formatting top-level items - functions, structs, enums, traits, impls. - -use std::borrow::Cow; -use std::cmp::min; - -use syntax::{abi, ast, ptr, symbol}; -use syntax::ast::{CrateSugar, ImplItem}; -use syntax::codemap::{BytePos, Span}; -use syntax::visit; - -use codemap::{LineRangeUtils, SpanUtils}; -use comment::{combine_strs_with_missing_comments, contains_comment, recover_comment_removed, - recover_missing_comment_in_span, rewrite_missing_comment, FindUncommented}; -use config::{BraceStyle, Config, Density, IndentStyle}; -use expr::{format_expr, is_empty_block, is_simple_block_stmt, rewrite_assign_rhs, - rewrite_call_inner, ExprType}; -use lists::{definitive_tactic, itemize_list, write_list, DefinitiveListTactic, ListFormatting, - ListItem, ListTactic, Separator, SeparatorPlace, SeparatorTactic}; -use rewrite::{Rewrite, RewriteContext}; -use shape::{Indent, Shape}; -use spanned::Spanned; -use types::join_bounds; -use utils::{colon_spaces, contains_skip, first_line_width, format_abi, format_constness, - format_defaultness, format_mutability, format_unsafety, format_visibility, - is_attributes_extendable, last_line_contains_single_line_comment, - last_line_extendable, last_line_used_width, last_line_width, mk_sp, - semicolon_for_expr, starts_with_newline, stmt_expr, trimmed_last_line_width}; -use vertical::rewrite_with_alignment; -use visitor::FmtVisitor; - -fn type_annotation_separator(config: &Config) -> &str { - colon_spaces(config.space_before_colon(), config.space_after_colon()) -} - -// Statements of the form -// let pat: ty = init; -impl Rewrite for ast::Local { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - debug!( - "Local::rewrite {:?} {} {:?}", - self, shape.width, shape.indent - ); - - skip_out_of_file_lines_range!(context, self.span); - - if contains_skip(&self.attrs) { - return None; - } - - let attrs_str = self.attrs.rewrite(context, shape)?; - let mut result = if attrs_str.is_empty() { - "let ".to_owned() - } else { - combine_strs_with_missing_comments( - context, - &attrs_str, - "let ", - mk_sp( - self.attrs.last().map(|a| a.span.hi()).unwrap(), - self.span.lo(), - ), - shape, - false, - )? - }; - - // 4 = "let ".len() - let pat_shape = shape.offset_left(4)?; - // 1 = ; - let pat_shape = pat_shape.sub_width(1)?; - let pat_str = self.pat.rewrite(context, pat_shape)?; - result.push_str(&pat_str); - - // String that is placed within the assignment pattern and expression. - let infix = { - let mut infix = String::with_capacity(32); - - if let Some(ref ty) = self.ty { - let separator = type_annotation_separator(context.config); - let indent = shape.indent + last_line_width(&result) + separator.len(); - // 1 = ; - let budget = shape.width.checked_sub(indent.width() + 1)?; - let rewrite = ty.rewrite(context, Shape::legacy(budget, indent))?; - - infix.push_str(separator); - infix.push_str(&rewrite); - } - - if self.init.is_some() { - infix.push_str(" ="); - } - - infix - }; - - result.push_str(&infix); - - if let Some(ref ex) = self.init { - // 1 = trailing semicolon; - let nested_shape = shape.sub_width(1)?; - - result = rewrite_assign_rhs(context, result, &**ex, nested_shape)?; - } - - result.push(';'); - Some(result) - } -} - -// TODO convert to using rewrite style rather than visitor -// TODO format modules in this style -#[allow(dead_code)] -struct Item<'a> { - keyword: &'static str, - abi: Cow<'static, str>, - vis: Option<&'a ast::Visibility>, - body: Vec<BodyElement<'a>>, - span: Span, -} - -impl<'a> Item<'a> { - fn from_foreign_mod(fm: &'a ast::ForeignMod, span: Span, config: &Config) -> Item<'a> { - Item { - keyword: "", - abi: format_abi(fm.abi, config.force_explicit_abi(), true), - vis: None, - body: fm.items - .iter() - .map(|i| BodyElement::ForeignItem(i)) - .collect(), - span, - } - } -} - -enum BodyElement<'a> { - // Stmt(&'a ast::Stmt), - // Field(&'a ast::Field), - // Variant(&'a ast::Variant), - // Item(&'a ast::Item), - ForeignItem(&'a ast::ForeignItem), -} - -/// Represents a fn's signature. -pub struct FnSig<'a> { - decl: &'a ast::FnDecl, - generics: &'a ast::Generics, - abi: abi::Abi, - constness: ast::Constness, - defaultness: ast::Defaultness, - unsafety: ast::Unsafety, - visibility: ast::Visibility, -} - -impl<'a> FnSig<'a> { - pub fn new( - decl: &'a ast::FnDecl, - generics: &'a ast::Generics, - vis: ast::Visibility, - ) -> FnSig<'a> { - FnSig { - decl, - generics, - abi: abi::Abi::Rust, - constness: ast::Constness::NotConst, - defaultness: ast::Defaultness::Final, - unsafety: ast::Unsafety::Normal, - visibility: vis, - } - } - - pub fn from_method_sig( - method_sig: &'a ast::MethodSig, - generics: &'a ast::Generics, - ) -> FnSig<'a> { - FnSig { - unsafety: method_sig.unsafety, - constness: method_sig.constness.node, - defaultness: ast::Defaultness::Final, - abi: method_sig.abi, - decl: &*method_sig.decl, - generics, - visibility: ast::Visibility::Inherited, - } - } - - pub fn from_fn_kind( - fn_kind: &'a visit::FnKind, - generics: &'a ast::Generics, - decl: &'a ast::FnDecl, - defualtness: ast::Defaultness, - ) -> FnSig<'a> { - match *fn_kind { - visit::FnKind::ItemFn(_, unsafety, constness, abi, visibility, _) => FnSig { - decl, - generics, - abi, - constness: constness.node, - defaultness: defualtness, - unsafety, - visibility: visibility.clone(), - }, - visit::FnKind::Method(_, method_sig, vis, _) => { - let mut fn_sig = FnSig::from_method_sig(method_sig, generics); - fn_sig.defaultness = defualtness; - if let Some(vis) = vis { - fn_sig.visibility = vis.clone(); - } - fn_sig - } - _ => unreachable!(), - } - } - - fn to_str(&self, context: &RewriteContext) -> String { - let mut result = String::with_capacity(128); - // Vis defaultness constness unsafety abi. - result.push_str(&*format_visibility(&self.visibility)); - result.push_str(format_defaultness(self.defaultness)); - result.push_str(format_constness(self.constness)); - result.push_str(format_unsafety(self.unsafety)); - result.push_str(&format_abi( - self.abi, - context.config.force_explicit_abi(), - false, - )); - result - } -} - -impl<'a> FmtVisitor<'a> { - fn format_item(&mut self, item: &Item) { - self.buffer.push_str(&item.abi); - - let snippet = self.snippet(item.span); - let brace_pos = snippet.find_uncommented("{").unwrap(); - - self.push_str("{"); - if !item.body.is_empty() || contains_comment(&snippet[brace_pos..]) { - // FIXME: this skips comments between the extern keyword and the opening - // brace. - self.last_pos = item.span.lo() + BytePos(brace_pos as u32 + 1); - self.block_indent = self.block_indent.block_indent(self.config); - - if item.body.is_empty() { - self.format_missing_no_indent(item.span.hi() - BytePos(1)); - self.block_indent = self.block_indent.block_unindent(self.config); - let indent_str = self.block_indent.to_string(self.config); - self.push_str(&indent_str); - } else { - for item in &item.body { - self.format_body_element(item); - } - - self.block_indent = self.block_indent.block_unindent(self.config); - self.format_missing_with_indent(item.span.hi() - BytePos(1)); - } - } - - self.push_str("}"); - self.last_pos = item.span.hi(); - } - - fn format_body_element(&mut self, element: &BodyElement) { - match *element { - BodyElement::ForeignItem(item) => self.format_foreign_item(item), - } - } - - pub fn format_foreign_mod(&mut self, fm: &ast::ForeignMod, span: Span) { - let item = Item::from_foreign_mod(fm, span, self.config); - self.format_item(&item); - } - - fn format_foreign_item(&mut self, item: &ast::ForeignItem) { - let rewrite = item.rewrite(&self.get_context(), self.shape()); - self.push_rewrite(item.span(), rewrite); - self.last_pos = item.span.hi(); - } - - pub fn rewrite_fn( - &mut self, - indent: Indent, - ident: ast::Ident, - fn_sig: &FnSig, - span: Span, - block: &ast::Block, - ) -> Option<String> { - let context = self.get_context(); - - let mut newline_brace = newline_for_brace(self.config, &fn_sig.generics.where_clause); - - let (mut result, force_newline_brace) = - rewrite_fn_base(&context, indent, ident, fn_sig, span, newline_brace, true)?; - - // 2 = ` {` - if self.config.brace_style() == BraceStyle::AlwaysNextLine || force_newline_brace - || last_line_width(&result) + 2 > self.shape().width - { - newline_brace = true; - } else if !result.contains('\n') { - newline_brace = false; - } - - // Prepare for the function body by possibly adding a newline and - // indent. - // FIXME we'll miss anything between the end of the signature and the - // start of the body, but we need more spans from the compiler to solve - // this. - if newline_brace { - result.push('\n'); - result.push_str(&indent.to_string(self.config)); - } else { - result.push(' '); - } - - self.single_line_fn(&result, block).or_else(|| Some(result)) - } - - pub fn rewrite_required_fn( - &mut self, - indent: Indent, - ident: ast::Ident, - sig: &ast::MethodSig, - generics: &ast::Generics, - span: Span, - ) -> Option<String> { - // Drop semicolon or it will be interpreted as comment. - let span = mk_sp(span.lo(), span.hi() - BytePos(1)); - let context = self.get_context(); - - let (mut result, _) = rewrite_fn_base( - &context, - indent, - ident, - &FnSig::from_method_sig(sig, generics), - span, - false, - false, - )?; - - // Re-attach semicolon - result.push(';'); - - Some(result) - } - - fn single_line_fn(&self, fn_str: &str, block: &ast::Block) -> Option<String> { - if fn_str.contains('\n') { - return None; - } - - let codemap = self.get_context().codemap; - - if self.config.empty_item_single_line() && is_empty_block(block, codemap) - && self.block_indent.width() + fn_str.len() + 2 <= self.config.max_width() - { - return Some(format!("{}{{}}", fn_str)); - } - - if self.config.fn_single_line() && is_simple_block_stmt(block, codemap) { - let rewrite = { - if let Some(stmt) = block.stmts.first() { - match stmt_expr(stmt) { - Some(e) => { - let suffix = if semicolon_for_expr(&self.get_context(), e) { - ";" - } else { - "" - }; - - format_expr(e, ExprType::Statement, &self.get_context(), self.shape()) - .map(|s| s + suffix) - .or_else(|| Some(self.snippet(e.span).to_owned())) - } - None => stmt.rewrite(&self.get_context(), self.shape()), - } - } else { - None - } - }; - - if let Some(res) = rewrite { - let width = self.block_indent.width() + fn_str.len() + res.len() + 4; - if !res.contains('\n') && width <= self.config.max_width() { - return Some(format!("{}{{ {} }}", fn_str, res)); - } - } - } - - None - } - - pub fn visit_static(&mut self, static_parts: &StaticParts) { - let rewrite = rewrite_static(&self.get_context(), static_parts, self.block_indent); - self.push_rewrite(static_parts.span, rewrite); - } - - pub fn visit_struct(&mut self, struct_parts: &StructParts) { - let is_tuple = struct_parts.def.is_tuple(); - let rewrite = format_struct(&self.get_context(), struct_parts, self.block_indent, None) - .map(|s| if is_tuple { s + ";" } else { s }); - self.push_rewrite(struct_parts.span, rewrite); - } - - pub fn visit_enum( - &mut self, - ident: ast::Ident, - vis: &ast::Visibility, - enum_def: &ast::EnumDef, - generics: &ast::Generics, - span: Span, - ) { - let enum_header = format_header("enum ", ident, vis); - self.push_str(&enum_header); - - let enum_snippet = self.snippet(span); - let brace_pos = enum_snippet.find_uncommented("{").unwrap(); - let body_start = span.lo() + BytePos(brace_pos as u32 + 1); - let generics_str = format_generics( - &self.get_context(), - generics, - self.config.brace_style(), - if enum_def.variants.is_empty() { - BracePos::ForceSameLine - } else { - BracePos::Auto - }, - self.block_indent, - mk_sp(span.lo(), body_start), - last_line_width(&enum_header), - ).unwrap(); - self.push_str(&generics_str); - - self.last_pos = body_start; - - self.block_indent = self.block_indent.block_indent(self.config); - let variant_list = self.format_variant_list(enum_def, body_start, span.hi() - BytePos(1)); - match variant_list { - Some(ref body_str) => self.push_str(body_str), - None => self.format_missing_no_indent(span.hi() - BytePos(1)), - } - self.block_indent = self.block_indent.block_unindent(self.config); - - if variant_list.is_some() || contains_comment(&enum_snippet[brace_pos..]) { - let indent_str = self.block_indent.to_string(self.config); - self.push_str(&indent_str); - } - self.push_str("}"); - self.last_pos = span.hi(); - } - - // Format the body of an enum definition - fn format_variant_list( - &self, - enum_def: &ast::EnumDef, - body_lo: BytePos, - body_hi: BytePos, - ) -> Option<String> { - if enum_def.variants.is_empty() { - return None; - } - let mut result = String::with_capacity(1024); - result.push('\n'); - let indentation = self.block_indent.to_string(self.config); - result.push_str(&indentation); - - let itemize_list_with = |one_line_width: usize| { - itemize_list( - self.codemap, - enum_def.variants.iter(), - "}", - ",", - |f| { - if !f.node.attrs.is_empty() { - f.node.attrs[0].span.lo() - } else { - f.span.lo() - } - }, - |f| f.span.hi(), - |f| self.format_variant(f, one_line_width), - body_lo, - body_hi, - false, - ).collect() - }; - let mut items: Vec<_> = - itemize_list_with(self.config.width_heuristics().struct_variant_width); - // If one of the variants use multiple lines, use multi-lined formatting for all variants. - let has_multiline_variant = items.iter().any(|item| item.inner_as_ref().contains('\n')); - let has_single_line_variant = items.iter().any(|item| !item.inner_as_ref().contains('\n')); - if has_multiline_variant && has_single_line_variant { - items = itemize_list_with(0); - } - - let shape = self.shape().sub_width(2)?; - let fmt = ListFormatting { - tactic: DefinitiveListTactic::Vertical, - separator: ",", - trailing_separator: self.config.trailing_comma(), - separator_place: SeparatorPlace::Back, - shape, - ends_with_newline: true, - preserve_newline: true, - config: self.config, - }; - - let list = write_list(&items, &fmt)?; - result.push_str(&list); - result.push('\n'); - Some(result) - } - - // Variant of an enum. - fn format_variant(&self, field: &ast::Variant, one_line_width: usize) -> Option<String> { - if contains_skip(&field.node.attrs) { - let lo = field.node.attrs[0].span.lo(); - let span = mk_sp(lo, field.span.hi()); - return Some(self.snippet(span).to_owned()); - } - - let context = self.get_context(); - // 1 = ',' - let shape = self.shape().sub_width(1)?; - let attrs_str = field.node.attrs.rewrite(&context, shape)?; - let lo = field - .node - .attrs - .last() - .map_or(field.span.lo(), |attr| attr.span.hi()); - let span = mk_sp(lo, field.span.lo()); - - let variant_body = match field.node.data { - ast::VariantData::Tuple(..) | ast::VariantData::Struct(..) => format_struct( - &context, - &StructParts::from_variant(field), - self.block_indent, - Some(one_line_width), - )?, - ast::VariantData::Unit(..) => { - if let Some(ref expr) = field.node.disr_expr { - let lhs = format!("{} =", field.node.name); - rewrite_assign_rhs(&context, lhs, &**expr, shape)? - } else { - field.node.name.to_string() - } - } - }; - - combine_strs_with_missing_comments(&context, &attrs_str, &variant_body, span, shape, false) - } -} - -pub fn format_impl( - context: &RewriteContext, - item: &ast::Item, - offset: Indent, - where_span_end: Option<BytePos>, -) -> Option<String> { - if let ast::ItemKind::Impl(_, _, _, ref generics, _, ref self_ty, ref items) = item.node { - let mut result = String::with_capacity(128); - let ref_and_type = format_impl_ref_and_type(context, item, offset)?; - let indent_str = offset.to_string(context.config); - let sep = format!("\n{}", &indent_str); - result.push_str(&ref_and_type); - - let where_budget = if result.contains('\n') { - context.config.max_width() - } else { - context.budget(last_line_width(&result)) - }; - let option = WhereClauseOption::snuggled(&ref_and_type); - let where_clause_str = rewrite_where_clause( - context, - &generics.where_clause, - context.config.brace_style(), - Shape::legacy(where_budget, offset.block_only()), - Density::Vertical, - "{", - where_span_end, - self_ty.span.hi(), - option, - false, - )?; - - // If there is no where clause, we may have missing comments between the trait name and - // the opening brace. - if generics.where_clause.predicates.is_empty() { - if let Some(hi) = where_span_end { - match recover_missing_comment_in_span( - mk_sp(self_ty.span.hi(), hi), - Shape::indented(offset, context.config), - context, - last_line_width(&result), - ) { - Some(ref missing_comment) if !missing_comment.is_empty() => { - result.push_str(missing_comment); - } - _ => (), - } - } - } - - if is_impl_single_line(context, items, &result, &where_clause_str, item)? { - result.push_str(&where_clause_str); - if where_clause_str.contains('\n') || last_line_contains_single_line_comment(&result) { - result.push_str(&format!("{}{{{}}}", &sep, &sep)); - } else { - result.push_str(" {}"); - } - return Some(result); - } - - if !where_clause_str.is_empty() && !where_clause_str.contains('\n') { - result.push('\n'); - let width = offset.block_indent + context.config.tab_spaces() - 1; - let where_indent = Indent::new(0, width); - result.push_str(&where_indent.to_string(context.config)); - } - result.push_str(&where_clause_str); - - let need_newline = !last_line_extendable(&result) - && (last_line_contains_single_line_comment(&result) || result.contains('\n')); - match context.config.brace_style() { - _ if need_newline => result.push_str(&sep), - BraceStyle::AlwaysNextLine => result.push_str(&sep), - BraceStyle::PreferSameLine => result.push(' '), - BraceStyle::SameLineWhere => { - if !where_clause_str.is_empty() { - result.push_str(&sep); - } else { - result.push(' '); - } - } - } - - result.push('{'); - - let snippet = context.snippet(item.span); - let open_pos = snippet.find_uncommented("{")? + 1; - - if !items.is_empty() || contains_comment(&snippet[open_pos..]) { - let mut visitor = FmtVisitor::from_context(context); - visitor.block_indent = offset.block_only().block_indent(context.config); - visitor.last_pos = item.span.lo() + BytePos(open_pos as u32); - - visitor.visit_attrs(&item.attrs, ast::AttrStyle::Inner); - for item in items { - visitor.visit_impl_item(item); - } - - visitor.format_missing(item.span.hi() - BytePos(1)); - - let inner_indent_str = visitor.block_indent.to_string(context.config); - let outer_indent_str = offset.block_only().to_string(context.config); - - result.push('\n'); - result.push_str(&inner_indent_str); - result.push_str(visitor.buffer.to_string().trim()); - result.push('\n'); - result.push_str(&outer_indent_str); - } - - if result.ends_with('{') { - result.push_str(&sep); - } - result.push('}'); - - Some(result) - } else { - unreachable!(); - } -} - -fn is_impl_single_line( - context: &RewriteContext, - items: &[ImplItem], - result: &str, - where_clause_str: &str, - item: &ast::Item, -) -> Option<bool> { - let snippet = context.snippet(item.span); - let open_pos = snippet.find_uncommented("{")? + 1; - - Some( - context.config.empty_item_single_line() && items.is_empty() && !result.contains('\n') - && result.len() + where_clause_str.len() <= context.config.max_width() - && !contains_comment(&snippet[open_pos..]), - ) -} - -fn format_impl_ref_and_type( - context: &RewriteContext, - item: &ast::Item, - offset: Indent, -) -> Option<String> { - if let ast::ItemKind::Impl( - unsafety, - polarity, - defaultness, - ref generics, - ref trait_ref, - ref self_ty, - _, - ) = item.node - { - let mut result = String::with_capacity(128); - - result.push_str(&format_visibility(&item.vis)); - result.push_str(format_defaultness(defaultness)); - result.push_str(format_unsafety(unsafety)); - result.push_str("impl"); - - let lo = context.codemap.span_after(item.span, "impl"); - let hi = match *trait_ref { - Some(ref tr) => tr.path.span.lo(), - None => self_ty.span.lo(), - }; - let shape = generics_shape_from_config( - context.config, - Shape::indented(offset + last_line_width(&result), context.config), - 0, - )?; - let one_line_budget = shape.width.checked_sub(last_line_width(&result) + 2)?; - let generics_str = - rewrite_generics_inner(context, generics, shape, one_line_budget, mk_sp(lo, hi))?; - - let polarity_str = if polarity == ast::ImplPolarity::Negative { - "!" - } else { - "" - }; - - if let Some(ref trait_ref) = *trait_ref { - let result_len = result.len(); - if let Some(trait_ref_str) = rewrite_trait_ref( - context, - trait_ref, - offset, - &generics_str, - true, - polarity_str, - result_len, - ) { - result.push_str(&trait_ref_str); - } else { - let generics_str = - rewrite_generics_inner(context, generics, shape, 0, mk_sp(lo, hi))?; - result.push_str(&rewrite_trait_ref( - context, - trait_ref, - offset, - &generics_str, - false, - polarity_str, - result_len, - )?); - } - } else { - result.push_str(&generics_str); - } - - // Try to put the self type in a single line. - // ` for` - let trait_ref_overhead = if trait_ref.is_some() { 4 } else { 0 }; - let curly_brace_overhead = if generics.where_clause.predicates.is_empty() { - // If there is no where clause adapt budget for type formatting to take space and curly - // brace into account. - match context.config.brace_style() { - BraceStyle::AlwaysNextLine => 0, - _ => 2, - } - } else { - 0 - }; - let used_space = last_line_width(&result) + trait_ref_overhead + curly_brace_overhead; - // 1 = space before the type. - let budget = context.budget(used_space + 1); - if let Some(self_ty_str) = self_ty.rewrite(context, Shape::legacy(budget, offset)) { - if !self_ty_str.contains('\n') { - if trait_ref.is_some() { - result.push_str(" for "); - } else { - result.push(' '); - } - result.push_str(&self_ty_str); - return Some(result); - } - } - - // Couldn't fit the self type on a single line, put it on a new line. - result.push('\n'); - // Add indentation of one additional tab. - let new_line_offset = offset.block_indent(context.config); - result.push_str(&new_line_offset.to_string(context.config)); - if trait_ref.is_some() { - result.push_str("for "); - } - let budget = context.budget(last_line_width(&result)); - let type_offset = match context.config.indent_style() { - IndentStyle::Visual => new_line_offset + trait_ref_overhead, - IndentStyle::Block => new_line_offset, - }; - result.push_str(&*self_ty.rewrite(context, Shape::legacy(budget, type_offset))?); - Some(result) - } else { - unreachable!(); - } -} - -fn rewrite_trait_ref( - context: &RewriteContext, - trait_ref: &ast::TraitRef, - offset: Indent, - generics_str: &str, - retry: bool, - polarity_str: &str, - result_len: usize, -) -> Option<String> { - // 1 = space between generics and trait_ref - let used_space = 1 + polarity_str.len() + last_line_used_width(generics_str, result_len); - let shape = Shape::indented(offset + used_space, context.config); - if let Some(trait_ref_str) = trait_ref.rewrite(context, shape) { - if !(retry && trait_ref_str.contains('\n')) { - return Some(format!( - "{} {}{}", - generics_str, polarity_str, &trait_ref_str - )); - } - } - // We could not make enough space for trait_ref, so put it on new line. - if !retry { - let offset = offset.block_indent(context.config); - let shape = Shape::indented(offset, context.config); - let trait_ref_str = trait_ref.rewrite(context, shape)?; - Some(format!( - "{}\n{}{}{}", - generics_str, - &offset.to_string(context.config), - polarity_str, - &trait_ref_str - )) - } else { - None - } -} - -pub struct StructParts<'a> { - prefix: &'a str, - ident: ast::Ident, - vis: &'a ast::Visibility, - def: &'a ast::VariantData, - generics: Option<&'a ast::Generics>, - span: Span, -} - -impl<'a> StructParts<'a> { - fn format_header(&self) -> String { - format_header(self.prefix, self.ident, self.vis) - } - - fn from_variant(variant: &'a ast::Variant) -> Self { - StructParts { - prefix: "", - ident: variant.node.name, - vis: &ast::Visibility::Inherited, - def: &variant.node.data, - generics: None, - span: variant.span, - } - } - - pub fn from_item(item: &'a ast::Item) -> Self { - let (prefix, def, generics) = match item.node { - ast::ItemKind::Struct(ref def, ref generics) => ("struct ", def, generics), - ast::ItemKind::Union(ref def, ref generics) => ("union ", def, generics), - _ => unreachable!(), - }; - StructParts { - prefix, - ident: item.ident, - vis: &item.vis, - def, - generics: Some(generics), - span: item.span, - } - } -} - -fn format_struct( - context: &RewriteContext, - struct_parts: &StructParts, - offset: Indent, - one_line_width: Option<usize>, -) -> Option<String> { - match *struct_parts.def { - ast::VariantData::Unit(..) => format_unit_struct(context, struct_parts, offset), - ast::VariantData::Tuple(ref fields, _) => { - format_tuple_struct(context, struct_parts, fields, offset) - } - ast::VariantData::Struct(ref fields, _) => { - format_struct_struct(context, struct_parts, fields, offset, one_line_width) - } - } -} - -pub fn format_trait(context: &RewriteContext, item: &ast::Item, offset: Indent) -> Option<String> { - if let ast::ItemKind::Trait(_, unsafety, ref generics, ref type_param_bounds, ref trait_items) = - item.node - { - let mut result = String::with_capacity(128); - let header = format!( - "{}{}trait {}", - format_visibility(&item.vis), - format_unsafety(unsafety), - item.ident - ); - - result.push_str(&header); - - let body_lo = context.codemap.span_after(item.span, "{"); - - let shape = Shape::indented(offset, context.config).offset_left(result.len())?; - let generics_str = - rewrite_generics(context, generics, shape, mk_sp(item.span.lo(), body_lo))?; - result.push_str(&generics_str); - - // FIXME(#2055): rustfmt fails to format when there are comments between trait bounds. - if !type_param_bounds.is_empty() { - let ident_hi = context - .codemap - .span_after(item.span, &format!("{}", item.ident)); - let bound_hi = type_param_bounds.last().unwrap().span().hi(); - let snippet = context.snippet(mk_sp(ident_hi, bound_hi)); - if contains_comment(snippet) { - return None; - } - } - let trait_bound_str = rewrite_trait_bounds( - context, - type_param_bounds, - Shape::indented(offset, context.config), - )?; - // If the trait, generics, and trait bound cannot fit on the same line, - // put the trait bounds on an indented new line - if offset.width() + last_line_width(&result) + trait_bound_str.len() - > context.config.comment_width() - { - result.push('\n'); - let trait_indent = offset.block_only().block_indent(context.config); - result.push_str(&trait_indent.to_string(context.config)); - } - result.push_str(&trait_bound_str); - - let where_density = - if context.config.indent_style() == IndentStyle::Block && result.is_empty() { - Density::Compressed - } else { - Density::Tall - }; - - let where_budget = context.budget(last_line_width(&result)); - let pos_before_where = if type_param_bounds.is_empty() { - generics.where_clause.span.lo() - } else { - type_param_bounds[type_param_bounds.len() - 1].span().hi() - }; - let option = WhereClauseOption::snuggled(&generics_str); - let where_clause_str = rewrite_where_clause( - context, - &generics.where_clause, - context.config.brace_style(), - Shape::legacy(where_budget, offset.block_only()), - where_density, - "{", - None, - pos_before_where, - option, - false, - )?; - // If the where clause cannot fit on the same line, - // put the where clause on a new line - if !where_clause_str.contains('\n') - && last_line_width(&result) + where_clause_str.len() + offset.width() - > context.config.comment_width() - { - result.push('\n'); - let width = offset.block_indent + context.config.tab_spaces() - 1; - let where_indent = Indent::new(0, width); - result.push_str(&where_indent.to_string(context.config)); - } - result.push_str(&where_clause_str); - - if generics.where_clause.predicates.is_empty() { - let item_snippet = context.snippet(item.span); - if let Some(lo) = item_snippet.chars().position(|c| c == '/') { - // 1 = `{` - let comment_hi = body_lo - BytePos(1); - let comment_lo = item.span.lo() + BytePos(lo as u32); - if comment_lo < comment_hi { - match recover_missing_comment_in_span( - mk_sp(comment_lo, comment_hi), - Shape::indented(offset, context.config), - context, - last_line_width(&result), - ) { - Some(ref missing_comment) if !missing_comment.is_empty() => { - result.push_str(missing_comment); - } - _ => (), - } - } - } - } - - match context.config.brace_style() { - _ if last_line_contains_single_line_comment(&result) => { - result.push('\n'); - result.push_str(&offset.to_string(context.config)); - } - BraceStyle::AlwaysNextLine => { - result.push('\n'); - result.push_str(&offset.to_string(context.config)); - } - BraceStyle::PreferSameLine => result.push(' '), - BraceStyle::SameLineWhere => { - if !where_clause_str.is_empty() - && (!trait_items.is_empty() || result.contains('\n')) - { - result.push('\n'); - result.push_str(&offset.to_string(context.config)); - } else { - result.push(' '); - } - } - } - result.push('{'); - - let snippet = context.snippet(item.span); - let open_pos = snippet.find_uncommented("{")? + 1; - - if !trait_items.is_empty() || contains_comment(&snippet[open_pos..]) { - let mut visitor = FmtVisitor::from_context(context); - visitor.block_indent = offset.block_only().block_indent(context.config); - visitor.last_pos = item.span.lo() + BytePos(open_pos as u32); - - for item in trait_items { - visitor.visit_trait_item(item); - } - - visitor.format_missing(item.span.hi() - BytePos(1)); - - let inner_indent_str = visitor.block_indent.to_string(context.config); - let outer_indent_str = offset.block_only().to_string(context.config); - - result.push('\n'); - result.push_str(&inner_indent_str); - result.push_str(visitor.buffer.to_string().trim()); - result.push('\n'); - result.push_str(&outer_indent_str); - } else if result.contains('\n') { - result.push('\n'); - } - - result.push('}'); - Some(result) - } else { - unreachable!(); - } -} - -pub fn format_trait_alias( - context: &RewriteContext, - ident: ast::Ident, - generics: &ast::Generics, - ty_param_bounds: &ast::TyParamBounds, - shape: Shape, -) -> Option<String> { - let alias = ident.name.as_str(); - // 6 = "trait ", 2 = " =" - let g_shape = shape.offset_left(6 + alias.len())?.sub_width(2)?; - let generics_str = rewrite_generics(context, generics, g_shape, generics.span)?; - let lhs = format!("trait {}{} =", alias, generics_str); - // 1 = ";" - rewrite_assign_rhs(context, lhs, ty_param_bounds, shape.sub_width(1)?).map(|s| s + ";") -} - -fn format_unit_struct(context: &RewriteContext, p: &StructParts, offset: Indent) -> Option<String> { - let header_str = format_header(p.prefix, p.ident, p.vis); - let generics_str = if let Some(generics) = p.generics { - let hi = if generics.where_clause.predicates.is_empty() { - generics.span.hi() - } else { - generics.where_clause.span.hi() - }; - format_generics( - context, - generics, - context.config.brace_style(), - BracePos::None, - offset, - mk_sp(generics.span.lo(), hi), - last_line_width(&header_str), - )? - } else { - String::new() - }; - Some(format!("{}{};", header_str, generics_str)) -} - -pub fn format_struct_struct( - context: &RewriteContext, - struct_parts: &StructParts, - fields: &[ast::StructField], - offset: Indent, - one_line_width: Option<usize>, -) -> Option<String> { - let mut result = String::with_capacity(1024); - let span = struct_parts.span; - - let header_str = struct_parts.format_header(); - result.push_str(&header_str); - - let header_hi = span.lo() + BytePos(header_str.len() as u32); - let body_lo = context.codemap.span_after(span, "{"); - - let generics_str = match struct_parts.generics { - Some(g) => format_generics( - context, - g, - context.config.brace_style(), - if fields.is_empty() { - BracePos::ForceSameLine - } else { - BracePos::Auto - }, - offset, - mk_sp(header_hi, body_lo), - last_line_width(&result), - )?, - None => { - // 3 = ` {}`, 2 = ` {`. - let overhead = if fields.is_empty() { 3 } else { 2 }; - if (context.config.brace_style() == BraceStyle::AlwaysNextLine && !fields.is_empty()) - || context.config.max_width() < overhead + result.len() - { - format!("\n{}{{", offset.block_only().to_string(context.config)) - } else { - " {".to_owned() - } - } - }; - // 1 = `}` - let overhead = if fields.is_empty() { 1 } else { 0 }; - let total_width = result.len() + generics_str.len() + overhead; - if !generics_str.is_empty() && !generics_str.contains('\n') - && total_width > context.config.max_width() - { - result.push('\n'); - result.push_str(&offset.to_string(context.config)); - result.push_str(generics_str.trim_left()); - } else { - result.push_str(&generics_str); - } - - if fields.is_empty() { - let snippet = context.snippet(mk_sp(body_lo, span.hi() - BytePos(1))); - if snippet.trim().is_empty() { - // `struct S {}` - } else if snippet.trim_right_matches(&[' ', '\t'][..]).ends_with('\n') { - // fix indent - result.push_str(snippet.trim_right()); - result.push('\n'); - result.push_str(&offset.to_string(context.config)); - } else { - result.push_str(snippet); - } - result.push('}'); - return Some(result); - } - - // 3 = ` ` and ` }` - let one_line_budget = context.budget(result.len() + 3 + offset.width()); - let one_line_budget = - one_line_width.map_or(0, |one_line_width| min(one_line_width, one_line_budget)); - - let items_str = rewrite_with_alignment( - fields, - context, - Shape::indented(offset, context.config).sub_width(1)?, - mk_sp(body_lo, span.hi()), - one_line_budget, - )?; - - if !items_str.contains('\n') && !result.contains('\n') && items_str.len() <= one_line_budget { - Some(format!("{} {} }}", result, items_str)) - } else { - Some(format!( - "{}\n{}{}\n{}}}", - result, - offset - .block_indent(context.config) - .to_string(context.config), - items_str, - offset.to_string(context.config) - )) - } -} - -/// Returns a bytepos that is after that of `(` in `pub(..)`. If the given visibility does not -/// contain `pub(..)`, then return the `lo` of the `defualt_span`. Yeah, but for what? Well, we need -/// to bypass the `(` in the visibility when creating a span of tuple's body or fn's args. -fn get_bytepos_after_visibility( - context: &RewriteContext, - vis: &ast::Visibility, - default_span: Span, - terminator: &str, -) -> BytePos { - match *vis { - ast::Visibility::Crate(s, CrateSugar::PubCrate) => context - .codemap - .span_after(mk_sp(s.hi(), default_span.hi()), terminator), - ast::Visibility::Crate(s, CrateSugar::JustCrate) => s.hi(), - ast::Visibility::Restricted { ref path, .. } => path.span.hi(), - _ => default_span.lo(), - } -} - -fn format_tuple_struct( - context: &RewriteContext, - struct_parts: &StructParts, - fields: &[ast::StructField], - offset: Indent, -) -> Option<String> { - let mut result = String::with_capacity(1024); - let span = struct_parts.span; - - let header_str = struct_parts.format_header(); - result.push_str(&header_str); - - let body_lo = if fields.is_empty() { - let lo = get_bytepos_after_visibility(context, struct_parts.vis, span, ")"); - context.codemap.span_after(mk_sp(lo, span.hi()), "(") - } else { - fields[0].span.lo() - }; - let body_hi = if fields.is_empty() { - context.codemap.span_after(mk_sp(body_lo, span.hi()), ")") - } else { - // This is a dirty hack to work around a missing `)` from the span of the last field. - let last_arg_span = fields[fields.len() - 1].span; - if context.snippet(last_arg_span).ends_with(')') { - last_arg_span.hi() - } else { - context - .codemap - .span_after(mk_sp(last_arg_span.hi(), span.hi()), ")") - } - }; - - let where_clause_str = match struct_parts.generics { - Some(generics) => { - let budget = context.budget(last_line_width(&header_str)); - let shape = Shape::legacy(budget, offset); - let g_span = mk_sp(span.lo(), body_lo); - let generics_str = rewrite_generics(context, generics, shape, g_span)?; - result.push_str(&generics_str); - - let where_budget = context.budget(last_line_width(&result)); - let option = WhereClauseOption::new(true, false); - rewrite_where_clause( - context, - &generics.where_clause, - context.config.brace_style(), - Shape::legacy(where_budget, offset.block_only()), - Density::Compressed, - ";", - None, - body_hi, - option, - false, - )? - } - None => "".to_owned(), - }; - - if fields.is_empty() { - // 3 = `();` - let used_width = last_line_used_width(&result, offset.width()) + 3; - if used_width > context.config.max_width() { - result.push('\n'); - result.push_str(&offset - .block_indent(context.config) - .to_string(context.config)) - } - result.push('('); - let snippet = context.snippet(mk_sp( - body_lo, - context.codemap.span_before(mk_sp(body_lo, span.hi()), ")"), - )); - if snippet.is_empty() { - // `struct S ()` - } else if snippet.trim_right_matches(&[' ', '\t'][..]).ends_with('\n') { - result.push_str(snippet.trim_right()); - result.push('\n'); - result.push_str(&offset.to_string(context.config)); - } else { - result.push_str(snippet); - } - result.push(')'); - } else { - let shape = Shape::indented(offset, context.config).sub_width(1)?; - let fields = &fields.iter().collect::<Vec<_>>()[..]; - let one_line_width = context.config.width_heuristics().fn_call_width; - result = rewrite_call_inner(context, &result, fields, span, shape, one_line_width, false)?; - } - - if !where_clause_str.is_empty() && !where_clause_str.contains('\n') - && (result.contains('\n') - || offset.block_indent + result.len() + where_clause_str.len() + 1 - > context.config.max_width()) - { - // We need to put the where clause on a new line, but we didn't - // know that earlier, so the where clause will not be indented properly. - result.push('\n'); - result - .push_str(&(offset.block_only() + (context.config.tab_spaces() - 1)) - .to_string(context.config)); - } - result.push_str(&where_clause_str); - - Some(result) -} - -pub fn rewrite_type_alias( - context: &RewriteContext, - indent: Indent, - ident: ast::Ident, - ty: &ast::Ty, - generics: &ast::Generics, - vis: &ast::Visibility, - span: Span, -) -> Option<String> { - let mut result = String::with_capacity(128); - - result.push_str(&format_visibility(vis)); - result.push_str("type "); - result.push_str(&ident.to_string()); - - // 2 = `= ` - let g_shape = Shape::indented(indent, context.config) - .offset_left(result.len())? - .sub_width(2)?; - let g_span = mk_sp(context.codemap.span_after(span, "type"), ty.span.lo()); - let generics_str = rewrite_generics(context, generics, g_shape, g_span)?; - result.push_str(&generics_str); - - let where_budget = context.budget(last_line_width(&result)); - let option = WhereClauseOption::snuggled(&result); - let where_clause_str = rewrite_where_clause( - context, - &generics.where_clause, - context.config.brace_style(), - Shape::legacy(where_budget, indent), - Density::Vertical, - "=", - Some(span.hi()), - generics.span.hi(), - option, - false, - )?; - result.push_str(&where_clause_str); - if where_clause_str.is_empty() { - result.push_str(" ="); - } else { - result.push_str(&format!("\n{}=", indent.to_string(context.config))); - } - - // 1 = ";" - let ty_shape = Shape::indented(indent, context.config).sub_width(1)?; - rewrite_assign_rhs(context, result, ty, ty_shape).map(|s| s + ";") -} - -fn type_annotation_spacing(config: &Config) -> (&str, &str) { - ( - if config.space_before_colon() { " " } else { "" }, - if config.space_after_colon() { " " } else { "" }, - ) -} - -pub fn rewrite_struct_field_prefix( - context: &RewriteContext, - field: &ast::StructField, -) -> Option<String> { - let vis = format_visibility(&field.vis); - let type_annotation_spacing = type_annotation_spacing(context.config); - Some(match field.ident { - Some(name) => format!("{}{}{}:", vis, name, type_annotation_spacing.0), - None => format!("{}", vis), - }) -} - -impl Rewrite for ast::StructField { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - rewrite_struct_field(context, self, shape, 0) - } -} - -pub fn rewrite_struct_field( - context: &RewriteContext, - field: &ast::StructField, - shape: Shape, - lhs_max_width: usize, -) -> Option<String> { - if contains_skip(&field.attrs) { - return Some(context.snippet(field.span()).to_owned()); - } - - let type_annotation_spacing = type_annotation_spacing(context.config); - let prefix = rewrite_struct_field_prefix(context, field)?; - - let attrs_str = field.attrs.rewrite(context, shape)?; - let attrs_extendable = field.ident.is_none() && is_attributes_extendable(&attrs_str); - let missing_span = if field.attrs.is_empty() { - mk_sp(field.span.lo(), field.span.lo()) - } else { - mk_sp(field.attrs.last().unwrap().span.hi(), field.span.lo()) - }; - let mut spacing = String::from(if field.ident.is_some() { - type_annotation_spacing.1 - } else { - "" - }); - // Try to put everything on a single line. - let attr_prefix = combine_strs_with_missing_comments( - context, - &attrs_str, - &prefix, - missing_span, - shape, - attrs_extendable, - )?; - let overhead = last_line_width(&attr_prefix); - let lhs_offset = lhs_max_width.checked_sub(overhead).unwrap_or(0); - for _ in 0..lhs_offset { - spacing.push(' '); - } - // In this extreme case we will be missing a space betweeen an attribute and a field. - if prefix.is_empty() && !attrs_str.is_empty() && attrs_extendable && spacing.is_empty() { - spacing.push(' '); - } - let orig_ty = shape - .offset_left(overhead + spacing.len()) - .and_then(|ty_shape| field.ty.rewrite(context, ty_shape)); - if let Some(ref ty) = orig_ty { - if !ty.contains('\n') { - return Some(attr_prefix + &spacing + ty); - } - } - - let is_prefix_empty = prefix.is_empty(); - // We must use multiline. We are going to put attributes and a field on different lines. - let field_str = rewrite_assign_rhs(context, prefix, &*field.ty, shape)?; - // Remove a leading white-space from `rewrite_assign_rhs()` when rewriting a tuple struct. - let field_str = if is_prefix_empty { - field_str.trim_left() - } else { - &field_str - }; - combine_strs_with_missing_comments(context, &attrs_str, field_str, missing_span, shape, false) -} - -pub struct StaticParts<'a> { - prefix: &'a str, - vis: &'a ast::Visibility, - ident: ast::Ident, - ty: &'a ast::Ty, - mutability: ast::Mutability, - expr_opt: Option<&'a ptr::P<ast::Expr>>, - defaultness: Option<ast::Defaultness>, - span: Span, -} - -impl<'a> StaticParts<'a> { - pub fn from_item(item: &'a ast::Item) -> Self { - let (prefix, ty, mutability, expr) = match item.node { - ast::ItemKind::Static(ref ty, mutability, ref expr) => ("static", ty, mutability, expr), - ast::ItemKind::Const(ref ty, ref expr) => { - ("const", ty, ast::Mutability::Immutable, expr) - } - _ => unreachable!(), - }; - StaticParts { - prefix, - vis: &item.vis, - ident: item.ident, - ty, - mutability, - expr_opt: Some(expr), - defaultness: None, - span: item.span, - } - } - - pub fn from_trait_item(ti: &'a ast::TraitItem) -> Self { - let (ty, expr_opt) = match ti.node { - ast::TraitItemKind::Const(ref ty, ref expr_opt) => (ty, expr_opt), - _ => unreachable!(), - }; - StaticParts { - prefix: "const", - vis: &ast::Visibility::Inherited, - ident: ti.ident, - ty, - mutability: ast::Mutability::Immutable, - expr_opt: expr_opt.as_ref(), - defaultness: None, - span: ti.span, - } - } - - pub fn from_impl_item(ii: &'a ast::ImplItem) -> Self { - let (ty, expr) = match ii.node { - ast::ImplItemKind::Const(ref ty, ref expr) => (ty, expr), - _ => unreachable!(), - }; - StaticParts { - prefix: "const", - vis: &ii.vis, - ident: ii.ident, - ty, - mutability: ast::Mutability::Immutable, - expr_opt: Some(expr), - defaultness: Some(ii.defaultness), - span: ii.span, - } - } -} - -fn rewrite_static( - context: &RewriteContext, - static_parts: &StaticParts, - offset: Indent, -) -> Option<String> { - let colon = colon_spaces( - context.config.space_before_colon(), - context.config.space_after_colon(), - ); - let mut prefix = format!( - "{}{}{} {}{}{}", - format_visibility(static_parts.vis), - static_parts.defaultness.map_or("", format_defaultness), - static_parts.prefix, - format_mutability(static_parts.mutability), - static_parts.ident, - colon, - ); - // 2 = " =".len() - let ty_shape = - Shape::indented(offset.block_only(), context.config).offset_left(prefix.len() + 2)?; - let ty_str = match static_parts.ty.rewrite(context, ty_shape) { - Some(ty_str) => ty_str, - None => { - if prefix.ends_with(' ') { - prefix.pop(); - } - let nested_indent = offset.block_indent(context.config); - let nested_shape = Shape::indented(nested_indent, context.config); - let ty_str = static_parts.ty.rewrite(context, nested_shape)?; - format!("\n{}{}", nested_indent.to_string(context.config), ty_str) - } - }; - - if let Some(expr) = static_parts.expr_opt { - let lhs = format!("{}{} =", prefix, ty_str); - // 1 = ; - let remaining_width = context.budget(offset.block_indent + 1); - rewrite_assign_rhs( - context, - lhs, - &**expr, - Shape::legacy(remaining_width, offset.block_only()), - ).and_then(|res| recover_comment_removed(res, static_parts.span, context)) - .map(|s| if s.ends_with(';') { s } else { s + ";" }) - } else { - Some(format!("{}{};", prefix, ty_str)) - } -} - -pub fn rewrite_associated_type( - ident: ast::Ident, - ty_opt: Option<&ptr::P<ast::Ty>>, - ty_param_bounds_opt: Option<&ast::TyParamBounds>, - context: &RewriteContext, - indent: Indent, -) -> Option<String> { - let prefix = format!("type {}", ident); - - let type_bounds_str = if let Some(bounds) = ty_param_bounds_opt { - // 2 = ": ".len() - let shape = Shape::indented(indent, context.config).offset_left(prefix.len() + 2)?; - let bound_str = bounds - .iter() - .map(|ty_bound| ty_bound.rewrite(context, shape)) - .collect::<Option<Vec<_>>>()?; - if !bounds.is_empty() { - format!(": {}", join_bounds(context, shape, &bound_str)) - } else { - String::new() - } - } else { - String::new() - }; - - if let Some(ty) = ty_opt { - // 1 = `;` - let shape = Shape::indented(indent, context.config).sub_width(1)?; - let lhs = format!("{}{} =", prefix, type_bounds_str); - rewrite_assign_rhs(context, lhs, &**ty, shape).map(|s| s + ";") - } else { - Some(format!("{}{};", prefix, type_bounds_str)) - } -} - -pub fn rewrite_associated_impl_type( - ident: ast::Ident, - defaultness: ast::Defaultness, - ty_opt: Option<&ptr::P<ast::Ty>>, - ty_param_bounds_opt: Option<&ast::TyParamBounds>, - context: &RewriteContext, - indent: Indent, -) -> Option<String> { - let result = rewrite_associated_type(ident, ty_opt, ty_param_bounds_opt, context, indent)?; - - match defaultness { - ast::Defaultness::Default => Some(format!("default {}", result)), - _ => Some(result), - } -} - -impl Rewrite for ast::FunctionRetTy { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - match *self { - ast::FunctionRetTy::Default(_) => Some(String::new()), - ast::FunctionRetTy::Ty(ref ty) => { - let inner_width = shape.width.checked_sub(3)?; - ty.rewrite(context, Shape::legacy(inner_width, shape.indent + 3)) - .map(|r| format!("-> {}", r)) - } - } - } -} - -fn is_empty_infer(context: &RewriteContext, ty: &ast::Ty) -> bool { - match ty.node { - ast::TyKind::Infer => { - let original = context.snippet(ty.span); - original != "_" - } - _ => false, - } -} - -impl Rewrite for ast::Arg { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - if is_named_arg(self) { - let mut result = self.pat - .rewrite(context, Shape::legacy(shape.width, shape.indent))?; - - if !is_empty_infer(context, &*self.ty) { - if context.config.space_before_colon() { - result.push_str(" "); - } - result.push_str(":"); - if context.config.space_after_colon() { - result.push_str(" "); - } - let overhead = last_line_width(&result); - let max_width = shape.width.checked_sub(overhead)?; - let ty_str = self.ty - .rewrite(context, Shape::legacy(max_width, shape.indent))?; - result.push_str(&ty_str); - } - - Some(result) - } else { - self.ty.rewrite(context, shape) - } - } -} - -fn rewrite_explicit_self( - explicit_self: &ast::ExplicitSelf, - args: &[ast::Arg], - context: &RewriteContext, -) -> Option<String> { - match explicit_self.node { - ast::SelfKind::Region(lt, m) => { - let mut_str = format_mutability(m); - match lt { - Some(ref l) => { - let lifetime_str = l.rewrite( - context, - Shape::legacy(context.config.max_width(), Indent::empty()), - )?; - Some(format!("&{} {}self", lifetime_str, mut_str)) - } - None => Some(format!("&{}self", mut_str)), - } - } - ast::SelfKind::Explicit(ref ty, _) => { - assert!(!args.is_empty(), "&[ast::Arg] shouldn't be empty."); - - let mutability = explicit_self_mutability(&args[0]); - let type_str = ty.rewrite( - context, - Shape::legacy(context.config.max_width(), Indent::empty()), - )?; - - Some(format!( - "{}self: {}", - format_mutability(mutability), - type_str - )) - } - ast::SelfKind::Value(_) => { - assert!(!args.is_empty(), "&[ast::Arg] shouldn't be empty."); - - let mutability = explicit_self_mutability(&args[0]); - - Some(format!("{}self", format_mutability(mutability))) - } - } -} - -// Hacky solution caused by absence of `Mutability` in `SelfValue` and -// `SelfExplicit` variants of `ast::ExplicitSelf_`. -fn explicit_self_mutability(arg: &ast::Arg) -> ast::Mutability { - if let ast::PatKind::Ident(ast::BindingMode::ByValue(mutability), _, _) = arg.pat.node { - mutability - } else { - unreachable!() - } -} - -pub fn span_lo_for_arg(arg: &ast::Arg) -> BytePos { - if is_named_arg(arg) { - arg.pat.span.lo() - } else { - arg.ty.span.lo() - } -} - -pub fn span_hi_for_arg(context: &RewriteContext, arg: &ast::Arg) -> BytePos { - match arg.ty.node { - ast::TyKind::Infer if context.snippet(arg.ty.span) == "_" => arg.ty.span.hi(), - ast::TyKind::Infer if is_named_arg(arg) => arg.pat.span.hi(), - _ => arg.ty.span.hi(), - } -} - -pub fn is_named_arg(arg: &ast::Arg) -> bool { - if let ast::PatKind::Ident(_, ident, _) = arg.pat.node { - ident.node != symbol::keywords::Invalid.ident() - } else { - true - } -} - -// Return type is (result, force_new_line_for_brace) -fn rewrite_fn_base( - context: &RewriteContext, - indent: Indent, - ident: ast::Ident, - fn_sig: &FnSig, - span: Span, - newline_brace: bool, - has_body: bool, -) -> Option<(String, bool)> { - let mut force_new_line_for_brace = false; - - let where_clause = &fn_sig.generics.where_clause; - - let mut result = String::with_capacity(1024); - result.push_str(&fn_sig.to_str(context)); - - // fn foo - result.push_str("fn "); - result.push_str(&ident.to_string()); - - // Generics. - let overhead = if has_body && !newline_brace { - // 4 = `() {` - 4 - } else { - // 2 = `()` - 2 - }; - let used_width = last_line_used_width(&result, indent.width()); - let one_line_budget = context.budget(used_width + overhead); - let shape = Shape { - width: one_line_budget, - indent, - offset: used_width, - }; - let fd = fn_sig.decl; - let g_span = mk_sp(span.lo(), fd.output.span().lo()); - let generics_str = rewrite_generics(context, fn_sig.generics, shape, g_span)?; - result.push_str(&generics_str); - - let snuggle_angle_bracket = generics_str - .lines() - .last() - .map_or(false, |l| l.trim_left().len() == 1); - - // Note that the width and indent don't really matter, we'll re-layout the - // return type later anyway. - let ret_str = fd.output - .rewrite(context, Shape::indented(indent, context.config))?; - - let multi_line_ret_str = ret_str.contains('\n'); - let ret_str_len = if multi_line_ret_str { 0 } else { ret_str.len() }; - - // Args. - let (one_line_budget, multi_line_budget, mut arg_indent) = compute_budgets_for_args( - context, - &result, - indent, - ret_str_len, - newline_brace, - has_body, - multi_line_ret_str, - )?; - - debug!( - "rewrite_fn_base: one_line_budget: {}, multi_line_budget: {}, arg_indent: {:?}", - one_line_budget, multi_line_budget, arg_indent - ); - - // Check if vertical layout was forced. - if one_line_budget == 0 { - if snuggle_angle_bracket { - result.push('('); - } else { - result.push_str("("); - if context.config.indent_style() == IndentStyle::Visual { - result.push('\n'); - result.push_str(&arg_indent.to_string(context.config)); - } - } - } else { - result.push('('); - } - if context.config.spaces_within_parens_and_brackets() && !fd.inputs.is_empty() - && result.ends_with('(') - { - result.push(' ') - } - - // Skip `pub(crate)`. - let lo_after_visibility = get_bytepos_after_visibility(context, &fn_sig.visibility, span, ")"); - // A conservative estimation, to goal is to be over all parens in generics - let args_start = fn_sig - .generics - .params - .iter() - .last() - .map_or(lo_after_visibility, |param| param.span().hi()); - let args_end = if fd.inputs.is_empty() { - context - .codemap - .span_after(mk_sp(args_start, span.hi()), ")") - } else { - let last_span = mk_sp(fd.inputs[fd.inputs.len() - 1].span().hi(), span.hi()); - context.codemap.span_after(last_span, ")") - }; - let args_span = mk_sp( - context - .codemap - .span_after(mk_sp(args_start, span.hi()), "("), - args_end, - ); - let arg_str = rewrite_args( - context, - &fd.inputs, - fd.get_self().as_ref(), - one_line_budget, - multi_line_budget, - indent, - arg_indent, - args_span, - fd.variadic, - generics_str.contains('\n'), - )?; - - let put_args_in_block = match context.config.indent_style() { - IndentStyle::Block => arg_str.contains('\n') || arg_str.len() > one_line_budget, - _ => false, - } && !fd.inputs.is_empty(); - - let mut args_last_line_contains_comment = false; - if put_args_in_block { - arg_indent = indent.block_indent(context.config); - result.push('\n'); - result.push_str(&arg_indent.to_string(context.config)); - result.push_str(&arg_str); - result.push('\n'); - result.push_str(&indent.to_string(context.config)); - result.push(')'); - } else { - result.push_str(&arg_str); - let used_width = last_line_used_width(&result, indent.width()) + first_line_width(&ret_str); - // Put the closing brace on the next line if it overflows the max width. - // 1 = `)` - if fd.inputs.is_empty() && used_width + 1 > context.config.max_width() { - result.push('\n'); - } - if context.config.spaces_within_parens_and_brackets() && !fd.inputs.is_empty() { - result.push(' ') - } - // If the last line of args contains comment, we cannot put the closing paren - // on the same line. - if arg_str - .lines() - .last() - .map_or(false, |last_line| last_line.contains("//")) - { - args_last_line_contains_comment = true; - result.push('\n'); - result.push_str(&arg_indent.to_string(context.config)); - } - result.push(')'); - } - - // Return type. - if let ast::FunctionRetTy::Ty(..) = fd.output { - let ret_should_indent = match context.config.indent_style() { - // If our args are block layout then we surely must have space. - IndentStyle::Block if put_args_in_block || fd.inputs.is_empty() => false, - _ if args_last_line_contains_comment => false, - _ if result.contains('\n') || multi_line_ret_str => true, - _ => { - // If the return type would push over the max width, then put the return type on - // a new line. With the +1 for the signature length an additional space between - // the closing parenthesis of the argument and the arrow '->' is considered. - let mut sig_length = result.len() + indent.width() + ret_str_len + 1; - - // If there is no where clause, take into account the space after the return type - // and the brace. - if where_clause.predicates.is_empty() { - sig_length += 2; - } - - sig_length > context.config.max_width() - } - }; - let ret_indent = if ret_should_indent { - let indent = if arg_str.is_empty() { - // Aligning with non-existent args looks silly. - force_new_line_for_brace = true; - indent + 4 - } else { - // FIXME: we might want to check that using the arg indent - // doesn't blow our budget, and if it does, then fallback to - // the where clause indent. - arg_indent - }; - - result.push('\n'); - result.push_str(&indent.to_string(context.config)); - indent - } else { - result.push(' '); - Indent::new(indent.block_indent, last_line_width(&result)) - }; - - if multi_line_ret_str || ret_should_indent { - // Now that we know the proper indent and width, we need to - // re-layout the return type. - let ret_str = fd.output - .rewrite(context, Shape::indented(ret_indent, context.config))?; - result.push_str(&ret_str); - } else { - result.push_str(&ret_str); - } - - // Comment between return type and the end of the decl. - let snippet_lo = fd.output.span().hi(); - if where_clause.predicates.is_empty() { - let snippet_hi = span.hi(); - let snippet = context.snippet(mk_sp(snippet_lo, snippet_hi)); - // Try to preserve the layout of the original snippet. - let original_starts_with_newline = snippet - .find(|c| c != ' ') - .map_or(false, |i| starts_with_newline(&snippet[i..])); - let original_ends_with_newline = snippet - .rfind(|c| c != ' ') - .map_or(false, |i| snippet[i..].ends_with('\n')); - let snippet = snippet.trim(); - if !snippet.is_empty() { - result.push(if original_starts_with_newline { - '\n' - } else { - ' ' - }); - result.push_str(snippet); - if original_ends_with_newline { - force_new_line_for_brace = true; - } - } - } - } - - let pos_before_where = match fd.output { - ast::FunctionRetTy::Default(..) => args_span.hi(), - ast::FunctionRetTy::Ty(ref ty) => ty.span.hi(), - }; - - let is_args_multi_lined = arg_str.contains('\n'); - - let option = WhereClauseOption::new(!has_body, put_args_in_block && ret_str.is_empty()); - let where_clause_str = rewrite_where_clause( - context, - where_clause, - context.config.brace_style(), - Shape::indented(indent, context.config), - Density::Tall, - "{", - Some(span.hi()), - pos_before_where, - option, - is_args_multi_lined, - )?; - // If there are neither where clause nor return type, we may be missing comments between - // args and `{`. - if where_clause_str.is_empty() { - if let ast::FunctionRetTy::Default(ret_span) = fd.output { - match recover_missing_comment_in_span( - mk_sp(args_span.hi(), ret_span.hi()), - shape, - context, - last_line_width(&result), - ) { - Some(ref missing_comment) if !missing_comment.is_empty() => { - result.push_str(missing_comment); - force_new_line_for_brace = true; - } - _ => (), - } - } - } - - result.push_str(&where_clause_str); - - force_new_line_for_brace |= last_line_contains_single_line_comment(&result); - force_new_line_for_brace |= is_args_multi_lined && context.config.where_single_line(); - Some((result, force_new_line_for_brace)) -} - -#[derive(Copy, Clone)] -struct WhereClauseOption { - suppress_comma: bool, // Force no trailing comma - snuggle: bool, // Do not insert newline before `where` - compress_where: bool, // Try single line where clause instead of vertical layout -} - -impl WhereClauseOption { - pub fn new(suppress_comma: bool, snuggle: bool) -> WhereClauseOption { - WhereClauseOption { - suppress_comma, - snuggle, - compress_where: false, - } - } - - pub fn snuggled(current: &str) -> WhereClauseOption { - WhereClauseOption { - suppress_comma: false, - snuggle: trimmed_last_line_width(current) == 1, - compress_where: false, - } - } -} - -fn rewrite_args( - context: &RewriteContext, - args: &[ast::Arg], - explicit_self: Option<&ast::ExplicitSelf>, - one_line_budget: usize, - multi_line_budget: usize, - indent: Indent, - arg_indent: Indent, - span: Span, - variadic: bool, - generics_str_contains_newline: bool, -) -> Option<String> { - let mut arg_item_strs = args.iter() - .map(|arg| arg.rewrite(context, Shape::legacy(multi_line_budget, arg_indent))) - .collect::<Option<Vec<_>>>()?; - - // Account for sugary self. - // FIXME: the comment for the self argument is dropped. This is blocked - // on rust issue #27522. - let min_args = explicit_self - .and_then(|explicit_self| rewrite_explicit_self(explicit_self, args, context)) - .map_or(1, |self_str| { - arg_item_strs[0] = self_str; - 2 - }); - - // Comments between args. - let mut arg_items = Vec::new(); - if min_args == 2 { - arg_items.push(ListItem::from_str("")); - } - - // FIXME(#21): if there are no args, there might still be a comment, but - // without spans for the comment or parens, there is no chance of - // getting it right. You also don't get to put a comment on self, unless - // it is explicit. - if args.len() >= min_args || variadic { - let comment_span_start = if min_args == 2 { - let second_arg_start = if arg_has_pattern(&args[1]) { - args[1].pat.span.lo() - } else { - args[1].ty.span.lo() - }; - let reduced_span = mk_sp(span.lo(), second_arg_start); - - context.codemap.span_after_last(reduced_span, ",") - } else { - span.lo() - }; - - enum ArgumentKind<'a> { - Regular(&'a ast::Arg), - Variadic(BytePos), - } - - let variadic_arg = if variadic { - let variadic_span = mk_sp(args.last().unwrap().ty.span.hi(), span.hi()); - let variadic_start = context.codemap.span_after(variadic_span, "...") - BytePos(3); - Some(ArgumentKind::Variadic(variadic_start)) - } else { - None - }; - - let more_items = itemize_list( - context.codemap, - args[min_args - 1..] - .iter() - .map(ArgumentKind::Regular) - .chain(variadic_arg), - ")", - ",", - |arg| match *arg { - ArgumentKind::Regular(arg) => span_lo_for_arg(arg), - ArgumentKind::Variadic(start) => start, - }, - |arg| match *arg { - ArgumentKind::Regular(arg) => arg.ty.span.hi(), - ArgumentKind::Variadic(start) => start + BytePos(3), - }, - |arg| match *arg { - ArgumentKind::Regular(..) => None, - ArgumentKind::Variadic(..) => Some("...".to_owned()), - }, - comment_span_start, - span.hi(), - false, - ); - - arg_items.extend(more_items); - } - - let fits_in_one_line = !generics_str_contains_newline - && (arg_items.is_empty() - || arg_items.len() == 1 && arg_item_strs[0].len() <= one_line_budget); - - for (item, arg) in arg_items.iter_mut().zip(arg_item_strs) { - item.item = Some(arg); - } - - let last_line_ends_with_comment = arg_items - .iter() - .last() - .and_then(|item| item.post_comment.as_ref()) - .map_or(false, |s| s.trim().starts_with("//")); - - let (indent, trailing_comma) = match context.config.indent_style() { - IndentStyle::Block if fits_in_one_line => { - (indent.block_indent(context.config), SeparatorTactic::Never) - } - IndentStyle::Block => ( - indent.block_indent(context.config), - context.config.trailing_comma(), - ), - IndentStyle::Visual if last_line_ends_with_comment => { - (arg_indent, context.config.trailing_comma()) - } - IndentStyle::Visual => (arg_indent, SeparatorTactic::Never), - }; - - let tactic = definitive_tactic( - &arg_items, - context.config.fn_args_density().to_list_tactic(), - Separator::Comma, - one_line_budget, - ); - let budget = match tactic { - DefinitiveListTactic::Horizontal => one_line_budget, - _ => multi_line_budget, - }; - - debug!("rewrite_args: budget: {}, tactic: {:?}", budget, tactic); - - let fmt = ListFormatting { - tactic, - separator: ",", - trailing_separator: if variadic { - SeparatorTactic::Never - } else { - trailing_comma - }, - separator_place: SeparatorPlace::Back, - shape: Shape::legacy(budget, indent), - ends_with_newline: tactic.ends_with_newline(context.config.indent_style()), - preserve_newline: true, - config: context.config, - }; - - write_list(&arg_items, &fmt) -} - -fn arg_has_pattern(arg: &ast::Arg) -> bool { - if let ast::PatKind::Ident(_, ident, _) = arg.pat.node { - ident.node != symbol::keywords::Invalid.ident() - } else { - true - } -} - -fn compute_budgets_for_args( - context: &RewriteContext, - result: &str, - indent: Indent, - ret_str_len: usize, - newline_brace: bool, - has_braces: bool, - force_vertical_layout: bool, -) -> Option<((usize, usize, Indent))> { - debug!( - "compute_budgets_for_args {} {:?}, {}, {}", - result.len(), - indent, - ret_str_len, - newline_brace - ); - // Try keeping everything on the same line. - if !result.contains('\n') && !force_vertical_layout { - // 2 = `()`, 3 = `() `, space is before ret_string. - let overhead = if ret_str_len == 0 { 2 } else { 3 }; - let mut used_space = indent.width() + result.len() + ret_str_len + overhead; - if has_braces { - if !newline_brace { - // 2 = `{}` - used_space += 2; - } - } else { - // 1 = `;` - used_space += 1; - } - let one_line_budget = context.budget(used_space); - - if one_line_budget > 0 { - // 4 = "() {".len() - let (indent, multi_line_budget) = match context.config.indent_style() { - IndentStyle::Block => { - let indent = indent.block_indent(context.config); - (indent, context.budget(indent.width() + 1)) - } - IndentStyle::Visual => { - let indent = indent + result.len() + 1; - let multi_line_overhead = indent.width() + if newline_brace { 2 } else { 4 }; - (indent, context.budget(multi_line_overhead)) - } - }; - - return Some((one_line_budget, multi_line_budget, indent)); - } - } - - // Didn't work. we must force vertical layout and put args on a newline. - let new_indent = indent.block_indent(context.config); - let used_space = match context.config.indent_style() { - // 1 = `,` - IndentStyle::Block => new_indent.width() + 1, - // Account for `)` and possibly ` {`. - IndentStyle::Visual => new_indent.width() + if ret_str_len == 0 { 1 } else { 3 }, - }; - Some((0, context.budget(used_space), new_indent)) -} - -fn newline_for_brace(config: &Config, where_clause: &ast::WhereClause) -> bool { - let predicate_count = where_clause.predicates.len(); - - if config.where_single_line() && predicate_count == 1 { - return false; - } - let brace_style = config.brace_style(); - - brace_style == BraceStyle::AlwaysNextLine - || (brace_style == BraceStyle::SameLineWhere && predicate_count > 0) -} - -fn rewrite_generics( - context: &RewriteContext, - generics: &ast::Generics, - shape: Shape, - span: Span, -) -> Option<String> { - let g_shape = generics_shape_from_config(context.config, shape, 0)?; - let one_line_width = shape.width.checked_sub(2).unwrap_or(0); - rewrite_generics_inner(context, generics, g_shape, one_line_width, span) - .or_else(|| rewrite_generics_inner(context, generics, g_shape, 0, span)) -} - -fn rewrite_generics_inner( - context: &RewriteContext, - generics: &ast::Generics, - shape: Shape, - one_line_width: usize, - span: Span, -) -> Option<String> { - // FIXME: convert bounds to where clauses where they get too big or if - // there is a where clause at all. - - if generics.params.is_empty() { - return Some(String::new()); - } - - let items = itemize_list( - context.codemap, - generics.params.iter(), - ">", - ",", - |arg| arg.span().lo(), - |arg| arg.span().hi(), - |arg| arg.rewrite(context, shape), - context.codemap.span_after(span, "<"), - span.hi(), - false, - ); - format_generics_item_list(context, items, shape, one_line_width) -} - -pub fn generics_shape_from_config(config: &Config, shape: Shape, offset: usize) -> Option<Shape> { - match config.indent_style() { - IndentStyle::Visual => shape.visual_indent(1 + offset).sub_width(offset + 2), - IndentStyle::Block => { - // 1 = "," - shape - .block() - .block_indent(config.tab_spaces()) - .with_max_width(config) - .sub_width(1) - } - } -} - -pub fn format_generics_item_list<I>( - context: &RewriteContext, - items: I, - shape: Shape, - one_line_budget: usize, -) -> Option<String> -where - I: Iterator<Item = ListItem>, -{ - let item_vec = items.collect::<Vec<_>>(); - - let tactic = definitive_tactic( - &item_vec, - ListTactic::HorizontalVertical, - Separator::Comma, - one_line_budget, - ); - let fmt = ListFormatting { - tactic, - separator: ",", - trailing_separator: if context.config.indent_style() == IndentStyle::Visual { - SeparatorTactic::Never - } else { - context.config.trailing_comma() - }, - separator_place: SeparatorPlace::Back, - shape, - ends_with_newline: tactic.ends_with_newline(context.config.indent_style()), - preserve_newline: true, - config: context.config, - }; - - let list_str = write_list(&item_vec, &fmt)?; - - Some(wrap_generics_with_angle_brackets( - context, - &list_str, - shape.indent, - )) -} - -pub fn wrap_generics_with_angle_brackets( - context: &RewriteContext, - list_str: &str, - list_offset: Indent, -) -> String { - if context.config.indent_style() == IndentStyle::Block - && (list_str.contains('\n') || list_str.ends_with(',')) - { - format!( - "<\n{}{}\n{}>", - list_offset.to_string(context.config), - list_str, - list_offset - .block_unindent(context.config) - .to_string(context.config) - ) - } else if context.config.spaces_within_parens_and_brackets() { - format!("< {} >", list_str) - } else { - format!("<{}>", list_str) - } -} - -fn rewrite_trait_bounds( - context: &RewriteContext, - bounds: &[ast::TyParamBound], - shape: Shape, -) -> Option<String> { - if bounds.is_empty() { - return Some(String::new()); - } - let bound_str = bounds - .iter() - .map(|ty_bound| ty_bound.rewrite(context, shape)) - .collect::<Option<Vec<_>>>()?; - Some(format!(": {}", join_bounds(context, shape, &bound_str))) -} - -fn rewrite_where_clause_rfc_style( - context: &RewriteContext, - where_clause: &ast::WhereClause, - shape: Shape, - terminator: &str, - span_end: Option<BytePos>, - span_end_before_where: BytePos, - where_clause_option: WhereClauseOption, - is_args_multi_line: bool, -) -> Option<String> { - let block_shape = shape.block().with_max_width(context.config); - - let (span_before, span_after) = - missing_span_before_after_where(span_end_before_where, where_clause); - let (comment_before, comment_after) = - rewrite_comments_before_after_where(context, span_before, span_after, shape)?; - - let starting_newline = if where_clause_option.snuggle && comment_before.is_empty() { - " ".to_owned() - } else { - "\n".to_owned() + &block_shape.indent.to_string(context.config) - }; - - let clause_shape = block_shape.block_left(context.config.tab_spaces())?; - // 1 = `,` - let clause_shape = clause_shape.sub_width(1)?; - // each clause on one line, trailing comma (except if suppress_comma) - let span_start = where_clause.predicates[0].span().lo(); - // If we don't have the start of the next span, then use the end of the - // predicates, but that means we miss comments. - let len = where_clause.predicates.len(); - let end_of_preds = where_clause.predicates[len - 1].span().hi(); - let span_end = span_end.unwrap_or(end_of_preds); - let items = itemize_list( - context.codemap, - where_clause.predicates.iter(), - terminator, - ",", - |pred| pred.span().lo(), - |pred| pred.span().hi(), - |pred| pred.rewrite(context, clause_shape), - span_start, - span_end, - false, - ); - let where_single_line = context.config.where_single_line() && len == 1 && !is_args_multi_line; - let comma_tactic = if where_clause_option.suppress_comma || where_single_line { - SeparatorTactic::Never - } else { - context.config.trailing_comma() - }; - - // shape should be vertical only and only if we have `where_single_line` option enabled - // and the number of items of the where clause is equal to 1 - let shape_tactic = if where_single_line { - DefinitiveListTactic::Horizontal - } else { - DefinitiveListTactic::Vertical - }; - - let fmt = ListFormatting { - tactic: shape_tactic, - separator: ",", - trailing_separator: comma_tactic, - separator_place: SeparatorPlace::Back, - shape: clause_shape, - ends_with_newline: true, - preserve_newline: true, - config: context.config, - }; - let preds_str = write_list(&items.collect::<Vec<_>>(), &fmt)?; - - let comment_separator = |comment: &str, shape: Shape| { - if comment.is_empty() { - String::new() - } else { - format!("\n{}", shape.indent.to_string(context.config)) - } - }; - let newline_before_where = comment_separator(&comment_before, shape); - let newline_after_where = comment_separator(&comment_after, clause_shape); - - // 6 = `where ` - let clause_sep = if where_clause_option.compress_where && comment_before.is_empty() - && comment_after.is_empty() && !preds_str.contains('\n') - && 6 + preds_str.len() <= shape.width || where_single_line - { - String::from(" ") - } else { - format!("\n{}", clause_shape.indent.to_string(context.config)) - }; - Some(format!( - "{}{}{}where{}{}{}{}", - starting_newline, - comment_before, - newline_before_where, - newline_after_where, - comment_after, - clause_sep, - preds_str - )) -} - -fn rewrite_where_clause( - context: &RewriteContext, - where_clause: &ast::WhereClause, - brace_style: BraceStyle, - shape: Shape, - density: Density, - terminator: &str, - span_end: Option<BytePos>, - span_end_before_where: BytePos, - where_clause_option: WhereClauseOption, - is_args_multi_line: bool, -) -> Option<String> { - if where_clause.predicates.is_empty() { - return Some(String::new()); - } - - if context.config.indent_style() == IndentStyle::Block { - return rewrite_where_clause_rfc_style( - context, - where_clause, - shape, - terminator, - span_end, - span_end_before_where, - where_clause_option, - is_args_multi_line, - ); - } - - let extra_indent = Indent::new(context.config.tab_spaces(), 0); - - let offset = match context.config.indent_style() { - IndentStyle::Block => shape.indent + extra_indent.block_indent(context.config), - // 6 = "where ".len() - IndentStyle::Visual => shape.indent + extra_indent + 6, - }; - // FIXME: if indent_style != Visual, then the budgets below might - // be out by a char or two. - - let budget = context.config.max_width() - offset.width(); - let span_start = where_clause.predicates[0].span().lo(); - // If we don't have the start of the next span, then use the end of the - // predicates, but that means we miss comments. - let len = where_clause.predicates.len(); - let end_of_preds = where_clause.predicates[len - 1].span().hi(); - let span_end = span_end.unwrap_or(end_of_preds); - let items = itemize_list( - context.codemap, - where_clause.predicates.iter(), - terminator, - ",", - |pred| pred.span().lo(), - |pred| pred.span().hi(), - |pred| pred.rewrite(context, Shape::legacy(budget, offset)), - span_start, - span_end, - false, - ); - let item_vec = items.collect::<Vec<_>>(); - // FIXME: we don't need to collect here - let tactic = definitive_tactic(&item_vec, ListTactic::Vertical, Separator::Comma, budget); - - let mut comma_tactic = context.config.trailing_comma(); - // Kind of a hack because we don't usually have trailing commas in where clauses. - if comma_tactic == SeparatorTactic::Vertical || where_clause_option.suppress_comma { - comma_tactic = SeparatorTactic::Never; - } - - let fmt = ListFormatting { - tactic, - separator: ",", - trailing_separator: comma_tactic, - separator_place: SeparatorPlace::Back, - shape: Shape::legacy(budget, offset), - ends_with_newline: tactic.ends_with_newline(context.config.indent_style()), - preserve_newline: true, - config: context.config, - }; - let preds_str = write_list(&item_vec, &fmt)?; - - let end_length = if terminator == "{" { - // If the brace is on the next line we don't need to count it otherwise it needs two - // characters " {" - match brace_style { - BraceStyle::AlwaysNextLine | BraceStyle::SameLineWhere => 0, - BraceStyle::PreferSameLine => 2, - } - } else if terminator == "=" { - 2 - } else { - terminator.len() - }; - if density == Density::Tall || preds_str.contains('\n') - || shape.indent.width() + " where ".len() + preds_str.len() + end_length > shape.width - { - Some(format!( - "\n{}where {}", - (shape.indent + extra_indent).to_string(context.config), - preds_str - )) - } else { - Some(format!(" where {}", preds_str)) - } -} - -fn missing_span_before_after_where( - before_item_span_end: BytePos, - where_clause: &ast::WhereClause, -) -> (Span, Span) { - let missing_span_before = mk_sp(before_item_span_end, where_clause.span.lo()); - // 5 = `where` - let pos_after_where = where_clause.span.lo() + BytePos(5); - let missing_span_after = mk_sp(pos_after_where, where_clause.predicates[0].span().lo()); - (missing_span_before, missing_span_after) -} - -fn rewrite_comments_before_after_where( - context: &RewriteContext, - span_before_where: Span, - span_after_where: Span, - shape: Shape, -) -> Option<(String, String)> { - let before_comment = rewrite_missing_comment(span_before_where, shape, context)?; - let after_comment = rewrite_missing_comment( - span_after_where, - shape.block_indent(context.config.tab_spaces()), - context, - )?; - Some((before_comment, after_comment)) -} - -fn format_header(item_name: &str, ident: ast::Ident, vis: &ast::Visibility) -> String { - format!("{}{}{}", format_visibility(vis), item_name, ident) -} - -#[derive(PartialEq, Eq, Clone, Copy)] -enum BracePos { - None, - Auto, - ForceSameLine, -} - -fn format_generics( - context: &RewriteContext, - generics: &ast::Generics, - brace_style: BraceStyle, - brace_pos: BracePos, - offset: Indent, - span: Span, - used_width: usize, -) -> Option<String> { - let shape = Shape::legacy(context.budget(used_width + offset.width()), offset); - let mut result = rewrite_generics(context, generics, shape, span)?; - - let same_line_brace = if !generics.where_clause.predicates.is_empty() || result.contains('\n') { - let budget = context.budget(last_line_used_width(&result, offset.width())); - let mut option = WhereClauseOption::snuggled(&result); - if brace_pos == BracePos::None { - option.suppress_comma = true; - } - // If the generics are not parameterized then generics.span.hi() == 0, - // so we use span.lo(), which is the position after `struct Foo`. - let span_end_before_where = if generics.is_parameterized() { - generics.span.hi() - } else { - span.lo() - }; - let where_clause_str = rewrite_where_clause( - context, - &generics.where_clause, - brace_style, - Shape::legacy(budget, offset.block_only()), - Density::Tall, - "{", - Some(span.hi()), - span_end_before_where, - option, - false, - )?; - result.push_str(&where_clause_str); - brace_pos == BracePos::ForceSameLine || brace_style == BraceStyle::PreferSameLine - || (generics.where_clause.predicates.is_empty() - && trimmed_last_line_width(&result) == 1) - } else { - brace_pos == BracePos::ForceSameLine || trimmed_last_line_width(&result) == 1 - || brace_style != BraceStyle::AlwaysNextLine - }; - if brace_pos == BracePos::None { - return Some(result); - } - let total_used_width = last_line_used_width(&result, used_width); - let remaining_budget = context.budget(total_used_width); - // If the same line brace if forced, it indicates that we are rewriting an item with empty body, - // and hence we take the closer into account as well for one line budget. - // We assume that the closer has the same length as the opener. - let overhead = if brace_pos == BracePos::ForceSameLine { - // 3 = ` {}` - 3 - } else { - // 2 = ` {` - 2 - }; - let forbid_same_line_brace = overhead > remaining_budget; - if !forbid_same_line_brace && same_line_brace { - result.push(' '); - } else { - result.push('\n'); - result.push_str(&offset.block_only().to_string(context.config)); - } - result.push('{'); - - Some(result) -} - -impl Rewrite for ast::ForeignItem { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - let attrs_str = self.attrs.rewrite(context, shape)?; - // Drop semicolon or it will be interpreted as comment. - // FIXME: this may be a faulty span from libsyntax. - let span = mk_sp(self.span.lo(), self.span.hi() - BytePos(1)); - - let item_str = match self.node { - ast::ForeignItemKind::Fn(ref fn_decl, ref generics) => { - rewrite_fn_base( - context, - shape.indent, - self.ident, - &FnSig::new(fn_decl, generics, self.vis.clone()), - span, - false, - false, - ).map(|(s, _)| format!("{};", s)) - } - ast::ForeignItemKind::Static(ref ty, is_mutable) => { - // FIXME(#21): we're dropping potential comments in between the - // function keywords here. - let vis = format_visibility(&self.vis); - let mut_str = if is_mutable { "mut " } else { "" }; - let prefix = format!("{}static {}{}:", vis, mut_str, self.ident); - // 1 = ; - let shape = shape.sub_width(1)?; - ty.rewrite(context, shape).map(|ty_str| { - // 1 = space between prefix and type. - let sep = if prefix.len() + ty_str.len() + 1 <= shape.width { - String::from(" ") - } else { - let nested_indent = shape.indent.block_indent(context.config); - format!("\n{}", nested_indent.to_string(context.config)) - }; - format!("{}{}{};", prefix, sep, ty_str) - }) - } - ast::ForeignItemKind::Ty => { - let vis = format_visibility(&self.vis); - Some(format!("{}type {};", vis, self.ident)) - } - }?; - - let missing_span = if self.attrs.is_empty() { - mk_sp(self.span.lo(), self.span.lo()) - } else { - mk_sp(self.attrs[self.attrs.len() - 1].span.hi(), self.span.lo()) - }; - combine_strs_with_missing_comments( - context, - &attrs_str, - &item_str, - missing_span, - shape, - false, - ) - } -} - -impl Rewrite for ast::GenericParam { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - match *self { - ast::GenericParam::Lifetime(ref lifetime_def) => lifetime_def.rewrite(context, shape), - ast::GenericParam::Type(ref ty) => ty.rewrite(context, shape), - } - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 6aca11ab3ee..00000000000 --- a/src/lib.rs +++ /dev/null @@ -1,828 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -#![feature(decl_macro)] -#![feature(match_default_bindings)] -#![feature(type_ascription)] - -#[macro_use] -extern crate derive_new; -extern crate diff; -#[macro_use] -extern crate log; -extern crate regex; -extern crate rustc_errors as errors; -extern crate serde; -#[macro_use] -extern crate serde_derive; -extern crate serde_json; -extern crate syntax; -extern crate term; -extern crate unicode_segmentation; - -use std::collections::HashMap; -use std::fmt; -use std::io::{self, stdout, Write}; -use std::iter::repeat; -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::parse::{self, ParseSess}; - -use checkstyle::{output_footer, output_header}; -use comment::{CharClasses, FullCodeCharKind}; -pub use config::Config; -use filemap::FileMap; -use issues::{BadIssueSeeker, Issue}; -use shape::Indent; -use utils::use_colored_tty; -use visitor::{FmtVisitor, SnippetProvider}; - -pub use self::summary::Summary; - -#[macro_use] -mod utils; -mod chains; -mod checkstyle; -mod closures; -pub mod codemap; -mod comment; -pub mod config; -mod expr; -pub mod file_lines; -pub mod filemap; -mod imports; -mod issues; -mod items; -mod lists; -mod macros; -mod missed_spans; -pub mod modules; -mod patterns; -mod rewrite; -pub mod rustfmt_diff; -mod shape; -mod spanned; -mod string; -mod summary; -mod types; -mod vertical; -pub mod visitor; - -#[derive(Clone, Copy)] -pub enum ErrorKind { - // Line has exceeded character limit (found, maximum) - LineOverflow(usize, usize), - // Line ends in whitespace - TrailingWhitespace, - // TO-DO or FIX-ME item without an issue number - BadIssue(Issue), -} - -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), - } - } -} - -// Formatting errors that are identified *after* rustfmt has run. -pub struct FormattingError { - line: usize, - kind: ErrorKind, - is_comment: bool, - is_string: bool, - line_buffer: String, -} - -impl FormattingError { - fn msg_prefix(&self) -> &str { - match self.kind { - ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => "error:", - ErrorKind::BadIssue(_) => "WARNING:", - } - } - - 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 { - "" - } - } - - // (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) - } - _ => unreachable!(), - } - } -} - -pub struct FormatReport { - // Maps stringified file paths to their associated formatting errors. - file_error_map: HashMap<FileName, Vec<FormattingError>>, -} - -impl FormatReport { - fn new() -> FormatReport { - FormatReport { - file_error_map: HashMap::new(), - } - } - - pub fn warning_count(&self) -> usize { - self.file_error_map - .iter() - .map(|(_, errors)| errors.len()) - .sum() - } - - 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{}\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(()) - } -} - -// Formatting which depends on the AST. -fn format_ast<F>( - krate: &ast::Crate, - parse_session: &mut ParseSess, - main_file: &FileName, - config: &Config, - mut after_file: F, -) -> Result<(FileMap, bool), io::Error> -where - F: FnMut(&FileName, &mut String, &[(usize, usize)]) -> Result<bool, io::Error>, -{ - let mut result = FileMap::new(); - // 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; - for (path, module) in modules::list_files(krate, parse_session.codemap())? { - if skip_children && path != *main_file { - continue; - } - if config.verbose() { - 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.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.skip_empty_lines(filemap.end_pos); - visitor.format_separate_mod(module, &*filemap); - }; - - 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((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 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 line_len = 0; - let mut cur_line = 1; - let mut newline_count = 0; - let mut errors = vec![]; - let mut issue_seeker = BadIssueSeeker::new(config.report_todo(), config.report_fixme()); - 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 (kind, (b, c)) in CharClasses::new(text.chars().enumerate()) { - if c == '\r' { - continue; - } - - if format_line { - // Add warnings for bad todos/ fixmes - if let Some(issue) = issue_seeker.inspect(c) { - errors.push(FormattingError { - 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(..) = 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 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: 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; - line_buffer.clear(); - is_string = false; - } else { - newline_count = 0; - line_len += if c == '\t' { config.tab_spaces() } else { 1 }; - if c.is_whitespace() { - if last_wspace.is_none() { - last_wspace = Some(b); - } - } else { - last_wspace = None; - } - 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; - text.truncate(line); - } - - 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.clone(), errors); -} - -fn parse_input( - input: Input, - parse_session: &ParseSess, -) -> Result<ast::Crate, Option<DiagnosticBuilder>> { - let result = match input { - Input::File(file) => { - let mut parser = parse::new_parser_from_file(parse_session, &file); - parser.cfg_mods = 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; - parser.parse_crate_mod() - } - }; - - match result { - Ok(c) => { - if parse_session.span_diagnostic.has_errors() { - // Bail out if the parser recovered from an error. - Err(None) - } else { - Ok(c) - } - } - Err(e) => Err(Some(e)), - } -} - -/// 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()) { - let offset = if config.hard_tabs() { - 1 - } else { - config.tab_spaces() - }; - &line[offset..] - } 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::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 = if config.hide_parse_errors() { - let silent_emitter = Box::new(EmitterWriter::new( - Box::new(Vec::new()), - Some(codemap.clone()), - false, - false, - )); - Handler::with_emitter(true, false, silent_emitter) - } else { - let supports_color = term::stderr().map_or(false, |term| term.supports_color()); - let color_cfg = if supports_color { - ColorConfig::Auto - } else { - ColorConfig::Never - }; - Handler::with_tty_emitter(color_cfg, 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) => FileName::Real(file.clone()), - Input::Text(..) => FileName::Custom("stdin".to_owned()), - }; - - let krate = match parse_input(input, &parse_session) { - Ok(krate) => krate, - Err(diagnostic) => { - if let Some(mut diagnostic) = diagnostic { - diagnostic.emit(); - } - 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()), - Some(codemap.clone()), - false, - false, - )); - parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter); - - let mut report = FormatReport::new(); - - let format_result = format_ast( - &krate, - &mut parse_session, - &main_file, - config, - |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, 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 has_diff { - summary.add_diff(); - } - - Ok((summary, file_map, report)) - } - Err(e) => Err((e, summary)), - } -} - -#[derive(Debug)] -pub enum Input { - File(PathBuf), - Text(String), -} - -pub fn run(input: Input, config: &Config) -> Summary { - 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() - && t.supports_attr(term::Attr::Bold) => - { - match report.print_warnings_fancy(term::stderr().unwrap()) { - Ok(..) => (), - Err(..) => panic!("Unable to write to stderr: {}", report), - } - } - _ => msg!("{}", report), - } - } - - summary - } - Err((msg, mut summary)) => { - msg!("Error writing files: {}", msg); - summary.add_operational_error(); - summary - } - } -} - -#[cfg(test)] -mod test { - use super::{format_code_block, format_snippet, Config}; - - #[test] - 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()); - } - - 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 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 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)); - } -} diff --git a/src/lists.rs b/src/lists.rs deleted file mode 100644 index aa1e0b430ef..00000000000 --- a/src/lists.rs +++ /dev/null @@ -1,857 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::cmp; -use std::iter::Peekable; - -use syntax::codemap::{BytePos, CodeMap}; - -use comment::{find_comment_end, rewrite_comment, FindUncommented}; -use config::{Config, IndentStyle}; -use rewrite::RewriteContext; -use shape::{Indent, Shape}; -use utils::{count_newlines, first_line_width, last_line_width, mk_sp, starts_with_newline}; - -/// Formatting tactic for lists. This will be cast down to a -/// `DefinitiveListTactic` depending on the number and length of the items and -/// their comments. -#[derive(Eq, PartialEq, Debug, Copy, Clone)] -pub enum ListTactic { - // One item per row. - Vertical, - // All items on one row. - Horizontal, - // Try Horizontal layout, if that fails then vertical. - HorizontalVertical, - // HorizontalVertical with a soft limit of n characters. - LimitedHorizontalVertical(usize), - // Pack as many items as possible per row over (possibly) many rows. - Mixed, -} - -impl_enum_serialize_and_deserialize!(ListTactic, Vertical, Horizontal, HorizontalVertical, Mixed); - -#[derive(Eq, PartialEq, Debug, Copy, Clone)] -pub enum SeparatorTactic { - Always, - Never, - Vertical, -} - -impl_enum_serialize_and_deserialize!(SeparatorTactic, Always, Never, Vertical); - -impl SeparatorTactic { - pub fn from_bool(b: bool) -> SeparatorTactic { - if b { - SeparatorTactic::Always - } else { - SeparatorTactic::Never - } - } -} - -pub struct ListFormatting<'a> { - pub tactic: DefinitiveListTactic, - pub separator: &'a str, - pub trailing_separator: SeparatorTactic, - pub separator_place: SeparatorPlace, - pub shape: Shape, - // Non-expressions, e.g. items, will have a new line at the end of the list. - // Important for comment styles. - pub ends_with_newline: bool, - // Remove newlines between list elements for expressions. - pub preserve_newline: bool, - pub config: &'a Config, -} - -impl<'a> ListFormatting<'a> { - pub fn needs_trailing_separator(&self) -> bool { - match self.trailing_separator { - // We always put separator in front. - SeparatorTactic::Always => true, - SeparatorTactic::Vertical => self.tactic == DefinitiveListTactic::Vertical, - SeparatorTactic::Never => { - self.tactic == DefinitiveListTactic::Vertical && self.separator_place.is_front() - } - } - } -} - -impl AsRef<ListItem> for ListItem { - fn as_ref(&self) -> &ListItem { - self - } -} - -#[derive(PartialEq, Eq)] -pub enum ListItemCommentStyle { - // Try to keep the comment on the same line with the item. - SameLine, - // Put the comment on the previous or the next line of the item. - DifferentLine, - // No comment available. - None, -} - -pub struct ListItem { - // None for comments mean that they are not present. - pub pre_comment: Option<String>, - pub pre_comment_style: ListItemCommentStyle, - // Item should include attributes and doc comments. None indicates a failed - // rewrite. - pub item: Option<String>, - pub post_comment: Option<String>, - // Whether there is extra whitespace before this item. - pub new_lines: bool, -} - -impl ListItem { - pub fn inner_as_ref(&self) -> &str { - self.item.as_ref().map_or("", |s| s) - } - - pub fn is_different_group(&self) -> bool { - self.inner_as_ref().contains('\n') || self.pre_comment.is_some() - || self.post_comment - .as_ref() - .map_or(false, |s| s.contains('\n')) - } - - pub fn is_multiline(&self) -> bool { - self.inner_as_ref().contains('\n') - || self.pre_comment - .as_ref() - .map_or(false, |s| s.contains('\n')) - || self.post_comment - .as_ref() - .map_or(false, |s| s.contains('\n')) - } - - pub fn has_comment(&self) -> bool { - self.pre_comment - .as_ref() - .map_or(false, |comment| comment.trim_left().starts_with("//")) - || self.post_comment - .as_ref() - .map_or(false, |comment| comment.trim_left().starts_with("//")) - } - - pub fn from_str<S: Into<String>>(s: S) -> ListItem { - ListItem { - pre_comment: None, - pre_comment_style: ListItemCommentStyle::None, - item: Some(s.into()), - post_comment: None, - new_lines: false, - } - } -} - -/// The definitive formatting tactic for lists. -#[derive(Eq, PartialEq, Debug, Copy, Clone)] -pub enum DefinitiveListTactic { - Vertical, - Horizontal, - Mixed, - /// Special case tactic for `format!()`, `write!()` style macros. - SpecialMacro(usize), -} - -impl DefinitiveListTactic { - pub fn ends_with_newline(&self, indent_style: IndentStyle) -> bool { - match indent_style { - IndentStyle::Block => *self != DefinitiveListTactic::Horizontal, - IndentStyle::Visual => false, - } - } -} - -/// The type of separator for lists. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum Separator { - Comma, - VerticalBar, -} - -impl Separator { - pub fn len(&self) -> usize { - match *self { - // 2 = `, ` - Separator::Comma => 2, - // 3 = ` | ` - Separator::VerticalBar => 3, - } - } -} - -/// Where to put separator. -#[derive(Eq, PartialEq, Debug, Copy, Clone)] -pub enum SeparatorPlace { - Front, - Back, -} - -impl_enum_serialize_and_deserialize!(SeparatorPlace, Front, Back); - -impl SeparatorPlace { - pub fn is_front(&self) -> bool { - *self == SeparatorPlace::Front - } - - pub fn is_back(&self) -> bool { - *self == SeparatorPlace::Back - } - - pub fn from_tactic( - default: SeparatorPlace, - tactic: DefinitiveListTactic, - sep: &str, - ) -> SeparatorPlace { - match tactic { - DefinitiveListTactic::Vertical => default, - _ => if sep == "," { - SeparatorPlace::Back - } else { - default - }, - } - } -} - -pub fn definitive_tactic<I, T>( - items: I, - tactic: ListTactic, - sep: Separator, - width: usize, -) -> DefinitiveListTactic -where - I: IntoIterator<Item = T> + Clone, - T: AsRef<ListItem>, -{ - let pre_line_comments = items - .clone() - .into_iter() - .any(|item| item.as_ref().has_comment()); - - let limit = match tactic { - _ if pre_line_comments => return DefinitiveListTactic::Vertical, - ListTactic::Mixed => return DefinitiveListTactic::Mixed, - ListTactic::Horizontal => return DefinitiveListTactic::Horizontal, - ListTactic::Vertical => return DefinitiveListTactic::Vertical, - ListTactic::LimitedHorizontalVertical(limit) => ::std::cmp::min(width, limit), - ListTactic::HorizontalVertical => width, - }; - - let (sep_count, total_width) = calculate_width(items.clone()); - let total_sep_len = sep.len() * sep_count.checked_sub(1).unwrap_or(0); - let real_total = total_width + total_sep_len; - - if real_total <= limit && !pre_line_comments - && !items.into_iter().any(|item| item.as_ref().is_multiline()) - { - DefinitiveListTactic::Horizontal - } else { - DefinitiveListTactic::Vertical - } -} - -// Format a list of commented items into a string. -// TODO: add unit tests -pub fn write_list<I, T>(items: I, formatting: &ListFormatting) -> Option<String> -where - I: IntoIterator<Item = T> + Clone, - T: AsRef<ListItem>, -{ - let tactic = formatting.tactic; - let sep_len = formatting.separator.len(); - - // Now that we know how we will layout, we can decide for sure if there - // will be a trailing separator. - let mut trailing_separator = formatting.needs_trailing_separator(); - let mut result = String::with_capacity(128); - let cloned_items = items.clone(); - let mut iter = items.into_iter().enumerate().peekable(); - let mut item_max_width: Option<usize> = None; - let sep_place = - SeparatorPlace::from_tactic(formatting.separator_place, tactic, formatting.separator); - - let mut line_len = 0; - let indent_str = &formatting.shape.indent.to_string(formatting.config); - while let Some((i, item)) = iter.next() { - let item = item.as_ref(); - let inner_item = item.item.as_ref()?; - let first = i == 0; - let last = iter.peek().is_none(); - let mut separate = match sep_place { - SeparatorPlace::Front => !first, - SeparatorPlace::Back => !last || trailing_separator, - }; - let item_sep_len = if separate { sep_len } else { 0 }; - - // Item string may be multi-line. Its length (used for block comment alignment) - // should be only the length of the last line. - let item_last_line = if item.is_multiline() { - inner_item.lines().last().unwrap_or("") - } else { - inner_item.as_ref() - }; - let mut item_last_line_width = item_last_line.len() + item_sep_len; - if item_last_line.starts_with(&**indent_str) { - item_last_line_width -= indent_str.len(); - } - - match tactic { - DefinitiveListTactic::Horizontal if !first => { - result.push(' '); - } - DefinitiveListTactic::SpecialMacro(num_args_before) => { - if i == 0 { - // Nothing - } else if i < num_args_before { - result.push(' '); - } else if i <= num_args_before + 1 { - result.push('\n'); - result.push_str(indent_str); - } else { - result.push(' '); - } - } - DefinitiveListTactic::Vertical if !first => { - result.push('\n'); - result.push_str(indent_str); - } - DefinitiveListTactic::Mixed => { - let total_width = total_item_width(item) + item_sep_len; - - // 1 is space between separator and item. - if line_len > 0 && line_len + 1 + total_width > formatting.shape.width { - result.push('\n'); - result.push_str(indent_str); - line_len = 0; - if formatting.ends_with_newline { - if last { - separate = true; - } else { - trailing_separator = true; - } - } - } - - if line_len > 0 { - result.push(' '); - line_len += 1; - } - - line_len += total_width; - } - _ => {} - } - - // Pre-comments - if let Some(ref comment) = item.pre_comment { - // Block style in non-vertical mode. - let block_mode = tactic != DefinitiveListTactic::Vertical; - // Width restriction is only relevant in vertical mode. - let comment = - rewrite_comment(comment, block_mode, formatting.shape, formatting.config)?; - result.push_str(&comment); - - if tactic == DefinitiveListTactic::Vertical { - // We cannot keep pre-comments on the same line if the comment if normalized. - let keep_comment = if formatting.config.normalize_comments() - || item.pre_comment_style == ListItemCommentStyle::DifferentLine - { - false - } else { - // We will try to keep the comment on the same line with the item here. - // 1 = ` ` - let total_width = total_item_width(item) + item_sep_len + 1; - total_width <= formatting.shape.width - }; - if keep_comment { - result.push(' '); - } else { - result.push('\n'); - result.push_str(indent_str); - } - } else { - result.push(' '); - } - item_max_width = None; - } - - if separate && sep_place.is_front() && !first { - result.push_str(formatting.separator.trim()); - result.push(' '); - } - result.push_str(&inner_item[..]); - - // Post-comments - if tactic != DefinitiveListTactic::Vertical && item.post_comment.is_some() { - let comment = item.post_comment.as_ref().unwrap(); - let formatted_comment = rewrite_comment( - comment, - true, - Shape::legacy(formatting.shape.width, Indent::empty()), - formatting.config, - )?; - - result.push(' '); - result.push_str(&formatted_comment); - } - - if separate && sep_place.is_back() { - result.push_str(formatting.separator); - } - - if tactic == DefinitiveListTactic::Vertical && item.post_comment.is_some() { - let comment = item.post_comment.as_ref().unwrap(); - let overhead = last_line_width(&result) + first_line_width(comment.trim()); - - let rewrite_post_comment = |item_max_width: &mut Option<usize>| { - if item_max_width.is_none() && !last && !inner_item.contains('\n') { - *item_max_width = Some(max_width_of_item_with_post_comment( - &cloned_items, - i, - overhead, - formatting.config.max_width(), - )); - } - let overhead = if starts_with_newline(comment) { - 0 - } else if let Some(max_width) = *item_max_width { - max_width + 2 - } else { - // 1 = space between item and comment. - item_last_line_width + 1 - }; - let width = formatting.shape.width.checked_sub(overhead).unwrap_or(1); - let offset = formatting.shape.indent + overhead; - let comment_shape = Shape::legacy(width, offset); - - // Use block-style only for the last item or multiline comments. - let block_style = !formatting.ends_with_newline && last - || comment.trim().contains('\n') - || comment.trim().len() > width; - - rewrite_comment( - comment.trim_left(), - block_style, - comment_shape, - formatting.config, - ) - }; - - let mut formatted_comment = rewrite_post_comment(&mut item_max_width)?; - - if !starts_with_newline(comment) { - let mut comment_alignment = - post_comment_alignment(item_max_width, inner_item.len()); - if first_line_width(&formatted_comment) + last_line_width(&result) - + comment_alignment + 1 > formatting.config.max_width() - { - item_max_width = None; - formatted_comment = rewrite_post_comment(&mut item_max_width)?; - comment_alignment = post_comment_alignment(item_max_width, inner_item.len()); - } - for _ in 0..(comment_alignment + 1) { - result.push(' '); - } - // An additional space for the missing trailing separator. - if last && item_max_width.is_some() && !separate && !formatting.separator.is_empty() - { - result.push(' '); - } - } else { - result.push('\n'); - result.push_str(indent_str); - } - if formatted_comment.contains('\n') { - item_max_width = None; - } - result.push_str(&formatted_comment); - } else { - item_max_width = None; - } - - if formatting.preserve_newline && !last && tactic == DefinitiveListTactic::Vertical - && item.new_lines - { - item_max_width = None; - result.push('\n'); - } - } - - Some(result) -} - -fn max_width_of_item_with_post_comment<I, T>( - items: &I, - i: usize, - overhead: usize, - max_budget: usize, -) -> usize -where - I: IntoIterator<Item = T> + Clone, - T: AsRef<ListItem>, -{ - let mut max_width = 0; - let mut first = true; - for item in items.clone().into_iter().skip(i) { - let item = item.as_ref(); - let inner_item_width = item.inner_as_ref().len(); - if !first - && (item.is_different_group() || !item.post_comment.is_some() - || inner_item_width + overhead > max_budget) - { - return max_width; - } - if max_width < inner_item_width { - max_width = inner_item_width; - } - if item.new_lines { - return max_width; - } - first = false; - } - max_width -} - -fn post_comment_alignment(item_max_width: Option<usize>, inner_item_len: usize) -> usize { - item_max_width - .and_then(|max_line_width| max_line_width.checked_sub(inner_item_len)) - .unwrap_or(0) -} - -pub struct ListItems<'a, I, F1, F2, F3> -where - I: Iterator, -{ - codemap: &'a CodeMap, - inner: Peekable<I>, - get_lo: F1, - get_hi: F2, - get_item_string: F3, - prev_span_end: BytePos, - next_span_start: BytePos, - terminator: &'a str, - separator: &'a str, - leave_last: bool, -} - -impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3> -where - I: Iterator<Item = T>, - F1: Fn(&T) -> BytePos, - F2: Fn(&T) -> BytePos, - F3: Fn(&T) -> Option<String>, -{ - type Item = ListItem; - - fn next(&mut self) -> Option<Self::Item> { - let white_space: &[_] = &[' ', '\t']; - - self.inner.next().map(|item| { - let mut new_lines = false; - // Pre-comment - let pre_snippet = self.codemap - .span_to_snippet(mk_sp(self.prev_span_end, (self.get_lo)(&item))) - .unwrap(); - let trimmed_pre_snippet = pre_snippet.trim(); - let has_single_line_comment = trimmed_pre_snippet.starts_with("//"); - let has_block_comment = trimmed_pre_snippet.starts_with("/*"); - let (pre_comment, pre_comment_style) = if has_single_line_comment { - ( - Some(trimmed_pre_snippet.to_owned()), - ListItemCommentStyle::DifferentLine, - ) - } else if has_block_comment { - let comment_end = pre_snippet.chars().rev().position(|c| c == '/').unwrap(); - if pre_snippet - .chars() - .rev() - .take(comment_end + 1) - .any(|c| c == '\n') - { - ( - Some(trimmed_pre_snippet.to_owned()), - ListItemCommentStyle::DifferentLine, - ) - } else { - ( - Some(trimmed_pre_snippet.to_owned()), - ListItemCommentStyle::SameLine, - ) - } - } else { - (None, ListItemCommentStyle::None) - }; - - // Post-comment - let next_start = match self.inner.peek() { - Some(next_item) => (self.get_lo)(next_item), - None => self.next_span_start, - }; - let post_snippet = self.codemap - .span_to_snippet(mk_sp((self.get_hi)(&item), next_start)) - .unwrap(); - - let comment_end = match self.inner.peek() { - Some(..) => { - let mut block_open_index = post_snippet.find("/*"); - // check if it really is a block comment (and not `//*` or a nested comment) - if let Some(i) = block_open_index { - match post_snippet.find('/') { - Some(j) if j < i => block_open_index = None, - _ if i > 0 && &post_snippet[i - 1..i] == "/" => block_open_index = None, - _ => (), - } - } - let newline_index = post_snippet.find('\n'); - if let Some(separator_index) = post_snippet.find_uncommented(self.separator) { - match (block_open_index, newline_index) { - // Separator before comment, with the next item on same line. - // Comment belongs to next item. - (Some(i), None) if i > separator_index => separator_index + 1, - // Block-style post-comment before the separator. - (Some(i), None) => cmp::max( - find_comment_end(&post_snippet[i..]).unwrap() + i, - separator_index + 1, - ), - // Block-style post-comment. Either before or after the separator. - (Some(i), Some(j)) if i < j => cmp::max( - find_comment_end(&post_snippet[i..]).unwrap() + i, - separator_index + 1, - ), - // Potential *single* line comment. - (_, Some(j)) if j > separator_index => j + 1, - _ => post_snippet.len(), - } - } else if let Some(newline_index) = newline_index { - // Match arms may not have trailing comma. In any case, for match arms, - // we will assume that the post comment belongs to the next arm if they - // do not end with trailing comma. - newline_index + 1 - } else { - 0 - } - } - None => post_snippet - .find_uncommented(self.terminator) - .unwrap_or_else(|| post_snippet.len()), - }; - - if !post_snippet.is_empty() && comment_end > 0 { - // Account for extra whitespace between items. This is fiddly - // because of the way we divide pre- and post- comments. - - // Everything from the separator to the next item. - let test_snippet = &post_snippet[comment_end - 1..]; - let first_newline = test_snippet - .find('\n') - .unwrap_or_else(|| test_snippet.len()); - // From the end of the first line of comments. - let test_snippet = &test_snippet[first_newline..]; - let first = test_snippet - .find(|c: char| !c.is_whitespace()) - .unwrap_or_else(|| test_snippet.len()); - // From the end of the first line of comments to the next non-whitespace char. - let test_snippet = &test_snippet[..first]; - - if count_newlines(test_snippet) > 1 { - // There were multiple line breaks which got trimmed to nothing. - new_lines = true; - } - } - - // Cleanup post-comment: strip separators and whitespace. - self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32); - let post_snippet = post_snippet[..comment_end].trim(); - - let post_snippet_trimmed = if post_snippet.starts_with(|c| c == ',' || c == ':') { - post_snippet[1..].trim_matches(white_space) - } else if post_snippet.ends_with(',') { - post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space) - } else { - post_snippet - }; - - let post_comment = if !post_snippet_trimmed.is_empty() { - Some(post_snippet_trimmed.to_owned()) - } else { - None - }; - - ListItem { - pre_comment, - pre_comment_style, - item: if self.inner.peek().is_none() && self.leave_last { - None - } else { - (self.get_item_string)(&item) - }, - post_comment, - new_lines, - } - }) - } -} - -// Creates an iterator over a list's items with associated comments. -pub fn itemize_list<'a, T, I, F1, F2, F3>( - codemap: &'a CodeMap, - inner: I, - terminator: &'a str, - separator: &'a str, - get_lo: F1, - get_hi: F2, - get_item_string: F3, - prev_span_end: BytePos, - next_span_start: BytePos, - leave_last: bool, -) -> ListItems<'a, I, F1, F2, F3> -where - I: Iterator<Item = T>, - F1: Fn(&T) -> BytePos, - F2: Fn(&T) -> BytePos, - F3: Fn(&T) -> Option<String>, -{ - ListItems { - codemap, - inner: inner.peekable(), - get_lo, - get_hi, - get_item_string, - prev_span_end, - next_span_start, - terminator, - separator, - leave_last, - } -} - -/// Returns the count and total width of the list items. -fn calculate_width<I, T>(items: I) -> (usize, usize) -where - I: IntoIterator<Item = T>, - T: AsRef<ListItem>, -{ - items - .into_iter() - .map(|item| total_item_width(item.as_ref())) - .fold((0, 0), |acc, l| (acc.0 + 1, acc.1 + l)) -} - -pub fn total_item_width(item: &ListItem) -> usize { - comment_len(item.pre_comment.as_ref().map(|x| &(*x)[..])) - + comment_len(item.post_comment.as_ref().map(|x| &(*x)[..])) - + item.item.as_ref().map_or(0, |str| str.len()) -} - -fn comment_len(comment: Option<&str>) -> usize { - match comment { - Some(s) => { - let text_len = s.trim().len(); - if text_len > 0 { - // We'll put " /*" before and " */" after inline comments. - text_len + 6 - } else { - text_len - } - } - None => 0, - } -} - -// Compute horizontal and vertical shapes for a struct-lit-like thing. -pub fn struct_lit_shape( - shape: Shape, - context: &RewriteContext, - prefix_width: usize, - suffix_width: usize, -) -> Option<(Option<Shape>, Shape)> { - let v_shape = match context.config.indent_style() { - IndentStyle::Visual => shape - .visual_indent(0) - .shrink_left(prefix_width)? - .sub_width(suffix_width)?, - IndentStyle::Block => { - let shape = shape.block_indent(context.config.tab_spaces()); - Shape { - width: context.budget(shape.indent.width()), - ..shape - } - } - }; - let shape_width = shape.width.checked_sub(prefix_width + suffix_width); - if let Some(w) = shape_width { - let shape_width = cmp::min(w, context.config.width_heuristics().struct_lit_width); - Some((Some(Shape::legacy(shape_width, shape.indent)), v_shape)) - } else { - Some((None, v_shape)) - } -} - -// Compute the tactic for the internals of a struct-lit-like thing. -pub fn struct_lit_tactic( - h_shape: Option<Shape>, - context: &RewriteContext, - items: &[ListItem], -) -> DefinitiveListTactic { - if let Some(h_shape) = h_shape { - let prelim_tactic = match (context.config.indent_style(), items.len()) { - (IndentStyle::Visual, 1) => ListTactic::HorizontalVertical, - _ if context.config.struct_lit_single_line() => ListTactic::HorizontalVertical, - _ => ListTactic::Vertical, - }; - definitive_tactic(items, prelim_tactic, Separator::Comma, h_shape.width) - } else { - DefinitiveListTactic::Vertical - } -} - -// Given a tactic and possible shapes for horizontal and vertical layout, -// come up with the actual shape to use. -pub fn shape_for_tactic( - tactic: DefinitiveListTactic, - h_shape: Option<Shape>, - v_shape: Shape, -) -> Shape { - match tactic { - DefinitiveListTactic::Horizontal => h_shape.unwrap(), - _ => v_shape, - } -} - -// Create a ListFormatting object for formatting the internals of a -// struct-lit-like thing, that is a series of fields. -pub fn struct_lit_formatting<'a>( - shape: Shape, - tactic: DefinitiveListTactic, - context: &'a RewriteContext, - force_no_trailing_comma: bool, -) -> ListFormatting<'a> { - let ends_with_newline = context.config.indent_style() != IndentStyle::Visual - && tactic == DefinitiveListTactic::Vertical; - ListFormatting { - tactic, - separator: ",", - trailing_separator: if force_no_trailing_comma { - SeparatorTactic::Never - } else { - context.config.trailing_comma() - }, - separator_place: SeparatorPlace::Back, - shape, - ends_with_newline, - preserve_newline: true, - config: context.config, - } -} diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index 3abfe6239fd..00000000000 --- a/src/macros.rs +++ /dev/null @@ -1,871 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -// Format list-like macro invocations. These are invocations whose token trees -// can be interpreted as expressions and separated by commas. -// Note that these token trees do not actually have to be interpreted as -// expressions by the compiler. An example of an invocation we would reformat is -// foo!( x, y, z ). The token x may represent an identifier in the code, but we -// interpreted as an expression. -// Macro uses which are not-list like, such as bar!(key => val), will not be -// reformatted. -// List-like invocations with parentheses will be formatted as function calls, -// and those with brackets will be formatted as array literals. - -use std::collections::HashMap; -use syntax::ast; -use syntax::codemap::{BytePos, Span}; -use syntax::parse::new_parser_from_tts; -use syntax::parse::parser::Parser; -use syntax::parse::token::{BinOpToken, DelimToken, Token}; -use syntax::print::pprust; -use syntax::symbol; -use syntax::tokenstream::{Cursor, ThinTokenStream, TokenStream, TokenTree}; -use syntax::util::ThinVec; - -use codemap::SpanUtils; -use comment::{contains_comment, remove_trailing_white_spaces, FindUncommented}; -use expr::{rewrite_array, rewrite_call_inner}; -use lists::{itemize_list, write_list, DefinitiveListTactic, ListFormatting, SeparatorPlace, - SeparatorTactic}; -use rewrite::{Rewrite, RewriteContext}; -use shape::{Indent, Shape}; -use utils::{format_visibility, mk_sp}; - -const FORCED_BRACKET_MACROS: &[&str] = &["vec!"]; - -// FIXME: use the enum from libsyntax? -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum MacroStyle { - Parens, - Brackets, - Braces, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MacroPosition { - Item, - Statement, - Expression, - Pat, -} - -impl MacroStyle { - fn opener(&self) -> &'static str { - match *self { - MacroStyle::Parens => "(", - MacroStyle::Brackets => "[", - MacroStyle::Braces => "{", - } - } -} - -#[derive(Debug)] -pub enum MacroArg { - Expr(ast::Expr), - Ty(ast::Ty), - Pat(ast::Pat), -} - -impl Rewrite for MacroArg { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - match *self { - MacroArg::Expr(ref expr) => expr.rewrite(context, shape), - MacroArg::Ty(ref ty) => ty.rewrite(context, shape), - MacroArg::Pat(ref pat) => pat.rewrite(context, shape), - } - } -} - -fn parse_macro_arg(parser: &mut Parser) -> Option<MacroArg> { - macro_rules! parse_macro_arg { - ($macro_arg: ident, $parser: ident) => { - let mut cloned_parser = (*parser).clone(); - match cloned_parser.$parser() { - Ok(x) => { - if parser.sess.span_diagnostic.has_errors() { - parser.sess.span_diagnostic.reset_err_count(); - } else { - // Parsing succeeded. - *parser = cloned_parser; - return Some(MacroArg::$macro_arg((*x).clone())); - } - } - Err(mut e) => { - e.cancel(); - parser.sess.span_diagnostic.reset_err_count(); - } - } - }; - } - - parse_macro_arg!(Expr, parse_expr); - parse_macro_arg!(Ty, parse_ty); - parse_macro_arg!(Pat, parse_pat); - - None -} - -pub fn rewrite_macro( - mac: &ast::Mac, - extra_ident: Option<ast::Ident>, - context: &RewriteContext, - shape: Shape, - position: MacroPosition, -) -> Option<String> { - let context = &mut context.clone(); - context.inside_macro = true; - if context.config.use_try_shorthand() { - if let Some(expr) = convert_try_mac(mac, context) { - context.inside_macro = false; - return expr.rewrite(context, shape); - } - } - - let original_style = macro_style(mac, context); - - let macro_name = match extra_ident { - None => format!("{}!", mac.node.path), - Some(ident) => { - if ident == symbol::keywords::Invalid.ident() { - format!("{}!", mac.node.path) - } else { - format!("{}! {}", mac.node.path, ident) - } - } - }; - - let style = if FORCED_BRACKET_MACROS.contains(&¯o_name[..]) { - MacroStyle::Brackets - } else { - original_style - }; - - let ts: TokenStream = mac.node.stream(); - if ts.is_empty() && !contains_comment(context.snippet(mac.span)) { - return match style { - MacroStyle::Parens if position == MacroPosition::Item => { - Some(format!("{}();", macro_name)) - } - MacroStyle::Parens => Some(format!("{}()", macro_name)), - MacroStyle::Brackets => Some(format!("{}[]", macro_name)), - MacroStyle::Braces => Some(format!("{}{{}}", macro_name)), - }; - } - - let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect()); - let mut arg_vec = Vec::new(); - let mut vec_with_semi = false; - let mut trailing_comma = false; - - if MacroStyle::Braces != style { - loop { - match parse_macro_arg(&mut parser) { - Some(arg) => arg_vec.push(arg), - None => return Some(context.snippet(mac.span).to_owned()), - } - - match parser.token { - Token::Eof => break, - Token::Comma => (), - Token::Semi => { - // Try to parse `vec![expr; expr]` - if FORCED_BRACKET_MACROS.contains(&¯o_name[..]) { - parser.bump(); - if parser.token != Token::Eof { - match parse_macro_arg(&mut parser) { - Some(arg) => { - arg_vec.push(arg); - parser.bump(); - if parser.token == Token::Eof && arg_vec.len() == 2 { - vec_with_semi = true; - break; - } - } - None => return Some(context.snippet(mac.span).to_owned()), - } - } - } - return Some(context.snippet(mac.span).to_owned()); - } - _ => return Some(context.snippet(mac.span).to_owned()), - } - - parser.bump(); - - if parser.token == Token::Eof { - trailing_comma = true; - break; - } - } - } - - match style { - MacroStyle::Parens => { - // Format macro invocation as function call, forcing no trailing - // comma because not all macros support them. - rewrite_call_inner( - context, - ¯o_name, - &arg_vec.iter().map(|e| &*e).collect::<Vec<_>>()[..], - mac.span, - shape, - context.config.width_heuristics().fn_call_width, - trailing_comma, - ).map(|rw| match position { - MacroPosition::Item => format!("{};", rw), - _ => rw, - }) - } - MacroStyle::Brackets => { - let mac_shape = shape.offset_left(macro_name.len())?; - // Handle special case: `vec![expr; expr]` - if vec_with_semi { - let (lbr, rbr) = if context.config.spaces_within_parens_and_brackets() { - ("[ ", " ]") - } else { - ("[", "]") - }; - // 6 = `vec!` + `; ` - let total_overhead = lbr.len() + rbr.len() + 6; - let nested_shape = mac_shape.block_indent(context.config.tab_spaces()); - let lhs = arg_vec[0].rewrite(context, nested_shape)?; - let rhs = arg_vec[1].rewrite(context, nested_shape)?; - if !lhs.contains('\n') && !rhs.contains('\n') - && lhs.len() + rhs.len() + total_overhead <= shape.width - { - Some(format!("{}{}{}; {}{}", macro_name, lbr, lhs, rhs, rbr)) - } else { - Some(format!( - "{}{}\n{}{};\n{}{}\n{}{}", - macro_name, - lbr, - nested_shape.indent.to_string(context.config), - lhs, - nested_shape.indent.to_string(context.config), - rhs, - shape.indent.to_string(context.config), - rbr - )) - } - } else { - // If we are rewriting `vec!` macro or other special macros, - // then we can rewrite this as an usual array literal. - // Otherwise, we must preserve the original existence of trailing comma. - if FORCED_BRACKET_MACROS.contains(¯o_name.as_str()) { - context.inside_macro = false; - trailing_comma = false; - } - // Convert `MacroArg` into `ast::Expr`, as `rewrite_array` only accepts the latter. - let sp = mk_sp( - context - .codemap - .span_after(mac.span, original_style.opener()), - mac.span.hi() - BytePos(1), - ); - let arg_vec = &arg_vec.iter().map(|e| &*e).collect::<Vec<_>>()[..]; - let rewrite = rewrite_array(arg_vec, sp, context, mac_shape, trailing_comma)?; - - Some(format!("{}{}", macro_name, rewrite)) - } - } - MacroStyle::Braces => { - // Skip macro invocations with braces, for now. - indent_macro_snippet(context, context.snippet(mac.span), shape.indent) - } - } -} - -pub fn rewrite_macro_def( - context: &RewriteContext, - shape: Shape, - indent: Indent, - def: &ast::MacroDef, - ident: ast::Ident, - vis: &ast::Visibility, - span: Span, -) -> Option<String> { - let snippet = Some(remove_trailing_white_spaces(context.snippet(span))); - - let mut parser = MacroParser::new(def.stream().into_trees()); - let parsed_def = match parser.parse() { - Some(def) => def, - None => return snippet, - }; - - let mut result = if def.legacy { - String::from("macro_rules!") - } else { - format!("{}macro", format_visibility(vis)) - }; - - result += " "; - result += &ident.name.as_str(); - - let multi_branch_style = def.legacy || parsed_def.branches.len() != 1; - - let arm_shape = if multi_branch_style { - shape - .block_indent(context.config.tab_spaces()) - .with_max_width(context.config) - } else { - shape - }; - - let branch_items = itemize_list( - context.codemap, - parsed_def.branches.iter(), - "}", - ";", - |branch| branch.span.lo(), - |branch| branch.span.hi(), - |branch| branch.rewrite(context, arm_shape, multi_branch_style), - context.codemap.span_after(span, "{"), - span.hi(), - false, - ).collect::<Vec<_>>(); - - let fmt = ListFormatting { - tactic: DefinitiveListTactic::Vertical, - separator: if def.legacy { ";" } else { "" }, - trailing_separator: SeparatorTactic::Always, - separator_place: SeparatorPlace::Back, - shape: arm_shape, - ends_with_newline: true, - preserve_newline: true, - config: context.config, - }; - - if multi_branch_style { - result += " {\n"; - result += &arm_shape.indent.to_string(context.config); - } - - result += write_list(&branch_items, &fmt)?.as_str(); - - if multi_branch_style { - result += "\n"; - result += &indent.to_string(context.config); - result += "}"; - } - - Some(result) -} - -// Replaces `$foo` with `zfoo`. We must check for name overlap to ensure we -// aren't causing problems. -// This should also work for escaped `$` variables, where we leave earlier `$`s. -fn replace_names(input: &str) -> Option<(String, HashMap<String, String>)> { - // Each substitution will require five or six extra bytes. - let mut result = String::with_capacity(input.len() + 64); - let mut substs = HashMap::new(); - let mut dollar_count = 0; - let mut cur_name = String::new(); - - for c in input.chars() { - if c == '$' { - dollar_count += 1; - } else if dollar_count == 0 { - result.push(c); - } else if !c.is_alphanumeric() && !cur_name.is_empty() { - // Terminates a name following one or more dollars. - let mut new_name = String::new(); - let mut old_name = String::new(); - old_name.push('$'); - for _ in 0..(dollar_count - 1) { - new_name.push('$'); - old_name.push('$'); - } - new_name.push('z'); - new_name.push_str(&cur_name); - old_name.push_str(&cur_name); - - result.push_str(&new_name); - substs.insert(old_name, new_name); - - result.push(c); - - dollar_count = 0; - cur_name = String::new(); - } else if c == '(' && cur_name.is_empty() { - // FIXME: Support macro def with repeat. - return None; - } else if c.is_alphanumeric() { - cur_name.push(c); - } - } - - // FIXME: duplicate code - if !cur_name.is_empty() { - let mut new_name = String::new(); - let mut old_name = String::new(); - old_name.push('$'); - for _ in 0..(dollar_count - 1) { - new_name.push('$'); - old_name.push('$'); - } - new_name.push('z'); - new_name.push_str(&cur_name); - old_name.push_str(&cur_name); - - result.push_str(&new_name); - substs.insert(old_name, new_name); - } - - debug!("replace_names `{}` {:?}", result, substs); - - Some((result, substs)) -} - -// This is a bit sketchy. The token rules probably need tweaking, but it works -// for some common cases. I hope the basic logic is sufficient. Note that the -// meaning of some tokens is a bit different here from usual Rust, e.g., `*` -// and `(`/`)` have special meaning. -// -// We always try and format on one line. -fn format_macro_args(toks: ThinTokenStream) -> Option<String> { - let mut result = String::with_capacity(128); - let mut insert_space = SpaceState::Never; - - for tok in (toks.into(): TokenStream).trees() { - match tok { - TokenTree::Token(_, t) => { - if !result.is_empty() && force_space_before(&t) { - insert_space = SpaceState::Always; - } - if force_no_space_before(&t) { - insert_space = SpaceState::Never; - } - match (insert_space, ident_like(&t)) { - (SpaceState::Always, _) - | (SpaceState::Punctuation, false) - | (SpaceState::Ident, true) => { - result.push(' '); - } - _ => {} - } - result.push_str(&pprust::token_to_string(&t)); - insert_space = next_space(&t); - } - TokenTree::Delimited(_, d) => { - if let SpaceState::Always = insert_space { - result.push(' '); - } - let formatted = format_macro_args(d.tts)?; - match d.delim { - DelimToken::Paren => { - result.push_str(&format!("({})", formatted)); - insert_space = SpaceState::Always; - } - DelimToken::Bracket => { - result.push_str(&format!("[{}]", formatted)); - insert_space = SpaceState::Always; - } - DelimToken::Brace => { - result.push_str(&format!(" {{ {} }}", formatted)); - insert_space = SpaceState::Always; - } - DelimToken::NoDelim => { - result.push_str(&format!("{}", formatted)); - insert_space = SpaceState::Always; - } - } - } - } - } - - Some(result) -} - -// We should insert a space if the next token is a: -#[derive(Copy, Clone)] -enum SpaceState { - Never, - Punctuation, - Ident, // Or ident/literal-like thing. - Always, -} - -fn force_space_before(tok: &Token) -> bool { - match *tok { - Token::Eq - | Token::Lt - | Token::Le - | Token::EqEq - | Token::Ne - | Token::Ge - | Token::Gt - | Token::AndAnd - | Token::OrOr - | Token::Not - | Token::Tilde - | Token::BinOpEq(_) - | Token::At - | Token::RArrow - | Token::LArrow - | Token::FatArrow - | Token::Pound - | Token::Dollar => true, - Token::BinOp(bot) => bot != BinOpToken::Star, - _ => false, - } -} - -fn force_no_space_before(tok: &Token) -> bool { - match *tok { - Token::Semi | Token::Comma | Token::Dot => true, - Token::BinOp(bot) => bot == BinOpToken::Star, - _ => false, - } -} -fn ident_like(tok: &Token) -> bool { - match *tok { - Token::Ident(_) | Token::Literal(..) | Token::Lifetime(_) => true, - _ => false, - } -} - -fn next_space(tok: &Token) -> SpaceState { - match *tok { - Token::Not - | Token::Tilde - | Token::At - | Token::Comma - | Token::Dot - | Token::DotDot - | Token::DotDotDot - | Token::DotDotEq - | Token::DotEq - | Token::Question - | Token::Underscore - | Token::BinOp(_) => SpaceState::Punctuation, - - Token::ModSep - | Token::Pound - | Token::Dollar - | Token::OpenDelim(_) - | Token::CloseDelim(_) - | Token::Whitespace => SpaceState::Never, - - Token::Literal(..) | Token::Ident(_) | Token::Lifetime(_) => SpaceState::Ident, - - _ => SpaceState::Always, - } -} - -/// Tries to convert a macro use into a short hand try expression. Returns None -/// when the macro is not an instance of try! (or parsing the inner expression -/// failed). -pub fn convert_try_mac(mac: &ast::Mac, context: &RewriteContext) -> Option<ast::Expr> { - if &format!("{}", mac.node.path)[..] == "try" { - let ts: TokenStream = mac.node.tts.clone().into(); - let mut parser = new_parser_from_tts(context.parse_session, ts.trees().collect()); - - Some(ast::Expr { - id: ast::NodeId::new(0), // dummy value - node: ast::ExprKind::Try(parser.parse_expr().ok()?), - span: mac.span, // incorrect span, but shouldn't matter too much - attrs: ThinVec::new(), - }) - } else { - None - } -} - -fn macro_style(mac: &ast::Mac, context: &RewriteContext) -> MacroStyle { - let snippet = context.snippet(mac.span); - let paren_pos = snippet.find_uncommented("(").unwrap_or(usize::max_value()); - let bracket_pos = snippet.find_uncommented("[").unwrap_or(usize::max_value()); - let brace_pos = snippet.find_uncommented("{").unwrap_or(usize::max_value()); - - if paren_pos < bracket_pos && paren_pos < brace_pos { - MacroStyle::Parens - } else if bracket_pos < brace_pos { - MacroStyle::Brackets - } else { - MacroStyle::Braces - } -} - -/// Indent each line according to the specified `indent`. -/// e.g. -/// ```rust -/// foo!{ -/// x, -/// y, -/// foo( -/// a, -/// b, -/// c, -/// ), -/// } -/// ``` -/// will become -/// ```rust -/// foo!{ -/// x, -/// y, -/// foo( -/// a, -/// b, -/// c, -// ), -/// } -/// ``` -fn indent_macro_snippet( - context: &RewriteContext, - macro_str: &str, - indent: Indent, -) -> Option<String> { - let mut lines = macro_str.lines(); - let first_line = lines.next().map(|s| s.trim_right())?; - let mut trimmed_lines = Vec::with_capacity(16); - - let min_prefix_space_width = lines - .filter_map(|line| { - let prefix_space_width = if is_empty_line(line) { - None - } else { - Some(get_prefix_space_width(context, line)) - }; - trimmed_lines.push((line.trim(), prefix_space_width)); - prefix_space_width - }) - .min()?; - - Some( - String::from(first_line) + "\n" - + &trimmed_lines - .iter() - .map(|&(line, prefix_space_width)| match prefix_space_width { - Some(original_indent_width) => { - let new_indent_width = indent.width() - + original_indent_width - .checked_sub(min_prefix_space_width) - .unwrap_or(0); - let new_indent = Indent::from_width(context.config, new_indent_width); - format!("{}{}", new_indent.to_string(context.config), line.trim()) - } - None => String::new(), - }) - .collect::<Vec<_>>() - .join("\n"), - ) -} - -fn get_prefix_space_width(context: &RewriteContext, s: &str) -> usize { - let mut width = 0; - for c in s.chars() { - match c { - ' ' => width += 1, - '\t' => width += context.config.tab_spaces(), - _ => return width, - } - } - width -} - -fn is_empty_line(s: &str) -> bool { - s.is_empty() || s.chars().all(char::is_whitespace) -} - -// A very simple parser that just parses a macros 2.0 definition into its branches. -// Currently we do not attempt to parse any further than that. -#[derive(new)] -struct MacroParser { - toks: Cursor, -} - -impl MacroParser { - // (`(` ... `)` `=>` `{` ... `}`)* - fn parse(&mut self) -> Option<Macro> { - let mut branches = vec![]; - while self.toks.look_ahead(1).is_some() { - branches.push(self.parse_branch()?); - } - - Some(Macro { branches }) - } - - // `(` ... `)` `=>` `{` ... `}` - fn parse_branch(&mut self) -> Option<MacroBranch> { - let tok = self.toks.next()?; - let (lo, args_paren_kind) = match tok { - TokenTree::Token(..) => return None, - TokenTree::Delimited(sp, ref d) => (sp.lo(), d.delim), - }; - let args = tok.joint().into(); - match self.toks.next()? { - TokenTree::Token(_, Token::FatArrow) => {} - _ => return None, - } - let (mut hi, body) = match self.toks.next()? { - TokenTree::Token(..) => return None, - TokenTree::Delimited(sp, _) => { - let data = sp.data(); - ( - data.hi, - Span::new(data.lo + BytePos(1), data.hi - BytePos(1), data.ctxt), - ) - } - }; - if let Some(TokenTree::Token(sp, Token::Semi)) = self.toks.look_ahead(0) { - self.toks.next(); - hi = sp.hi(); - } - Some(MacroBranch { - span: mk_sp(lo, hi), - args_paren_kind, - args, - body, - }) - } -} - -// A parsed macros 2.0 macro definition. -struct Macro { - branches: Vec<MacroBranch>, -} - -// FIXME: it would be more efficient to use references to the token streams -// rather than clone them, if we can make the borrowing work out. -struct MacroBranch { - span: Span, - args_paren_kind: DelimToken, - args: ThinTokenStream, - body: Span, -} - -impl MacroBranch { - fn rewrite( - &self, - context: &RewriteContext, - shape: Shape, - multi_branch_style: bool, - ) -> Option<String> { - // Only attempt to format function-like macros. - if self.args_paren_kind != DelimToken::Paren { - // FIXME(#1539): implement for non-sugared macros. - return None; - } - - let mut result = format_macro_args(self.args.clone())?; - - if multi_branch_style { - result += " =>"; - } - - // The macro body is the most interesting part. It might end up as various - // AST nodes, but also has special variables (e.g, `$foo`) which can't be - // parsed as regular Rust code (and note that these can be escaped using - // `$$`). We'll try and format like an AST node, but we'll substitute - // variables for new names with the same length first. - - let old_body = context.snippet(self.body).trim(); - let (body_str, substs) = replace_names(old_body)?; - - let mut config = context.config.clone(); - config.set().hide_parse_errors(true); - - result += " {"; - - let has_block_body = old_body.starts_with('{'); - - let body_indent = if has_block_body { - shape.indent - } else { - // We'll hack the indent below, take this into account when formatting, - let body_indent = shape.indent.block_indent(&config); - let new_width = config.max_width() - body_indent.width(); - config.set().max_width(new_width); - body_indent - }; - - // First try to format as items, then as statements. - let new_body = match ::format_snippet(&body_str, &config) { - Some(new_body) => new_body, - None => match ::format_code_block(&body_str, &config) { - Some(new_body) => new_body, - None => return None, - }, - }; - - // Indent the body since it is in a block. - let indent_str = body_indent.to_string(&config); - let mut new_body = new_body - .trim_right() - .lines() - .fold(String::new(), |mut s, l| { - if !l.is_empty() { - s += &indent_str; - } - s + l + "\n" - }); - - // Undo our replacement of macro variables. - // FIXME: this could be *much* more efficient. - for (old, new) in &substs { - if old_body.find(new).is_some() { - debug!("rewrite_macro_def: bailing matching variable: `{}`", new); - return None; - } - new_body = new_body.replace(new, old); - } - - if has_block_body { - result += new_body.trim(); - } else if !new_body.is_empty() { - result += "\n"; - result += &new_body; - result += &shape.indent.to_string(&config); - } - - result += "}"; - - Some(result) - } -} - -#[cfg(test)] -mod test { - use super::*; - use syntax::parse::{parse_stream_from_source_str, ParseSess}; - use syntax::codemap::{FileName, FilePathMapping}; - - fn format_macro_args_str(s: &str) -> String { - let input = parse_stream_from_source_str( - FileName::Custom("stdin".to_owned()), - s.to_owned(), - &ParseSess::new(FilePathMapping::empty()), - None, - ); - format_macro_args(input.into()).unwrap() - } - - #[test] - fn test_format_macro_args() { - assert_eq!(format_macro_args_str(""), "".to_owned()); - assert_eq!(format_macro_args_str("$ x : ident"), "$x: ident".to_owned()); - assert_eq!( - format_macro_args_str("$ m1 : ident , $ m2 : ident , $ x : ident"), - "$m1: ident, $m2: ident, $x: ident".to_owned() - ); - assert_eq!( - format_macro_args_str("$($beginning:ident),*;$middle:ident;$($end:ident),*"), - "$($beginning: ident),*; $middle: ident; $($end: ident),*".to_owned() - ); - assert_eq!( - format_macro_args_str( - "$ name : ident ( $ ( $ dol : tt $ var : ident ) * ) $ ( $ body : tt ) *" - ), - "$name: ident($($dol: tt $var: ident)*) $($body: tt)*".to_owned() - ); - } -} diff --git a/src/missed_spans.rs b/src/missed_spans.rs deleted file mode 100644 index dff6b94bd75..00000000000 --- a/src/missed_spans.rs +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::borrow::Cow; -use std::iter::repeat; - -use syntax::codemap::{BytePos, FileName, Pos, Span}; - -use codemap::LineRangeUtils; -use comment::{rewrite_comment, CodeCharKind, CommentCodeSlices}; -use config::WriteMode; -use shape::{Indent, Shape}; -use utils::{count_newlines, last_line_width, mk_sp}; -use visitor::FmtVisitor; - -struct SnippetStatus { - /// An offset to the current line from the beginnig of the original snippet. - line_start: usize, - /// A length of trailing whitespaces on the current line. - last_wspace: Option<usize>, - /// The current line number. - cur_line: usize, -} - -impl SnippetStatus { - fn new(cur_line: usize) -> Self { - SnippetStatus { - line_start: 0, - last_wspace: None, - cur_line, - } - } -} - -impl<'a> FmtVisitor<'a> { - fn output_at_start(&self) -> bool { - self.buffer.is_empty() - } - - // TODO these format_missing methods are ugly. Refactor and add unit tests - // for the central whitespace stripping loop. - pub fn format_missing(&mut self, end: BytePos) { - self.format_missing_inner(end, |this, last_snippet, _| this.push_str(last_snippet)) - } - - pub fn format_missing_with_indent(&mut self, end: BytePos) { - let config = self.config; - self.format_missing_inner(end, |this, last_snippet, snippet| { - this.push_str(last_snippet.trim_right()); - if last_snippet == snippet && !this.output_at_start() { - // No new lines in the snippet. - this.push_str("\n"); - } - let indent = this.block_indent.to_string(config); - this.push_str(&indent); - }) - } - - pub fn format_missing_no_indent(&mut self, end: BytePos) { - self.format_missing_inner(end, |this, last_snippet, _| { - this.push_str(last_snippet.trim_right()); - }) - } - - fn format_missing_inner<F: Fn(&mut FmtVisitor, &str, &str)>( - &mut self, - end: BytePos, - process_last_snippet: F, - ) { - let start = self.last_pos; - - if start == end { - // Do nothing if this is the beginning of the file. - if !self.output_at_start() { - process_last_snippet(self, "", ""); - } - return; - } - - assert!( - start < end, - "Request to format inverted span: {:?} to {:?}", - self.codemap.lookup_char_pos(start), - self.codemap.lookup_char_pos(end) - ); - - self.last_pos = end; - let span = mk_sp(start, end); - let snippet = self.snippet(span); - if snippet.trim().is_empty() && !out_of_file_lines_range!(self, span) { - // Keep vertical spaces within range. - self.push_vertical_spaces(count_newlines(snippet)); - process_last_snippet(self, "", snippet); - } else { - self.write_snippet(span, &process_last_snippet); - } - } - - fn push_vertical_spaces(&mut self, mut newline_count: usize) { - // The buffer already has a trailing newline. - let offset = if self.buffer.ends_with('\n') { 0 } else { 1 }; - let newline_upper_bound = self.config.blank_lines_upper_bound() + offset; - let newline_lower_bound = self.config.blank_lines_lower_bound() + offset; - if newline_count > newline_upper_bound { - newline_count = newline_upper_bound; - } else if newline_count < newline_lower_bound { - newline_count = newline_lower_bound; - } - let blank_lines: String = repeat('\n').take(newline_count).collect(); - self.push_str(&blank_lines); - } - - fn write_snippet<F>(&mut self, span: Span, process_last_snippet: F) - where - F: Fn(&mut FmtVisitor, &str, &str), - { - // Get a snippet from the file start to the span's hi without allocating. - // We need it to determine what precedes the current comment. If the comment - // follows code on the same line, we won't touch it. - let big_span_lo = self.codemap.lookup_char_pos(span.lo()).file.start_pos; - let local_begin = self.codemap.lookup_byte_offset(big_span_lo); - let local_end = self.codemap.lookup_byte_offset(span.hi()); - let start_index = local_begin.pos.to_usize(); - let end_index = local_end.pos.to_usize(); - let big_snippet = &local_begin.fm.src.as_ref().unwrap()[start_index..end_index]; - - let big_diff = (span.lo() - big_span_lo).to_usize(); - let snippet = self.snippet(span); - - debug!("write_snippet `{}`", snippet); - - self.write_snippet_inner(big_snippet, big_diff, snippet, span, process_last_snippet); - } - - fn write_snippet_inner<F>( - &mut self, - big_snippet: &str, - big_diff: usize, - old_snippet: &str, - span: Span, - process_last_snippet: F, - ) where - F: Fn(&mut FmtVisitor, &str, &str), - { - // Trim whitespace from the right hand side of each line. - // Annoyingly, the library functions for splitting by lines etc. are not - // quite right, so we must do it ourselves. - let char_pos = self.codemap.lookup_char_pos(span.lo()); - let file_name = &char_pos.file.name; - let mut status = SnippetStatus::new(char_pos.line); - - let snippet = &*match self.config.write_mode() { - WriteMode::Coverage => replace_chars(old_snippet), - _ => Cow::from(old_snippet), - }; - - for (kind, offset, subslice) in CommentCodeSlices::new(snippet) { - debug!("{:?}: {:?}", kind, subslice); - - let newline_count = count_newlines(subslice); - let within_file_lines_range = self.config.file_lines().intersects_range( - file_name, - status.cur_line, - status.cur_line + newline_count, - ); - - if CodeCharKind::Comment == kind && within_file_lines_range { - // 1: comment. - self.process_comment( - &mut status, - snippet, - &big_snippet[..(offset + big_diff)], - offset, - subslice, - ); - } else if subslice.trim().is_empty() && newline_count > 0 && within_file_lines_range { - // 2: blank lines. - self.push_vertical_spaces(newline_count); - status.cur_line += newline_count; - status.line_start = offset + newline_count; - } else { - // 3: code which we failed to format or which is not within file-lines range. - self.process_missing_code(&mut status, snippet, subslice, offset, file_name); - } - } - - process_last_snippet(self, &snippet[status.line_start..], snippet); - } - - fn process_comment( - &mut self, - status: &mut SnippetStatus, - snippet: &str, - big_snippet: &str, - offset: usize, - subslice: &str, - ) { - let last_char = big_snippet - .chars() - .rev() - .skip_while(|rev_c| [' ', '\t'].contains(rev_c)) - .next(); - - let fix_indent = last_char.map_or(true, |rev_c| ['{', '\n'].contains(&rev_c)); - - let comment_indent = if fix_indent { - if let Some('{') = last_char { - self.push_str("\n"); - } - let indent_str = self.block_indent.to_string(self.config); - self.push_str(&indent_str); - self.block_indent - } else { - self.push_str(" "); - Indent::from_width(self.config, last_line_width(&self.buffer)) - }; - - let comment_width = ::std::cmp::min( - self.config.comment_width(), - self.config.max_width() - self.block_indent.width(), - ); - let comment_shape = Shape::legacy(comment_width, comment_indent); - let comment_str = rewrite_comment(subslice, false, comment_shape, self.config) - .unwrap_or_else(|| String::from(subslice)); - self.push_str(&comment_str); - - status.last_wspace = None; - status.line_start = offset + subslice.len(); - - if let Some('/') = subslice.chars().nth(1) { - // check that there are no contained block comments - if !subslice - .split('\n') - .map(|s| s.trim_left()) - .any(|s| s.len() >= 2 && &s[0..2] == "/*") - { - // Add a newline after line comments - self.push_str("\n"); - } - } else if status.line_start <= snippet.len() { - // For other comments add a newline if there isn't one at the end already - match snippet[status.line_start..].chars().next() { - Some('\n') | Some('\r') => (), - _ => self.push_str("\n"), - } - } - - status.cur_line += count_newlines(subslice); - } - - fn process_missing_code( - &mut self, - status: &mut SnippetStatus, - snippet: &str, - subslice: &str, - offset: usize, - file_name: &FileName, - ) { - for (mut i, c) in subslice.char_indices() { - i += offset; - - if c == '\n' { - let skip_this_line = !self.config - .file_lines() - .contains_line(file_name, status.cur_line); - if skip_this_line { - status.last_wspace = None; - } - - if let Some(lw) = status.last_wspace { - self.push_str(&snippet[status.line_start..lw]); - self.push_str("\n"); - status.last_wspace = None; - } else { - self.push_str(&snippet[status.line_start..i + 1]); - } - - status.cur_line += 1; - status.line_start = i + 1; - } else if c.is_whitespace() && status.last_wspace.is_none() { - status.last_wspace = Some(i); - } else if c == ';' && status.last_wspace.is_some() { - status.line_start = i; - status.last_wspace = None; - } else { - status.last_wspace = None; - } - } - - let remaining = snippet[status.line_start..subslice.len() + offset].trim(); - if !remaining.is_empty() { - self.push_str(remaining); - status.line_start = subslice.len() + offset; - } - } -} - -fn replace_chars(string: &str) -> Cow<str> { - Cow::from( - string - .chars() - .map(|ch| if ch.is_whitespace() { ch } else { 'X' }) - .collect::<String>(), - ) -} diff --git a/src/modules.rs b/src/modules.rs deleted file mode 100644 index 0f439910324..00000000000 --- a/src/modules.rs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::collections::BTreeMap; -use std::io; -use std::path::{Path, PathBuf}; - -use syntax::ast; -use syntax::codemap::{self, FileName}; -use syntax::parse::parser; - -use utils::contains_skip; - -/// List all the files containing modules of a crate. -/// If a file is used twice in a crate, it appears only once. -pub fn list_files<'a>( - krate: &'a ast::Crate, - codemap: &codemap::CodeMap, -) -> Result<BTreeMap<FileName, &'a ast::Mod>, io::Error> { - let mut result = BTreeMap::new(); // Enforce file order determinism - let root_filename = codemap.span_to_filename(krate.span); - { - let parent = match root_filename { - FileName::Real(ref path) => path.parent().unwrap(), - _ => Path::new(""), - }; - list_submodules(&krate.module, parent, codemap, &mut result)?; - } - result.insert(root_filename, &krate.module); - Ok(result) -} - -/// Recursively list all external modules included in a module. -fn list_submodules<'a>( - module: &'a ast::Mod, - search_dir: &Path, - codemap: &codemap::CodeMap, - result: &mut BTreeMap<FileName, &'a ast::Mod>, -) -> Result<(), io::Error> { - debug!("list_submodules: search_dir: {:?}", search_dir); - for item in &module.items { - if let ast::ItemKind::Mod(ref sub_mod) = item.node { - if !contains_skip(&item.attrs) { - let is_internal = - codemap.span_to_filename(item.span) == codemap.span_to_filename(sub_mod.inner); - let dir_path = if is_internal { - search_dir.join(&item.ident.to_string()) - } else { - let mod_path = module_file(item.ident, &item.attrs, search_dir, codemap)?; - let dir_path = mod_path.parent().unwrap().to_owned(); - result.insert(FileName::Real(mod_path), sub_mod); - dir_path - }; - list_submodules(sub_mod, &dir_path, codemap, result)?; - } - } - } - Ok(()) -} - -/// Find the file corresponding to an external mod -fn module_file( - id: ast::Ident, - attrs: &[ast::Attribute], - dir_path: &Path, - codemap: &codemap::CodeMap, -) -> Result<PathBuf, io::Error> { - if let Some(path) = parser::Parser::submod_path_from_attr(attrs, dir_path) { - return Ok(path); - } - - match parser::Parser::default_submod_path(id, None, dir_path, codemap).result { - Ok(parser::ModulePathSuccess { path, .. }) => Ok(path), - Err(_) => Err(io::Error::new( - io::ErrorKind::Other, - format!("Couldn't find module {}", id), - )), - } -} diff --git a/src/patterns.rs b/src/patterns.rs deleted file mode 100644 index 4719160061b..00000000000 --- a/src/patterns.rs +++ /dev/null @@ -1,379 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use syntax::ast::{self, BindingMode, FieldPat, Pat, PatKind, RangeEnd, RangeSyntax}; -use syntax::codemap::{self, BytePos, Span}; -use syntax::ptr; - -use codemap::SpanUtils; -use comment::FindUncommented; -use expr::{can_be_overflowed_expr, rewrite_call_inner, rewrite_pair, rewrite_unary_prefix, - wrap_struct_field, PairParts}; -use lists::{itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape, - struct_lit_tactic, write_list, DefinitiveListTactic, SeparatorPlace, SeparatorTactic}; -use macros::{rewrite_macro, MacroPosition}; -use rewrite::{Rewrite, RewriteContext}; -use shape::Shape; -use spanned::Spanned; -use types::{rewrite_path, PathContext}; -use utils::{format_mutability, mk_sp}; - -impl Rewrite for Pat { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - match self.node { - PatKind::Box(ref pat) => rewrite_unary_prefix(context, "box ", &**pat, shape), - PatKind::Ident(binding_mode, ident, ref sub_pat) => { - let (prefix, mutability) = match binding_mode { - BindingMode::ByRef(mutability) => ("ref ", mutability), - BindingMode::ByValue(mutability) => ("", mutability), - }; - let mut_infix = format_mutability(mutability); - let id_str = ident.node.to_string(); - let sub_pat = match *sub_pat { - Some(ref p) => { - // 3 - ` @ `. - let width = shape - .width - .checked_sub(prefix.len() + mut_infix.len() + id_str.len() + 3)?; - format!( - " @ {}", - p.rewrite(context, Shape::legacy(width, shape.indent))? - ) - } - None => "".to_owned(), - }; - - Some(format!("{}{}{}{}", prefix, mut_infix, id_str, sub_pat)) - } - PatKind::Wild => { - if 1 <= shape.width { - Some("_".to_owned()) - } else { - None - } - } - PatKind::Range(ref lhs, ref rhs, ref end_kind) => { - let infix = match *end_kind { - RangeEnd::Included(RangeSyntax::DotDotDot) => "...", - RangeEnd::Included(RangeSyntax::DotDotEq) => "..=", - RangeEnd::Excluded => "..", - }; - let infix = if context.config.spaces_around_ranges() { - format!(" {} ", infix) - } else { - infix.to_owned() - }; - rewrite_pair( - &**lhs, - &**rhs, - PairParts::new("", &infix, ""), - context, - shape, - SeparatorPlace::Front, - ) - } - PatKind::Ref(ref pat, mutability) => { - let prefix = format!("&{}", format_mutability(mutability)); - rewrite_unary_prefix(context, &prefix, &**pat, shape) - } - PatKind::Tuple(ref items, dotdot_pos) => { - rewrite_tuple_pat(items, dotdot_pos, None, self.span, context, shape) - } - PatKind::Path(ref q_self, ref path) => { - rewrite_path(context, PathContext::Expr, q_self.as_ref(), path, shape) - } - PatKind::TupleStruct(ref path, ref pat_vec, dotdot_pos) => { - let path_str = rewrite_path(context, PathContext::Expr, None, path, shape)?; - rewrite_tuple_pat( - pat_vec, - dotdot_pos, - Some(path_str), - self.span, - context, - shape, - ) - } - PatKind::Lit(ref expr) => expr.rewrite(context, shape), - PatKind::Slice(ref prefix, ref slice_pat, ref suffix) => { - // Rewrite all the sub-patterns. - let prefix = prefix.iter().map(|p| p.rewrite(context, shape)); - let slice_pat = slice_pat - .as_ref() - .map(|p| Some(format!("{}..", p.rewrite(context, shape)?))); - let suffix = suffix.iter().map(|p| p.rewrite(context, shape)); - - // Munge them together. - let pats: Option<Vec<String>> = - prefix.chain(slice_pat.into_iter()).chain(suffix).collect(); - - // Check that all the rewrites succeeded, and if not return None. - let pats = pats?; - - // Unwrap all the sub-strings and join them with commas. - let result = if context.config.spaces_within_parens_and_brackets() { - format!("[ {} ]", pats.join(", ")) - } else { - format!("[{}]", pats.join(", ")) - }; - Some(result) - } - PatKind::Struct(ref path, ref fields, ellipsis) => { - rewrite_struct_pat(path, fields, ellipsis, self.span, context, shape) - } - PatKind::Mac(ref mac) => rewrite_macro(mac, None, context, shape, MacroPosition::Pat), - } - } -} - -fn rewrite_struct_pat( - path: &ast::Path, - fields: &[codemap::Spanned<ast::FieldPat>], - ellipsis: bool, - span: Span, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - // 2 = ` {` - let path_shape = shape.sub_width(2)?; - let path_str = rewrite_path(context, PathContext::Expr, None, path, path_shape)?; - - if fields.is_empty() && !ellipsis { - return Some(format!("{} {{}}", path_str)); - } - - let (ellipsis_str, terminator) = if ellipsis { (", ..", "..") } else { ("", "}") }; - - // 3 = ` { `, 2 = ` }`. - let (h_shape, v_shape) = - struct_lit_shape(shape, context, path_str.len() + 3, ellipsis_str.len() + 2)?; - - let items = itemize_list( - context.codemap, - fields.iter(), - terminator, - ",", - |f| f.span.lo(), - |f| f.span.hi(), - |f| f.node.rewrite(context, v_shape), - context.codemap.span_after(span, "{"), - span.hi(), - false, - ); - let item_vec = items.collect::<Vec<_>>(); - - let tactic = struct_lit_tactic(h_shape, context, &item_vec); - let nested_shape = shape_for_tactic(tactic, h_shape, v_shape); - let fmt = struct_lit_formatting(nested_shape, tactic, context, false); - - let mut fields_str = write_list(&item_vec, &fmt)?; - let one_line_width = h_shape.map_or(0, |shape| shape.width); - - if ellipsis { - if fields_str.contains('\n') || fields_str.len() > one_line_width { - // Add a missing trailing comma. - if fmt.trailing_separator == SeparatorTactic::Never { - fields_str.push_str(","); - } - fields_str.push_str("\n"); - fields_str.push_str(&nested_shape.indent.to_string(context.config)); - fields_str.push_str(".."); - } else { - if !fields_str.is_empty() { - // there are preceding struct fields being matched on - if fmt.tactic == DefinitiveListTactic::Vertical { - // if the tactic is Vertical, write_list already added a trailing , - fields_str.push_str(" "); - } else { - fields_str.push_str(", "); - } - } - fields_str.push_str(".."); - } - } - - let fields_str = wrap_struct_field(context, &fields_str, shape, v_shape, one_line_width); - Some(format!("{} {{{}}}", path_str, fields_str)) -} - -impl Rewrite for FieldPat { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - let pat = self.pat.rewrite(context, shape); - if self.is_shorthand { - pat - } else { - let pat_str = pat?; - let id_str = self.ident.to_string(); - let one_line_width = id_str.len() + 2 + pat_str.len(); - if one_line_width <= shape.width { - Some(format!("{}: {}", id_str, pat_str)) - } else { - let nested_shape = shape.block_indent(context.config.tab_spaces()); - let pat_str = self.pat.rewrite(context, nested_shape)?; - Some(format!( - "{}:\n{}{}", - id_str, - nested_shape.indent.to_string(context.config), - pat_str, - )) - } - } - } -} - -pub enum TuplePatField<'a> { - Pat(&'a ptr::P<ast::Pat>), - Dotdot(Span), -} - -impl<'a> Rewrite for TuplePatField<'a> { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - match *self { - TuplePatField::Pat(p) => p.rewrite(context, shape), - TuplePatField::Dotdot(_) => Some("..".to_string()), - } - } -} - -impl<'a> Spanned for TuplePatField<'a> { - fn span(&self) -> Span { - match *self { - TuplePatField::Pat(p) => p.span(), - TuplePatField::Dotdot(span) => span, - } - } -} - -pub fn can_be_overflowed_pat(context: &RewriteContext, pat: &TuplePatField, len: usize) -> bool { - match *pat { - TuplePatField::Pat(pat) => match pat.node { - ast::PatKind::Path(..) - | ast::PatKind::Tuple(..) - | ast::PatKind::Struct(..) - | ast::PatKind::TupleStruct(..) => context.use_block_indent() && len == 1, - ast::PatKind::Ref(ref p, _) | ast::PatKind::Box(ref p) => { - can_be_overflowed_pat(context, &TuplePatField::Pat(p), len) - } - ast::PatKind::Lit(ref expr) => can_be_overflowed_expr(context, expr, len), - _ => false, - }, - TuplePatField::Dotdot(..) => false, - } -} - -fn rewrite_tuple_pat( - pats: &[ptr::P<ast::Pat>], - dotdot_pos: Option<usize>, - path_str: Option<String>, - span: Span, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - let mut pat_vec: Vec<_> = pats.into_iter().map(|x| TuplePatField::Pat(x)).collect(); - - if let Some(pos) = dotdot_pos { - let prev = if pos == 0 { - span.lo() - } else { - pats[pos - 1].span().hi() - }; - let next = if pos + 1 >= pats.len() { - span.hi() - } else { - pats[pos + 1].span().lo() - }; - let dot_span = mk_sp(prev, next); - let snippet = context.snippet(dot_span); - let lo = dot_span.lo() + BytePos(snippet.find_uncommented("..").unwrap() as u32); - let dotdot = TuplePatField::Dotdot(Span::new( - lo, - // 2 == "..".len() - lo + BytePos(2), - codemap::NO_EXPANSION, - )); - pat_vec.insert(pos, dotdot); - } - - if pat_vec.is_empty() { - return Some(format!("{}()", path_str.unwrap_or_default())); - } - - let wildcard_suffix_len = count_wildcard_suffix_len(context, &pat_vec, span, shape); - let (pat_vec, span) = if context.config.condense_wildcard_suffixes() && wildcard_suffix_len >= 2 - { - let new_item_count = 1 + pat_vec.len() - wildcard_suffix_len; - let sp = pat_vec[new_item_count - 1].span(); - let snippet = context.snippet(sp); - let lo = sp.lo() + BytePos(snippet.find_uncommented("_").unwrap() as u32); - pat_vec[new_item_count - 1] = TuplePatField::Dotdot(mk_sp(lo, lo + BytePos(1))); - ( - &pat_vec[..new_item_count], - mk_sp(span.lo(), lo + BytePos(1)), - ) - } else { - (&pat_vec[..], span) - }; - - // add comma if `(x,)` - let add_comma = path_str.is_none() && pat_vec.len() == 1 && dotdot_pos.is_none(); - let mut context = context.clone(); - if let Some(&TuplePatField::Dotdot(..)) = pat_vec.last() { - context.inside_macro = true; - } - let path_str = path_str.unwrap_or_default(); - let mut pat_ref_vec = Vec::with_capacity(pat_vec.len()); - for pat in pat_vec { - pat_ref_vec.push(pat); - } - - rewrite_call_inner( - &context, - &path_str, - &pat_ref_vec[..], - span, - shape, - shape.width, - add_comma, - ) -} - -fn count_wildcard_suffix_len( - context: &RewriteContext, - patterns: &[TuplePatField], - span: Span, - shape: Shape, -) -> usize { - let mut suffix_len = 0; - - let items: Vec<_> = itemize_list( - context.codemap, - patterns.iter(), - ")", - ",", - |item| item.span().lo(), - |item| item.span().hi(), - |item| item.rewrite(context, shape), - context.codemap.span_after(span, "("), - span.hi() - BytePos(1), - false, - ).collect(); - - for item in items.iter().rev().take_while(|i| match i.item { - Some(ref internal_string) if internal_string == "_" => true, - _ => false, - }) { - suffix_len += 1; - - if item.pre_comment.is_some() || item.post_comment.is_some() { - break; - } - } - - suffix_len -} diff --git a/src/rewrite.rs b/src/rewrite.rs deleted file mode 100644 index 708e31d86dd..00000000000 --- a/src/rewrite.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -// A generic trait to abstract the rewriting of an element (of the AST). - -use syntax::codemap::{CodeMap, Span}; -use syntax::parse::ParseSess; - -use config::{Config, IndentStyle}; -use shape::Shape; -use visitor::SnippetProvider; - -pub trait Rewrite { - /// Rewrite self into shape. - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String>; -} - -#[derive(Clone)] -pub struct RewriteContext<'a> { - pub parse_session: &'a ParseSess, - pub codemap: &'a CodeMap, - pub config: &'a Config, - pub inside_macro: bool, - // Force block indent style even if we are using visual indent style. - pub use_block: bool, - // When `format_if_else_cond_comment` is true, unindent the comment on top - // of the `else` or `else if`. - pub is_if_else_block: bool, - // When rewriting chain, veto going multi line except the last element - pub force_one_line_chain: bool, - pub snippet_provider: &'a SnippetProvider<'a>, -} - -impl<'a> RewriteContext<'a> { - pub fn snippet(&self, span: Span) -> &str { - self.snippet_provider.span_to_snippet(span).unwrap() - } - - /// Return true if we should use block indent style for rewriting function call. - pub fn use_block_indent(&self) -> bool { - self.config.indent_style() == IndentStyle::Block || self.use_block - } - - pub fn budget(&self, used_width: usize) -> usize { - self.config.max_width().checked_sub(used_width).unwrap_or(0) - } -} diff --git a/src/rustfmt_diff.rs b/src/rustfmt_diff.rs deleted file mode 100644 index 1a2f570f89e..00000000000 --- a/src/rustfmt_diff.rs +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright 2017 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use config::Color; -use diff; -use std::collections::VecDeque; -use std::io; -use term; -use utils::use_colored_tty; - -#[derive(Debug, PartialEq)] -pub enum DiffLine { - Context(String), - Expected(String), - Resulting(String), -} - -#[derive(Debug, PartialEq)] -pub struct Mismatch { - pub line_number: u32, - pub lines: Vec<DiffLine>, -} - -impl Mismatch { - fn new(line_number: u32) -> Mismatch { - Mismatch { - line_number, - lines: Vec::new(), - } - } -} - -// This struct handles writing output to stdout and abstracts away the logic -// of printing in color, if it's possible in the executing environment. -pub struct OutputWriter { - terminal: Option<Box<term::Terminal<Output = io::Stdout>>>, -} - -impl OutputWriter { - // Create a new OutputWriter instance based on the caller's preference - // for colorized output and the capabilities of the terminal. - pub fn new(color: Color) -> Self { - if let Some(t) = term::stdout() { - if use_colored_tty(color) && t.supports_color() { - return OutputWriter { terminal: Some(t) }; - } - } - OutputWriter { terminal: None } - } - - // Write output in the optionally specified color. The output is written - // in the specified color if this OutputWriter instance contains a - // Terminal in its `terminal` field. - pub fn writeln(&mut self, msg: &str, color: Option<term::color::Color>) { - match &mut self.terminal { - Some(ref mut t) => { - if let Some(color) = color { - t.fg(color).unwrap(); - } - writeln!(t, "{}", msg).unwrap(); - if color.is_some() { - t.reset().unwrap(); - } - } - None => println!("{}", msg), - } - } -} - -// Produces a diff between the expected output and actual output of rustfmt. -pub fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> { - let mut line_number = 1; - let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size); - let mut lines_since_mismatch = context_size + 1; - let mut results = Vec::new(); - let mut mismatch = Mismatch::new(0); - - for result in diff::lines(expected, actual) { - match result { - diff::Result::Left(str) => { - if lines_since_mismatch >= context_size && lines_since_mismatch > 0 { - results.push(mismatch); - mismatch = Mismatch::new(line_number - context_queue.len() as u32); - } - - while let Some(line) = context_queue.pop_front() { - mismatch.lines.push(DiffLine::Context(line.to_owned())); - } - - mismatch.lines.push(DiffLine::Resulting(str.to_owned())); - lines_since_mismatch = 0; - } - diff::Result::Right(str) => { - if lines_since_mismatch >= context_size && lines_since_mismatch > 0 { - results.push(mismatch); - mismatch = Mismatch::new(line_number - context_queue.len() as u32); - } - - while let Some(line) = context_queue.pop_front() { - mismatch.lines.push(DiffLine::Context(line.to_owned())); - } - - mismatch.lines.push(DiffLine::Expected(str.to_owned())); - line_number += 1; - lines_since_mismatch = 0; - } - diff::Result::Both(str, _) => { - if context_queue.len() >= context_size { - let _ = context_queue.pop_front(); - } - - if lines_since_mismatch < context_size { - mismatch.lines.push(DiffLine::Context(str.to_owned())); - } else if context_size > 0 { - context_queue.push_back(str); - } - - line_number += 1; - lines_since_mismatch += 1; - } - } - } - - results.push(mismatch); - results.remove(0); - - results -} - -pub fn print_diff<F>(diff: Vec<Mismatch>, get_section_title: F, color: Color) -where - F: Fn(u32) -> String, -{ - let mut writer = OutputWriter::new(color); - - for mismatch in diff { - let title = get_section_title(mismatch.line_number); - writer.writeln(&format!("{}", title), None); - - for line in mismatch.lines { - match line { - DiffLine::Context(ref str) => writer.writeln(&format!(" {}⏎", str), None), - DiffLine::Expected(ref str) => { - writer.writeln(&format!("+{}⏎", str), Some(term::color::GREEN)) - } - DiffLine::Resulting(ref str) => { - writer.writeln(&format!("-{}⏎", str), Some(term::color::RED)) - } - } - } - } -} - -#[cfg(test)] -mod test { - use super::{make_diff, Mismatch}; - use super::DiffLine::*; - - #[test] - fn diff_simple() { - let src = "one\ntwo\nthree\nfour\nfive\n"; - let dest = "one\ntwo\ntrois\nfour\nfive\n"; - let diff = make_diff(src, dest, 1); - assert_eq!( - diff, - vec![ - Mismatch { - line_number: 2, - lines: vec![ - Context("two".to_owned()), - Resulting("three".to_owned()), - Expected("trois".to_owned()), - Context("four".to_owned()), - ], - }, - ] - ); - } - - #[test] - fn diff_simple2() { - let src = "one\ntwo\nthree\nfour\nfive\nsix\nseven\n"; - let dest = "one\ntwo\ntrois\nfour\ncinq\nsix\nseven\n"; - let diff = make_diff(src, dest, 1); - assert_eq!( - diff, - vec![ - Mismatch { - line_number: 2, - lines: vec![ - Context("two".to_owned()), - Resulting("three".to_owned()), - Expected("trois".to_owned()), - Context("four".to_owned()), - ], - }, - Mismatch { - line_number: 5, - lines: vec![ - Resulting("five".to_owned()), - Expected("cinq".to_owned()), - Context("six".to_owned()), - ], - }, - ] - ); - } - - #[test] - fn diff_zerocontext() { - let src = "one\ntwo\nthree\nfour\nfive\n"; - let dest = "one\ntwo\ntrois\nfour\nfive\n"; - let diff = make_diff(src, dest, 0); - assert_eq!( - diff, - vec![ - Mismatch { - line_number: 3, - lines: vec![Resulting("three".to_owned()), Expected("trois".to_owned())], - }, - ] - ); - } - - #[test] - fn diff_trailing_newline() { - let src = "one\ntwo\nthree\nfour\nfive"; - let dest = "one\ntwo\nthree\nfour\nfive\n"; - let diff = make_diff(src, dest, 1); - assert_eq!( - diff, - vec![ - Mismatch { - line_number: 5, - lines: vec![Context("five".to_owned()), Expected("".to_owned())], - }, - ] - ); - } -} diff --git a/src/shape.rs b/src/shape.rs deleted file mode 100644 index 8fe2e2b18c0..00000000000 --- a/src/shape.rs +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright 2017 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::borrow::Cow; -use std::ops::{Add, Sub}; - -use Config; - -#[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, -} - -// INDENT_BUFFER.len() = 80 -const INDENT_BUFFER_LEN: usize = 80; -const INDENT_BUFFER: &str = - " "; -impl Indent { - pub fn new(block_indent: usize, alignment: usize) -> Indent { - Indent { - block_indent, - 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) -> Cow<'static, str> { - 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; - if num_tabs == 0 && num_chars <= INDENT_BUFFER_LEN { - Cow::from(&INDENT_BUFFER[0..num_chars]) - } else { - let mut indent = String::with_capacity(num_chars); - for _ in 0..num_tabs { - indent.push('\t') - } - for _ in 0..num_spaces { - indent.push(' ') - } - Cow::from(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, - 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, - 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, - indent, - 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: self.width.checked_sub(width)?, - ..*self - }) - } - - pub fn shrink_left(&self, width: usize) -> Option<Shape> { - Some(Shape { - width: 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) - } -} - -#[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); - } - - #[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); - } - - #[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); - } - - #[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); - } - - #[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); - } -} diff --git a/src/spanned.rs b/src/spanned.rs deleted file mode 100644 index a431f3a544a..00000000000 --- a/src/spanned.rs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2017 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use syntax::ast; -use syntax::codemap::Span; - -use macros::MacroArg; -use utils::{mk_sp, outer_attributes}; - -/// 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); -implement_spanned!(ast::TraitItem); -implement_spanned!(ast::ImplItem); - -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) { - mk_sp(self.pat.span.lo(), self.ty.span.hi()) - } else { - self.ty.span - } - } -} - -impl Spanned for ast::GenericParam { - fn span(&self) -> Span { - match *self { - ast::GenericParam::Lifetime(ref lifetime_def) => lifetime_def.span(), - ast::GenericParam::Type(ref ty) => 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 ast::LifetimeDef { - fn span(&self) -> Span { - let hi = if self.bounds.is_empty() { - self.lifetime.span.hi() - } else { - self.bounds[self.bounds.len() - 1].span.hi() - }; - mk_sp(self.lifetime.span.lo(), hi) - } -} - -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(), - } - } -} diff --git a/src/string.rs b/src/string.rs deleted file mode 100644 index 2386d90ec8a..00000000000 --- a/src/string.rs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -// Format string literals. - -use regex::Regex; -use unicode_segmentation::UnicodeSegmentation; - -use config::Config; -use shape::Shape; -use utils::wrap_str; - -const MIN_STRING: usize = 10; - -pub struct StringFormat<'a> { - pub opener: &'a str, - pub closer: &'a str, - pub line_start: &'a str, - pub line_end: &'a str, - pub shape: Shape, - pub trim_end: bool, - pub config: &'a Config, -} - -impl<'a> StringFormat<'a> { - pub fn new(shape: Shape, config: &'a Config) -> StringFormat<'a> { - StringFormat { - opener: "\"", - closer: "\"", - line_start: " ", - line_end: "\\", - shape, - trim_end: false, - config, - } - } -} - -// FIXME: simplify this! -pub fn rewrite_string<'a>( - orig: &str, - fmt: &StringFormat<'a>, - max_width: Option<usize>, -) -> Option<String> { - // Strip line breaks. - let re = Regex::new(r"([^\\](\\\\)*)\\[\n\r][[:space:]]*").unwrap(); - let stripped_str = re.replace_all(orig, "$1"); - - let graphemes = UnicodeSegmentation::graphemes(&*stripped_str, false).collect::<Vec<&str>>(); - let shape = fmt.shape; - let indent = shape.indent.to_string(fmt.config); - let punctuation = ":,;."; - - // `cur_start` is the position in `orig` of the start of the current line. - let mut cur_start = 0; - let mut result = String::with_capacity( - stripped_str - .len() - .checked_next_power_of_two() - .unwrap_or(usize::max_value()), - ); - result.push_str(fmt.opener); - - let ender_length = fmt.line_end.len(); - // If we cannot put at least a single character per line, the rewrite won't - // succeed. - let mut max_chars = shape - .width - .checked_sub(fmt.opener.len() + ender_length + 1)? + 1; - - // Snip a line at a time from `orig` until it is used up. Push the snippet - // onto result. - 'outer: loop { - // `cur_end` will be where we break the line, as an offset into `orig`. - // Initialised to the maximum it could be (which may be beyond `orig`). - let mut cur_end = cur_start + max_chars; - - // We can fit the rest of the string on this line, so we're done. - if cur_end >= graphemes.len() { - let line = &graphemes[cur_start..].join(""); - result.push_str(line); - break 'outer; - } - - // Push cur_end left until we reach whitespace (or the line is too small). - while !graphemes[cur_end - 1].trim().is_empty() { - cur_end -= 1; - if cur_end < cur_start + MIN_STRING { - // We couldn't find whitespace before the string got too small. - // So start again at the max length and look for punctuation. - cur_end = cur_start + max_chars; - while !punctuation.contains(graphemes[cur_end - 1]) { - cur_end -= 1; - - // If we can't break at whitespace or punctuation, grow the string instead. - if cur_end < cur_start + MIN_STRING { - cur_end = cur_start + max_chars; - while !(punctuation.contains(graphemes[cur_end - 1]) - || graphemes[cur_end - 1].trim().is_empty()) - { - if cur_end >= graphemes.len() { - let line = &graphemes[cur_start..].join(""); - result.push_str(line); - break 'outer; - } - cur_end += 1; - } - break; - } - } - break; - } - } - // Make sure there is no whitespace to the right of the break. - while cur_end < stripped_str.len() && graphemes[cur_end].trim().is_empty() { - cur_end += 1; - } - - // Make the current line and add it on to result. - let raw_line = graphemes[cur_start..cur_end].join(""); - let line = if fmt.trim_end { - raw_line.trim() - } else { - raw_line.as_str() - }; - - result.push_str(line); - result.push_str(fmt.line_end); - result.push('\n'); - result.push_str(&indent); - result.push_str(fmt.line_start); - - // The next line starts where the current line ends. - cur_start = cur_end; - - if let Some(new_max_chars) = max_width { - max_chars = new_max_chars.checked_sub(fmt.opener.len() + ender_length + 1)? + 1; - } - } - - result.push_str(fmt.closer); - wrap_str(result, fmt.config.max_width(), fmt.shape) -} - -#[cfg(test)] -mod test { - use super::{rewrite_string, StringFormat}; - use shape::{Indent, Shape}; - - #[test] - fn issue343() { - let config = Default::default(); - let fmt = StringFormat::new(Shape::legacy(2, Indent::empty()), &config); - rewrite_string("eq_", &fmt, None); - } -} diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index a7472567a81..00000000000 --- a/src/types.rs +++ /dev/null @@ -1,820 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::iter::ExactSizeIterator; -use std::ops::Deref; - -use syntax::ast::{self, FunctionRetTy, Mutability}; -use syntax::codemap::{self, BytePos, Span}; -use syntax::print::pprust; -use syntax::symbol::keywords; - -use codemap::SpanUtils; -use config::{IndentStyle, TypeDensity}; -use expr::{rewrite_pair, rewrite_tuple, rewrite_unary_prefix, wrap_args_with_parens, PairParts}; -use items::{format_generics_item_list, generics_shape_from_config}; -use lists::{definitive_tactic, itemize_list, write_list, ListFormatting, ListTactic, Separator, - SeparatorPlace, SeparatorTactic}; -use macros::{rewrite_macro, MacroPosition}; -use rewrite::{Rewrite, RewriteContext}; -use shape::Shape; -use spanned::Spanned; -use utils::{colon_spaces, extra_offset, first_line_width, format_abi, format_mutability, - last_line_width, mk_sp}; - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum PathContext { - Expr, - Type, - Import, -} - -// Does not wrap on simple segments. -pub fn rewrite_path( - context: &RewriteContext, - path_context: PathContext, - qself: Option<&ast::QSelf>, - path: &ast::Path, - shape: Shape, -) -> Option<String> { - let skip_count = qself.map_or(0, |x| x.position); - - let mut result = if path.is_global() && qself.is_none() && path_context != PathContext::Import { - "::".to_owned() - } else { - String::new() - }; - - let mut span_lo = path.span.lo(); - - if let Some(qself) = qself { - result.push('<'); - if context.config.spaces_within_parens_and_brackets() { - result.push_str(" ") - } - - let fmt_ty = qself.ty.rewrite(context, shape)?; - result.push_str(&fmt_ty); - - if skip_count > 0 { - result.push_str(" as "); - if path.is_global() && path_context != PathContext::Import { - result.push_str("::"); - } - - // 3 = ">::".len() - let shape = shape.sub_width(3)?; - - result = rewrite_path_segments( - PathContext::Type, - result, - path.segments.iter().take(skip_count), - span_lo, - path.span.hi(), - context, - shape, - )?; - } - - if context.config.spaces_within_parens_and_brackets() { - result.push_str(" ") - } - - result.push_str(">::"); - span_lo = qself.ty.span.hi() + BytePos(1); - } - - rewrite_path_segments( - path_context, - result, - path.segments.iter().skip(skip_count), - span_lo, - path.span.hi(), - context, - shape, - ) -} - -fn rewrite_path_segments<'a, I>( - path_context: PathContext, - mut buffer: String, - iter: I, - mut span_lo: BytePos, - span_hi: BytePos, - context: &RewriteContext, - shape: Shape, -) -> Option<String> -where - I: Iterator<Item = &'a ast::PathSegment>, -{ - let mut first = true; - let shape = shape.visual_indent(0); - - for segment in iter { - // Indicates a global path, shouldn't be rendered. - if segment.identifier.name == keywords::CrateRoot.name() { - continue; - } - if first { - first = false; - } else { - buffer.push_str("::"); - } - - let extra_offset = extra_offset(&buffer, shape); - let new_shape = shape.shrink_left(extra_offset)?; - let segment_string = rewrite_segment( - path_context, - segment, - &mut span_lo, - span_hi, - context, - new_shape, - )?; - - buffer.push_str(&segment_string); - } - - Some(buffer) -} - -#[derive(Debug)] -enum SegmentParam<'a> { - LifeTime(&'a ast::Lifetime), - Type(&'a ast::Ty), - Binding(&'a ast::TypeBinding), -} - -impl<'a> SegmentParam<'a> { - fn get_span(&self) -> Span { - match *self { - SegmentParam::LifeTime(lt) => lt.span, - SegmentParam::Type(ty) => ty.span, - SegmentParam::Binding(binding) => binding.span, - } - } -} - -impl<'a> Rewrite for SegmentParam<'a> { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - match *self { - SegmentParam::LifeTime(lt) => lt.rewrite(context, shape), - SegmentParam::Type(ty) => ty.rewrite(context, shape), - SegmentParam::Binding(binding) => { - let mut result = match context.config.type_punctuation_density() { - TypeDensity::Wide => format!("{} = ", binding.ident), - TypeDensity::Compressed => format!("{}=", binding.ident), - }; - let budget = shape.width.checked_sub(result.len())?; - let rewrite = binding - .ty - .rewrite(context, Shape::legacy(budget, shape.indent + result.len()))?; - result.push_str(&rewrite); - Some(result) - } - } - } -} - -// Formats a path segment. There are some hacks involved to correctly determine -// the segment's associated span since it's not part of the AST. -// -// The span_lo is assumed to be greater than the end of any previous segment's -// parameters and lesser or equal than the start of current segment. -// -// span_hi is assumed equal to the end of the entire path. -// -// When the segment contains a positive number of parameters, we update span_lo -// so that invariants described above will hold for the next segment. -fn rewrite_segment( - path_context: PathContext, - segment: &ast::PathSegment, - span_lo: &mut BytePos, - span_hi: BytePos, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - let ident_len = segment.identifier.to_string().len(); - let shape = shape.shrink_left(ident_len)?; - - let params = if let Some(ref params) = segment.parameters { - match **params { - ast::PathParameters::AngleBracketed(ref data) - if !data.lifetimes.is_empty() || !data.types.is_empty() - || !data.bindings.is_empty() => - { - let param_list = data.lifetimes - .iter() - .map(SegmentParam::LifeTime) - .chain(data.types.iter().map(|x| SegmentParam::Type(&*x))) - .chain(data.bindings.iter().map(|x| SegmentParam::Binding(&*x))) - .collect::<Vec<_>>(); - - let next_span_lo = param_list.last().unwrap().get_span().hi() + BytePos(1); - let list_lo = context.codemap.span_after(mk_sp(*span_lo, span_hi), "<"); - let separator = if path_context == PathContext::Expr { - "::" - } else { - "" - }; - - let generics_shape = - generics_shape_from_config(context.config, shape, separator.len())?; - let one_line_width = shape.width.checked_sub(separator.len() + 2)?; - let items = itemize_list( - context.codemap, - param_list.into_iter(), - ">", - ",", - |param| param.get_span().lo(), - |param| param.get_span().hi(), - |seg| seg.rewrite(context, generics_shape), - list_lo, - span_hi, - false, - ); - let generics_str = - format_generics_item_list(context, items, generics_shape, one_line_width)?; - - // Update position of last bracket. - *span_lo = next_span_lo; - - format!("{}{}", separator, generics_str) - } - ast::PathParameters::Parenthesized(ref data) => { - let output = match data.output { - Some(ref ty) => FunctionRetTy::Ty(ty.clone()), - None => FunctionRetTy::Default(codemap::DUMMY_SP), - }; - format_function_type( - data.inputs.iter().map(|x| &**x), - &output, - false, - data.span, - context, - shape, - )? - } - _ => String::new(), - } - } else { - String::new() - }; - - Some(format!("{}{}", segment.identifier, params)) -} - -fn format_function_type<'a, I>( - inputs: I, - output: &FunctionRetTy, - variadic: bool, - span: Span, - context: &RewriteContext, - shape: Shape, -) -> Option<String> -where - I: ExactSizeIterator, - <I as Iterator>::Item: Deref, - <I::Item as Deref>::Target: Rewrite + Spanned + 'a, -{ - // Code for handling variadics is somewhat duplicated for items, but they - // are different enough to need some serious refactoring to share code. - enum ArgumentKind<T> - where - T: Deref, - <T as Deref>::Target: Rewrite + Spanned, - { - Regular(Box<T>), - Variadic(BytePos), - } - - let variadic_arg = if variadic { - let variadic_start = context.codemap.span_before(span, "..."); - Some(ArgumentKind::Variadic(variadic_start)) - } else { - None - }; - - // 2 for () - let budget = shape.width.checked_sub(2)?; - // 1 for ( - let offset = match context.config.indent_style() { - IndentStyle::Block => { - shape - .block() - .block_indent(context.config.tab_spaces()) - .indent - } - IndentStyle::Visual => shape.indent + 1, - }; - let list_shape = Shape::legacy(budget, offset); - let list_lo = context.codemap.span_after(span, "("); - let items = itemize_list( - context.codemap, - // FIXME Would be nice to avoid this allocation, - // but I couldn't get the types to work out. - inputs - .map(|i| ArgumentKind::Regular(Box::new(i))) - .chain(variadic_arg), - ")", - ",", - |arg| match *arg { - ArgumentKind::Regular(ref ty) => ty.span().lo(), - ArgumentKind::Variadic(start) => start, - }, - |arg| match *arg { - ArgumentKind::Regular(ref ty) => ty.span().hi(), - ArgumentKind::Variadic(start) => start + BytePos(3), - }, - |arg| match *arg { - ArgumentKind::Regular(ref ty) => ty.rewrite(context, list_shape), - ArgumentKind::Variadic(_) => Some("...".to_owned()), - }, - list_lo, - span.hi(), - false, - ); - - let item_vec: Vec<_> = items.collect(); - - let tactic = definitive_tactic( - &*item_vec, - ListTactic::HorizontalVertical, - Separator::Comma, - budget, - ); - - let fmt = ListFormatting { - tactic, - separator: ",", - trailing_separator: if !context.use_block_indent() || variadic { - SeparatorTactic::Never - } else { - context.config.trailing_comma() - }, - separator_place: SeparatorPlace::Back, - shape: list_shape, - ends_with_newline: tactic.ends_with_newline(context.config.indent_style()), - preserve_newline: true, - config: context.config, - }; - - let list_str = write_list(&item_vec, &fmt)?; - - let ty_shape = match context.config.indent_style() { - // 4 = " -> " - IndentStyle::Block => shape.offset_left(4)?, - IndentStyle::Visual => shape.block_left(4)?, - }; - let output = match *output { - FunctionRetTy::Ty(ref ty) => { - let type_str = ty.rewrite(context, ty_shape)?; - format!(" -> {}", type_str) - } - FunctionRetTy::Default(..) => String::new(), - }; - - let extendable = (!list_str.contains('\n') || list_str.is_empty()) && !output.contains('\n'); - let args = wrap_args_with_parens( - context, - &list_str, - extendable, - shape.sub_width(first_line_width(&output))?, - Shape::indented(offset, context.config), - ); - if last_line_width(&args) + first_line_width(&output) <= shape.width { - Some(format!("{}{}", args, output)) - } else { - Some(format!( - "{}\n{}{}", - args, - offset.to_string(context.config), - output.trim_left() - )) - } -} - -fn type_bound_colon(context: &RewriteContext) -> &'static str { - colon_spaces( - context.config.space_before_colon(), - context.config.space_after_colon(), - ) -} - -impl Rewrite for ast::WherePredicate { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - // TODO: dead spans? - let result = match *self { - ast::WherePredicate::BoundPredicate(ast::WhereBoundPredicate { - ref bound_generic_params, - ref bounded_ty, - ref bounds, - .. - }) => { - let type_str = bounded_ty.rewrite(context, shape)?; - - let colon = type_bound_colon(context); - - if let Some(lifetime_str) = - rewrite_lifetime_param(context, shape, bound_generic_params) - { - // 6 = "for<> ".len() - let used_width = lifetime_str.len() + type_str.len() + colon.len() + 6; - let ty_shape = shape.offset_left(used_width)?; - let bounds = bounds - .iter() - .map(|ty_bound| ty_bound.rewrite(context, ty_shape)) - .collect::<Option<Vec<_>>>()?; - let bounds_str = join_bounds(context, ty_shape, &bounds); - - if context.config.spaces_within_parens_and_brackets() - && !lifetime_str.is_empty() - { - format!( - "for< {} > {}{}{}", - lifetime_str, type_str, colon, bounds_str - ) - } else { - format!("for<{}> {}{}{}", lifetime_str, type_str, colon, bounds_str) - } - } else { - let used_width = type_str.len() + colon.len(); - let ty_shape = match context.config.indent_style() { - IndentStyle::Visual => shape.block_left(used_width)?, - IndentStyle::Block => shape, - }; - let bounds = bounds - .iter() - .map(|ty_bound| ty_bound.rewrite(context, ty_shape)) - .collect::<Option<Vec<_>>>()?; - let overhead = type_str.len() + colon.len(); - let bounds_str = join_bounds(context, ty_shape.sub_width(overhead)?, &bounds); - - format!("{}{}{}", type_str, colon, bounds_str) - } - } - ast::WherePredicate::RegionPredicate(ast::WhereRegionPredicate { - ref lifetime, - ref bounds, - .. - }) => rewrite_bounded_lifetime(lifetime, bounds.iter(), context, shape)?, - ast::WherePredicate::EqPredicate(ast::WhereEqPredicate { - ref lhs_ty, - ref rhs_ty, - .. - }) => { - let lhs_ty_str = lhs_ty.rewrite(context, shape)?; - // 3 = " = ".len() - let used_width = 3 + lhs_ty_str.len(); - let budget = shape.width.checked_sub(used_width)?; - let rhs_ty_str = - rhs_ty.rewrite(context, Shape::legacy(budget, shape.indent + used_width))?; - format!("{} = {}", lhs_ty_str, rhs_ty_str) - } - }; - - Some(result) - } -} - -impl Rewrite for ast::LifetimeDef { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - rewrite_bounded_lifetime(&self.lifetime, self.bounds.iter(), context, shape) - } -} - -fn rewrite_bounded_lifetime<'b, I>( - lt: &ast::Lifetime, - bounds: I, - context: &RewriteContext, - shape: Shape, -) -> Option<String> -where - I: ExactSizeIterator<Item = &'b ast::Lifetime>, -{ - let result = lt.rewrite(context, shape)?; - - if bounds.len() == 0 { - Some(result) - } else { - let appendix = bounds - .into_iter() - .map(|b| b.rewrite(context, shape)) - .collect::<Option<Vec<_>>>()?; - let colon = type_bound_colon(context); - let overhead = last_line_width(&result) + colon.len(); - let result = format!( - "{}{}{}", - result, - colon, - join_bounds(context, shape.sub_width(overhead)?, &appendix) - ); - Some(result) - } -} - -impl Rewrite for ast::TyParamBound { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - match *self { - ast::TyParamBound::TraitTyParamBound(ref tref, ast::TraitBoundModifier::None) => { - tref.rewrite(context, shape) - } - ast::TyParamBound::TraitTyParamBound(ref tref, ast::TraitBoundModifier::Maybe) => { - Some(format!( - "?{}", - tref.rewrite(context, shape.offset_left(1)?)? - )) - } - ast::TyParamBound::RegionTyParamBound(ref l) => l.rewrite(context, shape), - } - } -} - -impl Rewrite for ast::Lifetime { - fn rewrite(&self, _: &RewriteContext, _: Shape) -> Option<String> { - Some(pprust::lifetime_to_string(self)) - } -} - -impl Rewrite for ast::TyParamBounds { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - let strs = self.iter() - .map(|b| b.rewrite(context, shape)) - .collect::<Option<Vec<_>>>()?; - Some(join_bounds(context, shape, &strs)) - } -} - -impl Rewrite for ast::TyParam { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - let mut result = String::with_capacity(128); - // FIXME: If there are more than one attributes, this will force multiline. - match self.attrs.rewrite(context, shape) { - Some(ref rw) if !rw.is_empty() => result.push_str(&format!("{} ", rw)), - _ => (), - } - result.push_str(&self.ident.to_string()); - if !self.bounds.is_empty() { - result.push_str(type_bound_colon(context)); - let strs = self.bounds - .iter() - .map(|ty_bound| ty_bound.rewrite(context, shape)) - .collect::<Option<Vec<_>>>()?; - result.push_str(&join_bounds(context, shape, &strs)); - } - if let Some(ref def) = self.default { - let eq_str = match context.config.type_punctuation_density() { - TypeDensity::Compressed => "=", - TypeDensity::Wide => " = ", - }; - result.push_str(eq_str); - let budget = shape.width.checked_sub(result.len())?; - let rewrite = def.rewrite(context, Shape::legacy(budget, shape.indent + result.len()))?; - result.push_str(&rewrite); - } - - Some(result) - } -} - -impl Rewrite for ast::PolyTraitRef { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - if let Some(lifetime_str) = - rewrite_lifetime_param(context, shape, &self.bound_generic_params) - { - // 6 is "for<> ".len() - let extra_offset = lifetime_str.len() + 6; - let path_str = self.trait_ref - .rewrite(context, shape.offset_left(extra_offset)?)?; - - Some( - if context.config.spaces_within_parens_and_brackets() && !lifetime_str.is_empty() { - format!("for< {} > {}", lifetime_str, path_str) - } else { - format!("for<{}> {}", lifetime_str, path_str) - }, - ) - } else { - self.trait_ref.rewrite(context, shape) - } - } -} - -impl Rewrite for ast::TraitRef { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - rewrite_path(context, PathContext::Type, None, &self.path, shape) - } -} - -impl Rewrite for ast::Ty { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - match self.node { - ast::TyKind::TraitObject(ref bounds, ..) => bounds.rewrite(context, shape), - ast::TyKind::Ptr(ref mt) => { - let prefix = match mt.mutbl { - Mutability::Mutable => "*mut ", - Mutability::Immutable => "*const ", - }; - - rewrite_unary_prefix(context, prefix, &*mt.ty, shape) - } - ast::TyKind::Rptr(ref lifetime, ref mt) => { - let mut_str = format_mutability(mt.mutbl); - let mut_len = mut_str.len(); - Some(match *lifetime { - Some(ref lifetime) => { - let lt_budget = shape.width.checked_sub(2 + mut_len)?; - let lt_str = lifetime.rewrite( - context, - Shape::legacy(lt_budget, shape.indent + 2 + mut_len), - )?; - let lt_len = lt_str.len(); - let budget = shape.width.checked_sub(2 + mut_len + lt_len)?; - format!( - "&{} {}{}", - lt_str, - mut_str, - mt.ty.rewrite( - context, - Shape::legacy(budget, shape.indent + 2 + mut_len + lt_len) - )? - ) - } - None => { - let budget = shape.width.checked_sub(1 + mut_len)?; - format!( - "&{}{}", - mut_str, - mt.ty.rewrite( - context, - Shape::legacy(budget, shape.indent + 1 + mut_len) - )? - ) - } - }) - } - // FIXME: we drop any comments here, even though it's a silly place to put - // comments. - ast::TyKind::Paren(ref ty) => { - let budget = shape.width.checked_sub(2)?; - ty.rewrite(context, Shape::legacy(budget, shape.indent + 1)) - .map(|ty_str| { - if context.config.spaces_within_parens_and_brackets() { - format!("( {} )", ty_str) - } else { - format!("({})", ty_str) - } - }) - } - ast::TyKind::Slice(ref ty) => { - let budget = if context.config.spaces_within_parens_and_brackets() { - shape.width.checked_sub(4)? - } else { - shape.width.checked_sub(2)? - }; - ty.rewrite(context, Shape::legacy(budget, shape.indent + 1)) - .map(|ty_str| { - if context.config.spaces_within_parens_and_brackets() { - format!("[ {} ]", ty_str) - } else { - format!("[{}]", ty_str) - } - }) - } - ast::TyKind::Tup(ref items) => rewrite_tuple( - context, - &::utils::ptr_vec_to_ref_vec(items), - self.span, - shape, - ), - ast::TyKind::Path(ref q_self, ref path) => { - rewrite_path(context, PathContext::Type, q_self.as_ref(), path, shape) - } - ast::TyKind::Array(ref ty, ref repeats) => { - let use_spaces = context.config.spaces_within_parens_and_brackets(); - let lbr = if use_spaces { "[ " } else { "[" }; - let rbr = if use_spaces { " ]" } else { "]" }; - rewrite_pair( - &**ty, - &**repeats, - PairParts::new(lbr, "; ", rbr), - context, - shape, - SeparatorPlace::Back, - ) - } - ast::TyKind::Infer => { - if shape.width >= 1 { - Some("_".to_owned()) - } else { - None - } - } - ast::TyKind::BareFn(ref bare_fn) => rewrite_bare_fn(bare_fn, self.span, context, shape), - ast::TyKind::Never => Some(String::from("!")), - ast::TyKind::Mac(ref mac) => { - rewrite_macro(mac, None, context, shape, MacroPosition::Expression) - } - ast::TyKind::ImplicitSelf => Some(String::from("")), - ast::TyKind::ImplTrait(ref it) => it.rewrite(context, shape) - .map(|it_str| format!("impl {}", it_str)), - ast::TyKind::Err | ast::TyKind::Typeof(..) => unreachable!(), - } - } -} - -fn rewrite_bare_fn( - bare_fn: &ast::BareFnTy, - span: Span, - context: &RewriteContext, - shape: Shape, -) -> Option<String> { - let mut result = String::with_capacity(128); - - if let Some(ref lifetime_str) = rewrite_lifetime_param(context, shape, &bare_fn.generic_params) - { - result.push_str("for<"); - // 6 = "for<> ".len(), 4 = "for<". - // This doesn't work out so nicely for mutliline situation with lots of - // rightward drift. If that is a problem, we could use the list stuff. - result.push_str(lifetime_str); - result.push_str("> "); - } - - result.push_str(::utils::format_unsafety(bare_fn.unsafety)); - - result.push_str(&format_abi( - bare_fn.abi, - context.config.force_explicit_abi(), - false, - )); - - result.push_str("fn"); - - let func_ty_shape = shape.offset_left(result.len())?; - - let rewrite = format_function_type( - bare_fn.decl.inputs.iter(), - &bare_fn.decl.output, - bare_fn.decl.variadic, - span, - context, - func_ty_shape, - )?; - - result.push_str(&rewrite); - - Some(result) -} - -pub fn join_bounds(context: &RewriteContext, shape: Shape, type_strs: &[String]) -> String { - // Try to join types in a single line - let joiner = match context.config.type_punctuation_density() { - TypeDensity::Compressed => "+", - TypeDensity::Wide => " + ", - }; - let result = type_strs.join(joiner); - if result.contains('\n') || result.len() > shape.width { - let joiner_indent = shape.indent.block_indent(context.config); - let joiner = format!("\n{}+ ", joiner_indent.to_string(context.config)); - type_strs.join(&joiner) - } else { - result - } -} - -pub fn can_be_overflowed_type(context: &RewriteContext, ty: &ast::Ty, len: usize) -> bool { - match ty.node { - ast::TyKind::Path(..) | ast::TyKind::Tup(..) => context.use_block_indent() && len == 1, - ast::TyKind::Rptr(_, ref mutty) | ast::TyKind::Ptr(ref mutty) => { - can_be_overflowed_type(context, &*mutty.ty, len) - } - _ => false, - } -} - -/// Returns `None` if there is no `LifetimeDef` in the given generic parameters. -fn rewrite_lifetime_param( - context: &RewriteContext, - shape: Shape, - generic_params: &[ast::GenericParam], -) -> Option<String> { - let result = generic_params - .iter() - .filter(|p| p.is_lifetime_param()) - .map(|lt| lt.rewrite(context, shape)) - .collect::<Option<Vec<_>>>()? - .join(", "); - if result.is_empty() { - None - } else { - Some(result) - } -} diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index 20f4fdfe6d0..00000000000 --- a/src/utils.rs +++ /dev/null @@ -1,480 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::borrow::Cow; - -use syntax::{abi, ptr}; -use syntax::ast::{self, Attribute, CrateSugar, MetaItem, MetaItemKind, NestedMetaItem, - NestedMetaItemKind, Path, Visibility}; -use syntax::codemap::{BytePos, Span, NO_EXPANSION}; - -use config::Color; -use rewrite::RewriteContext; -use shape::Shape; - -// When we get scoped annotations, we should have rustfmt::skip. -const SKIP_ANNOTATION: &str = "rustfmt_skip"; - -// Computes the length of a string's last line, minus offset. -pub fn extra_offset(text: &str, shape: Shape) -> usize { - match text.rfind('\n') { - // 1 for newline character - Some(idx) => text.len() - .checked_sub(idx + 1 + shape.used_width()) - .unwrap_or(0), - None => text.len(), - } -} - -// Uses Cow to avoid allocating in the common cases. -pub fn format_visibility(vis: &Visibility) -> Cow<'static, str> { - match *vis { - Visibility::Public => Cow::from("pub "), - Visibility::Inherited => Cow::from(""), - Visibility::Crate(_, CrateSugar::PubCrate) => Cow::from("pub(crate) "), - Visibility::Crate(_, CrateSugar::JustCrate) => Cow::from("crate "), - Visibility::Restricted { ref path, .. } => { - let Path { ref segments, .. } = **path; - let mut segments_iter = segments.iter().map(|seg| seg.identifier.name.to_string()); - if path.is_global() { - segments_iter - .next() - .expect("Non-global path in pub(restricted)?"); - } - let is_keyword = |s: &str| s == "self" || s == "super"; - let path = segments_iter.collect::<Vec<_>>().join("::"); - let in_str = if is_keyword(&path) { "" } else { "in " }; - - Cow::from(format!("pub({}{}) ", in_str, path)) - } - } -} - -#[inline] -pub fn format_constness(constness: ast::Constness) -> &'static str { - match constness { - ast::Constness::Const => "const ", - ast::Constness::NotConst => "", - } -} - -#[inline] -pub fn format_defaultness(defaultness: ast::Defaultness) -> &'static str { - match defaultness { - ast::Defaultness::Default => "default ", - ast::Defaultness::Final => "", - } -} - -#[inline] -pub fn format_unsafety(unsafety: ast::Unsafety) -> &'static str { - match unsafety { - ast::Unsafety::Unsafe => "unsafe ", - ast::Unsafety::Normal => "", - } -} - -#[inline] -pub fn format_mutability(mutability: ast::Mutability) -> &'static str { - match mutability { - ast::Mutability::Mutable => "mut ", - ast::Mutability::Immutable => "", - } -} - -#[inline] -pub fn format_abi(abi: abi::Abi, explicit_abi: bool, is_mod: bool) -> Cow<'static, str> { - if abi == abi::Abi::Rust && !is_mod { - Cow::from("") - } else if abi == abi::Abi::C && !explicit_abi { - Cow::from("extern ") - } else { - Cow::from(format!("extern {} ", abi)) - } -} - -#[inline] -// Transform `Vec<syntax::ptr::P<T>>` into `Vec<&T>` -pub fn ptr_vec_to_ref_vec<T>(vec: &[ptr::P<T>]) -> Vec<&T> { - vec.iter().map(|x| &**x).collect::<Vec<_>>() -} - -#[inline] -pub fn filter_attributes(attrs: &[ast::Attribute], style: ast::AttrStyle) -> Vec<ast::Attribute> { - attrs - .iter() - .filter(|a| a.style == style) - .cloned() - .collect::<Vec<_>>() -} - -#[inline] -pub fn inner_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> { - filter_attributes(attrs, ast::AttrStyle::Inner) -} - -#[inline] -pub fn outer_attributes(attrs: &[ast::Attribute]) -> Vec<ast::Attribute> { - filter_attributes(attrs, ast::AttrStyle::Outer) -} - -#[inline] -pub fn last_line_contains_single_line_comment(s: &str) -> bool { - s.lines().last().map_or(false, |l| l.contains("//")) -} - -#[inline] -pub fn is_attributes_extendable(attrs_str: &str) -> bool { - !attrs_str.contains('\n') && !last_line_contains_single_line_comment(attrs_str) -} - -// The width of the first line in s. -#[inline] -pub fn first_line_width(s: &str) -> usize { - match s.find('\n') { - Some(n) => n, - None => s.len(), - } -} - -// The width of the last line in s. -#[inline] -pub fn last_line_width(s: &str) -> usize { - match s.rfind('\n') { - Some(n) => s.len() - n - 1, - None => s.len(), - } -} - -// The total used width of the last line. -#[inline] -pub fn last_line_used_width(s: &str, offset: usize) -> usize { - if s.contains('\n') { - last_line_width(s) - } else { - offset + s.len() - } -} - -#[inline] -pub fn trimmed_last_line_width(s: &str) -> usize { - match s.rfind('\n') { - Some(n) => s[(n + 1)..].trim().len(), - None => s.trim().len(), - } -} - -#[inline] -pub fn last_line_extendable(s: &str) -> bool { - if s.ends_with("\"#") { - return true; - } - for c in s.chars().rev() { - match c { - '(' | ')' | ']' | '}' | '?' | '>' => continue, - '\n' => break, - _ if c.is_whitespace() => continue, - _ => return false, - } - } - true -} - -#[inline] -fn is_skip(meta_item: &MetaItem) -> bool { - match meta_item.node { - MetaItemKind::Word => meta_item.name == SKIP_ANNOTATION, - MetaItemKind::List(ref l) => { - meta_item.name == "cfg_attr" && l.len() == 2 && is_skip_nested(&l[1]) - } - _ => false, - } -} - -#[inline] -fn is_skip_nested(meta_item: &NestedMetaItem) -> bool { - match meta_item.node { - NestedMetaItemKind::MetaItem(ref mi) => is_skip(mi), - NestedMetaItemKind::Literal(_) => false, - } -} - -#[inline] -pub fn contains_skip(attrs: &[Attribute]) -> bool { - attrs - .iter() - .any(|a| a.meta().map_or(false, |a| is_skip(&a))) -} - -#[inline] -pub fn semicolon_for_expr(context: &RewriteContext, expr: &ast::Expr) -> bool { - match expr.node { - ast::ExprKind::Ret(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Break(..) => { - context.config.trailing_semicolon() - } - _ => false, - } -} - -#[inline] -pub fn semicolon_for_stmt(context: &RewriteContext, stmt: &ast::Stmt) -> bool { - match stmt.node { - ast::StmtKind::Semi(ref expr) => match expr.node { - ast::ExprKind::While(..) - | ast::ExprKind::WhileLet(..) - | ast::ExprKind::Loop(..) - | ast::ExprKind::ForLoop(..) => false, - ast::ExprKind::Break(..) | ast::ExprKind::Continue(..) | ast::ExprKind::Ret(..) => { - context.config.trailing_semicolon() - } - _ => true, - }, - ast::StmtKind::Expr(..) => false, - _ => true, - } -} - -#[inline] -pub fn stmt_expr(stmt: &ast::Stmt) -> Option<&ast::Expr> { - match stmt.node { - ast::StmtKind::Expr(ref expr) => Some(expr), - _ => None, - } -} - -#[inline] -pub fn count_newlines(input: &str) -> usize { - input.chars().filter(|&c| c == '\n').count() -} - -// Macro for deriving implementations of Serialize/Deserialize for enums -#[macro_export] -macro_rules! impl_enum_serialize_and_deserialize { - ( $e:ident, $( $x:ident ),* ) => { - impl ::serde::ser::Serialize for $e { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where S: ::serde::ser::Serializer - { - use serde::ser::Error; - - // We don't know whether the user of the macro has given us all options. - #[allow(unreachable_patterns)] - match *self { - $( - $e::$x => serializer.serialize_str(stringify!($x)), - )* - _ => { - Err(S::Error::custom(format!("Cannot serialize {:?}", self))) - } - } - } - } - - impl<'de> ::serde::de::Deserialize<'de> for $e { - fn deserialize<D>(d: D) -> Result<Self, D::Error> - where D: ::serde::Deserializer<'de> { - use serde::de::{Error, Visitor}; - use std::marker::PhantomData; - use std::fmt; - struct StringOnly<T>(PhantomData<T>); - impl<'de, T> Visitor<'de> for StringOnly<T> - where T: ::serde::Deserializer<'de> { - type Value = String; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("string") - } - fn visit_str<E>(self, value: &str) -> Result<String, E> { - Ok(String::from(value)) - } - } - let s = d.deserialize_string(StringOnly::<D>(PhantomData))?; - $( - if stringify!($x).eq_ignore_ascii_case(&s) { - return Ok($e::$x); - } - )* - static ALLOWED: &'static[&str] = &[$(stringify!($x),)*]; - Err(D::Error::unknown_variant(&s, ALLOWED)) - } - } - - impl ::std::str::FromStr for $e { - type Err = &'static str; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - $( - if stringify!($x).eq_ignore_ascii_case(s) { - return Ok($e::$x); - } - )* - Err("Bad variant") - } - } - - impl ::config::ConfigType for $e { - fn doc_hint() -> String { - let mut variants = Vec::new(); - $( - variants.push(stringify!($x)); - )* - format!("[{}]", variants.join("|")) - } - } - }; -} - -macro_rules! msg { - ($($arg:tt)*) => ( - match writeln!(&mut ::std::io::stderr(), $($arg)* ) { - Ok(_) => {}, - Err(x) => panic!("Unable to write to stderr: {}", x), - } - ) -} - -// For format_missing and last_pos, need to use the source callsite (if applicable). -// Required as generated code spans aren't guaranteed to follow on from the last span. -macro_rules! source { - ($this: ident, $sp: expr) => { - $sp.source_callsite() - }; -} - -pub fn mk_sp(lo: BytePos, hi: BytePos) -> Span { - Span::new(lo, hi, NO_EXPANSION) -} - -// Return true if the given span does not intersect with file lines. -macro_rules! out_of_file_lines_range { - ($self: ident, $span: expr) => { - !$self - .config - .file_lines() - .intersects(&$self.codemap.lookup_line_range($span)) - }; -} - -macro_rules! skip_out_of_file_lines_range { - ($self: ident, $span: expr) => { - if out_of_file_lines_range!($self, $span) { - return None; - } - }; -} - -macro_rules! skip_out_of_file_lines_range_visitor { - ($self: ident, $span: expr) => { - if out_of_file_lines_range!($self, $span) { - $self.push_rewrite($span, None); - return; - } - }; -} - -// Wraps String in an Option. Returns Some when the string adheres to the -// Rewrite constraints defined for the Rewrite trait and else otherwise. -pub fn wrap_str(s: String, max_width: usize, shape: Shape) -> Option<String> { - if is_valid_str(&s, max_width, shape) { - Some(s) - } else { - None - } -} - -fn is_valid_str(snippet: &str, max_width: usize, shape: Shape) -> bool { - if !snippet.is_empty() { - // First line must fits with `shape.width`. - if first_line_width(snippet) > shape.width { - return false; - } - // If the snippet does not include newline, we are done. - if first_line_width(snippet) == snippet.len() { - return true; - } - // The other lines must fit within the maximum width. - if snippet.lines().skip(1).any(|line| line.len() > max_width) { - return false; - } - // A special check for the last line, since the caller may - // place trailing characters on this line. - if last_line_width(snippet) > shape.used_width() + shape.width { - return false; - } - } - true -} - -#[inline] -pub fn colon_spaces(before: bool, after: bool) -> &'static str { - match (before, after) { - (true, true) => " : ", - (true, false) => " :", - (false, true) => ": ", - (false, false) => ":", - } -} - -#[inline] -pub fn paren_overhead(context: &RewriteContext) -> usize { - if context.config.spaces_within_parens_and_brackets() { - 4 - } else { - 2 - } -} - -pub fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr { - match e.node { - ast::ExprKind::InPlace(ref e, _) - | ast::ExprKind::Call(ref e, _) - | ast::ExprKind::Binary(_, ref e, _) - | ast::ExprKind::Cast(ref e, _) - | ast::ExprKind::Type(ref e, _) - | ast::ExprKind::Assign(ref e, _) - | ast::ExprKind::AssignOp(_, ref e, _) - | ast::ExprKind::Field(ref e, _) - | ast::ExprKind::TupField(ref e, _) - | ast::ExprKind::Index(ref e, _) - | ast::ExprKind::Range(Some(ref e), _, _) - | ast::ExprKind::Try(ref e) => left_most_sub_expr(e), - _ => 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 - } -} - -pub fn use_colored_tty(color: Color) -> bool { - match color { - Color::Always => true, - Color::Never => false, - Color::Auto => isatty(), - } -} - -pub fn starts_with_newline(s: &str) -> bool { - s.starts_with('\n') || s.starts_with("\r\n") -} diff --git a/src/vertical.rs b/src/vertical.rs deleted file mode 100644 index 2ccb5d81339..00000000000 --- a/src/vertical.rs +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright 2017 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -// Format with vertical alignment. - -use std::cmp; - -use syntax::ast; -use syntax::codemap::{BytePos, Span}; - -use codemap::SpanUtils; -use comment::{combine_strs_with_missing_comments, contains_comment}; -use expr::rewrite_field; -use items::{rewrite_struct_field, rewrite_struct_field_prefix}; -use lists::{definitive_tactic, itemize_list, write_list, ListFormatting, ListTactic, Separator, - SeparatorPlace}; -use rewrite::{Rewrite, RewriteContext}; -use shape::{Indent, Shape}; -use spanned::Spanned; -use utils::{contains_skip, is_attributes_extendable, mk_sp}; - -pub trait AlignedItem { - fn skip(&self) -> bool; - fn get_span(&self) -> Span; - fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String>; - fn rewrite_aligned_item( - &self, - context: &RewriteContext, - shape: Shape, - prefix_max_width: usize, - ) -> Option<String>; -} - -impl AlignedItem for ast::StructField { - fn skip(&self) -> bool { - contains_skip(&self.attrs) - } - - fn get_span(&self) -> Span { - self.span() - } - - fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - let attrs_str = self.attrs.rewrite(context, shape)?; - let missing_span = if self.attrs.is_empty() { - mk_sp(self.span.lo(), self.span.lo()) - } else { - mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo()) - }; - let attrs_extendable = self.ident.is_none() && is_attributes_extendable(&attrs_str); - rewrite_struct_field_prefix(context, self).and_then(|field_str| { - combine_strs_with_missing_comments( - context, - &attrs_str, - &field_str, - missing_span, - shape, - attrs_extendable, - ) - }) - } - - fn rewrite_aligned_item( - &self, - context: &RewriteContext, - shape: Shape, - prefix_max_width: usize, - ) -> Option<String> { - rewrite_struct_field(context, self, shape, prefix_max_width) - } -} - -impl AlignedItem for ast::Field { - fn skip(&self) -> bool { - contains_skip(&self.attrs) - } - - fn get_span(&self) -> Span { - self.span() - } - - fn rewrite_prefix(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - let attrs_str = self.attrs.rewrite(context, shape)?; - let name = &self.ident.node.to_string(); - let missing_span = if self.attrs.is_empty() { - mk_sp(self.span.lo(), self.span.lo()) - } else { - mk_sp(self.attrs.last().unwrap().span.hi(), self.span.lo()) - }; - combine_strs_with_missing_comments( - context, - &attrs_str, - name, - missing_span, - shape, - is_attributes_extendable(&attrs_str), - ) - } - - fn rewrite_aligned_item( - &self, - context: &RewriteContext, - shape: Shape, - prefix_max_width: usize, - ) -> Option<String> { - rewrite_field(context, self, shape, prefix_max_width) - } -} - -pub fn rewrite_with_alignment<T: AlignedItem>( - fields: &[T], - context: &RewriteContext, - shape: Shape, - span: Span, - one_line_width: usize, -) -> Option<String> { - let (spaces, group_index) = if context.config.struct_field_align_threshold() > 0 { - group_aligned_items(context, fields) - } else { - ("", fields.len() - 1) - }; - let init = &fields[0..group_index + 1]; - let rest = &fields[group_index + 1..]; - let init_last_pos = if rest.is_empty() { - span.hi() - } else { - // Decide whether the missing comments should stick to init or rest. - let init_hi = init[init.len() - 1].get_span().hi(); - let rest_lo = rest[0].get_span().lo(); - let missing_span = mk_sp(init_hi, rest_lo); - let missing_span = mk_sp( - context.codemap.span_after(missing_span, ","), - missing_span.hi(), - ); - - let snippet = context.snippet(missing_span); - if snippet.trim_left().starts_with("//") { - let offset = snippet.lines().next().map_or(0, |l| l.len()); - // 2 = "," + "\n" - init_hi + BytePos(offset as u32 + 2) - } else if snippet.trim_left().starts_with("/*") { - let comment_lines = snippet - .lines() - .position(|line| line.trim_right().ends_with("*/")) - .unwrap_or(0); - - let offset = snippet - .lines() - .take(comment_lines + 1) - .collect::<Vec<_>>() - .join("\n") - .len(); - - init_hi + BytePos(offset as u32 + 2) - } else { - missing_span.lo() - } - }; - let init_span = mk_sp(span.lo(), init_last_pos); - let one_line_width = if rest.is_empty() { one_line_width } else { 0 }; - let result = - rewrite_aligned_items_inner(context, init, init_span, shape.indent, one_line_width)?; - if rest.is_empty() { - Some(result + spaces) - } else { - let rest_span = mk_sp(init_last_pos, span.hi()); - let rest_str = rewrite_with_alignment(rest, context, shape, rest_span, one_line_width)?; - Some( - result + spaces + "\n" - + &shape - .indent - .block_indent(context.config) - .to_string(context.config) + &rest_str, - ) - } -} - -fn struct_field_prefix_max_min_width<T: AlignedItem>( - context: &RewriteContext, - fields: &[T], - shape: Shape, -) -> (usize, usize) { - fields - .iter() - .map(|field| { - field.rewrite_prefix(context, shape).and_then(|field_str| { - if field_str.contains('\n') { - None - } else { - Some(field_str.len()) - } - }) - }) - .fold(Some((0, ::std::usize::MAX)), |acc, len| match (acc, len) { - (Some((max_len, min_len)), Some(len)) => { - Some((cmp::max(max_len, len), cmp::min(min_len, len))) - } - _ => None, - }) - .unwrap_or((0, 0)) -} - -fn rewrite_aligned_items_inner<T: AlignedItem>( - context: &RewriteContext, - fields: &[T], - span: Span, - offset: Indent, - one_line_width: usize, -) -> Option<String> { - let item_indent = offset.block_indent(context.config); - // 1 = "," - let item_shape = Shape::indented(item_indent, context.config).sub_width(1)?; - let (mut field_prefix_max_width, field_prefix_min_width) = - struct_field_prefix_max_min_width(context, fields, item_shape); - let max_diff = field_prefix_max_width - .checked_sub(field_prefix_min_width) - .unwrap_or(0); - if max_diff > context.config.struct_field_align_threshold() { - field_prefix_max_width = 0; - } - - let items = itemize_list( - context.codemap, - fields.iter(), - "}", - ",", - |field| field.get_span().lo(), - |field| field.get_span().hi(), - |field| field.rewrite_aligned_item(context, item_shape, field_prefix_max_width), - span.lo(), - span.hi(), - false, - ).collect::<Vec<_>>(); - - let tactic = definitive_tactic( - &items, - ListTactic::HorizontalVertical, - Separator::Comma, - one_line_width, - ); - - let fmt = ListFormatting { - tactic, - separator: ",", - trailing_separator: context.config.trailing_comma(), - separator_place: SeparatorPlace::Back, - shape: item_shape, - ends_with_newline: true, - preserve_newline: true, - config: context.config, - }; - write_list(&items, &fmt) -} - -fn group_aligned_items<T: AlignedItem>( - context: &RewriteContext, - fields: &[T], -) -> (&'static str, usize) { - let mut index = 0; - for i in 0..fields.len() - 1 { - if fields[i].skip() { - return ("", index); - } - // See if there are comments or empty lines between fields. - let span = mk_sp(fields[i].get_span().hi(), fields[i + 1].get_span().lo()); - let snippet = context - .snippet(span) - .lines() - .skip(1) - .collect::<Vec<_>>() - .join("\n"); - let spacings = if snippet.lines().rev().skip(1).any(|l| l.trim().is_empty()) { - "\n" - } else { - "" - }; - if contains_comment(&snippet) || snippet.lines().count() > 1 { - return (spacings, index); - } - index += 1; - } - ("", index) -} diff --git a/src/visitor.rs b/src/visitor.rs deleted file mode 100644 index cd6c0740ee6..00000000000 --- a/src/visitor.rs +++ /dev/null @@ -1,1113 +0,0 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::cmp; - -use syntax::{ast, visit}; -use syntax::attr::{self, HasAttrs}; -use syntax::codemap::{self, BytePos, CodeMap, Pos, Span}; -use syntax::parse::ParseSess; - -use codemap::{LineRangeUtils, SpanUtils}; -use comment::{combine_strs_with_missing_comments, contains_comment, CodeCharKind, - CommentCodeSlices, FindUncommented}; -use comment::rewrite_comment; -use config::{BraceStyle, Config}; -use expr::rewrite_literal; -use items::{format_impl, format_trait, format_trait_alias, rewrite_associated_impl_type, - rewrite_associated_type, rewrite_type_alias, FnSig, StaticParts, StructParts}; -use lists::{itemize_list, write_list, DefinitiveListTactic, ListFormatting, SeparatorPlace, - SeparatorTactic}; -use macros::{rewrite_macro, rewrite_macro_def, MacroPosition}; -use regex::Regex; -use rewrite::{Rewrite, RewriteContext}; -use shape::{Indent, Shape}; -use spanned::Spanned; -use utils::{self, contains_skip, count_newlines, inner_attributes, mk_sp, ptr_vec_to_ref_vec}; - -/// Returns attributes that are within `outer_span`. -pub fn filter_inline_attrs(attrs: &[ast::Attribute], outer_span: Span) -> Vec<ast::Attribute> { - attrs - .iter() - .filter(|a| outer_span.lo() <= a.span.lo() && a.span.hi() <= outer_span.hi()) - .cloned() - .collect() -} - -/// Returns true for `mod foo;`, false for `mod foo { .. }`. -fn is_mod_decl(item: &ast::Item) -> bool { - match item.node { - ast::ItemKind::Mod(ref m) => m.inner.hi() != item.span.hi(), - _ => false, - } -} - -fn contains_macro_use_attr(attrs: &[ast::Attribute], span: Span) -> bool { - attr::contains_name(&filter_inline_attrs(attrs, span), "macro_use") -} - -/// Returns true for `mod foo;` without any inline attributes. -/// We cannot reorder modules with attributes because doing so can break the code. -/// e.g. `#[macro_use]`. -fn is_mod_decl_without_attr(item: &ast::Item) -> bool { - is_mod_decl(item) && !contains_macro_use_attr(&item.attrs, item.span()) -} - -fn is_use_item(item: &ast::Item) -> bool { - match item.node { - ast::ItemKind::Use(_) => true, - _ => false, - } -} - -fn is_use_item_without_attr(item: &ast::Item) -> bool { - is_use_item(item) && !contains_macro_use_attr(&item.attrs, item.span()) -} - -fn is_extern_crate(item: &ast::Item) -> bool { - match item.node { - ast::ItemKind::ExternCrate(..) => true, - _ => false, - } -} - -fn is_extern_crate_without_attr(item: &ast::Item) -> bool { - is_extern_crate(item) && !contains_macro_use_attr(&item.attrs, item.span()) -} - -/// Creates a string slice corresponding to the specified span. -pub struct SnippetProvider<'a> { - /// A pointer to the content of the file we are formatting. - big_snippet: &'a str, - /// A position of the start of `big_snippet`, used as an offset. - start_pos: usize, -} - -impl<'a> SnippetProvider<'a> { - pub fn span_to_snippet(&self, span: Span) -> Option<&str> { - let start_index = span.lo().to_usize().checked_sub(self.start_pos)?; - let end_index = span.hi().to_usize().checked_sub(self.start_pos)?; - Some(&self.big_snippet[start_index..end_index]) - } - - pub fn new(start_pos: BytePos, big_snippet: &'a str) -> Self { - let start_pos = start_pos.to_usize(); - SnippetProvider { - big_snippet, - start_pos, - } - } -} - -pub struct FmtVisitor<'a> { - pub parse_session: &'a ParseSess, - pub codemap: &'a CodeMap, - pub buffer: String, - pub last_pos: BytePos, - // FIXME: use an RAII util or closure for indenting - pub block_indent: Indent, - pub config: &'a Config, - pub is_if_else_block: bool, - pub snippet_provider: &'a SnippetProvider<'a>, - pub line_number: usize, - pub skipped_range: Vec<(usize, usize)>, -} - -impl<'b, 'a: 'b> FmtVisitor<'a> { - pub fn shape(&self) -> Shape { - Shape::indented(self.block_indent, self.config) - } - - fn visit_stmt(&mut self, stmt: &ast::Stmt) { - debug!( - "visit_stmt: {:?} {:?}", - self.codemap.lookup_char_pos(stmt.span.lo()), - self.codemap.lookup_char_pos(stmt.span.hi()) - ); - - match stmt.node { - ast::StmtKind::Item(ref item) => { - self.visit_item(item); - } - ast::StmtKind::Local(..) | ast::StmtKind::Expr(..) | ast::StmtKind::Semi(..) => { - if contains_skip(get_attrs_from_stmt(stmt)) { - self.push_skipped_with_span(stmt.span()); - } else { - let rewrite = stmt.rewrite(&self.get_context(), self.shape()); - self.push_rewrite(stmt.span(), rewrite) - } - } - ast::StmtKind::Mac(ref mac) => { - let (ref mac, _macro_style, ref attrs) = **mac; - if self.visit_attrs(attrs, ast::AttrStyle::Outer) { - self.push_skipped_with_span(stmt.span()); - } else { - self.visit_mac(mac, None, MacroPosition::Statement); - } - self.format_missing(stmt.span.hi()); - } - } - } - - pub fn visit_block( - &mut self, - b: &ast::Block, - inner_attrs: Option<&[ast::Attribute]>, - has_braces: bool, - ) { - debug!( - "visit_block: {:?} {:?}", - self.codemap.lookup_char_pos(b.span.lo()), - self.codemap.lookup_char_pos(b.span.hi()) - ); - - // Check if this block has braces. - let brace_compensation = BytePos(if has_braces { 1 } else { 0 }); - - self.last_pos = self.last_pos + brace_compensation; - self.block_indent = self.block_indent.block_indent(self.config); - self.push_str("{"); - - if self.config.remove_blank_lines_at_start_or_end_of_block() { - if let Some(first_stmt) = b.stmts.first() { - let attr_lo = inner_attrs - .and_then(|attrs| inner_attributes(attrs).first().map(|attr| attr.span.lo())) - .or_else(|| { - // Attributes for an item in a statement position - // do not belong to the statement. (rust-lang/rust#34459) - if let ast::StmtKind::Item(ref item) = first_stmt.node { - item.attrs.first() - } else { - first_stmt.attrs().first() - }.and_then(|attr| { - // Some stmts can have embedded attributes. - // e.g. `match { #![attr] ... }` - let attr_lo = attr.span.lo(); - if attr_lo < first_stmt.span.lo() { - Some(attr_lo) - } else { - None - } - }) - }); - - let snippet = self.snippet(mk_sp( - self.last_pos, - attr_lo.unwrap_or(first_stmt.span.lo()), - )); - let len = CommentCodeSlices::new(snippet) - .nth(0) - .and_then(|(kind, _, s)| { - if kind == CodeCharKind::Normal { - s.rfind('\n') - } else { - None - } - }); - if let Some(len) = len { - self.last_pos = self.last_pos + BytePos::from_usize(len); - } - } - } - - // Format inner attributes if available. - let skip_rewrite = if let Some(attrs) = inner_attrs { - self.visit_attrs(attrs, ast::AttrStyle::Inner) - } else { - false - }; - - if skip_rewrite { - self.push_rewrite(b.span, None); - self.close_block(false); - self.last_pos = source!(self, b.span).hi(); - return; - } - - self.walk_block_stmts(b); - - if !b.stmts.is_empty() { - if let Some(expr) = utils::stmt_expr(&b.stmts[b.stmts.len() - 1]) { - if utils::semicolon_for_expr(&self.get_context(), expr) { - self.push_str(";"); - } - } - } - - let mut remove_len = BytePos(0); - if self.config.remove_blank_lines_at_start_or_end_of_block() { - if let Some(stmt) = b.stmts.last() { - let snippet = self.snippet(mk_sp( - stmt.span.hi(), - source!(self, b.span).hi() - brace_compensation, - )); - let len = CommentCodeSlices::new(snippet) - .last() - .and_then(|(kind, _, s)| { - if kind == CodeCharKind::Normal && s.trim().is_empty() { - Some(s.len()) - } else { - None - } - }); - if let Some(len) = len { - remove_len = BytePos::from_usize(len); - } - } - } - - let unindent_comment = (self.is_if_else_block && !b.stmts.is_empty()) && { - let end_pos = source!(self, b.span).hi() - brace_compensation - remove_len; - let snippet = self.snippet(mk_sp(self.last_pos, end_pos)); - snippet.contains("//") || snippet.contains("/*") - }; - // FIXME: we should compress any newlines here to just one - if unindent_comment { - self.block_indent = self.block_indent.block_unindent(self.config); - } - self.format_missing_with_indent( - source!(self, b.span).hi() - brace_compensation - remove_len, - ); - if unindent_comment { - self.block_indent = self.block_indent.block_indent(self.config); - } - self.close_block(unindent_comment); - self.last_pos = source!(self, b.span).hi(); - } - - // FIXME: this is a terrible hack to indent the comments between the last - // item in the block and the closing brace to the block's level. - // The closing brace itself, however, should be indented at a shallower - // level. - fn close_block(&mut self, unindent_comment: bool) { - let total_len = self.buffer.len(); - let chars_too_many = if unindent_comment { - 0 - } else if self.config.hard_tabs() { - 1 - } else { - self.config.tab_spaces() - }; - self.buffer.truncate(total_len - chars_too_many); - self.push_str("}"); - self.block_indent = self.block_indent.block_unindent(self.config); - } - - // Note that this only gets called for function definitions. Required methods - // on traits do not get handled here. - fn visit_fn( - &mut self, - fk: visit::FnKind, - generics: &ast::Generics, - fd: &ast::FnDecl, - s: Span, - defaultness: ast::Defaultness, - inner_attrs: Option<&[ast::Attribute]>, - ) { - let indent = self.block_indent; - let block; - let rewrite = match fk { - visit::FnKind::ItemFn(ident, _, _, _, _, b) | visit::FnKind::Method(ident, _, _, b) => { - block = b; - self.rewrite_fn( - indent, - ident, - &FnSig::from_fn_kind(&fk, generics, fd, defaultness), - mk_sp(s.lo(), b.span.lo()), - b, - ) - } - visit::FnKind::Closure(_) => unreachable!(), - }; - - if let Some(fn_str) = rewrite { - self.format_missing_with_indent(source!(self, s).lo()); - self.push_str(&fn_str); - if let Some(c) = fn_str.chars().last() { - if c == '}' { - self.last_pos = source!(self, block.span).hi(); - return; - } - } - } else { - self.format_missing(source!(self, block.span).lo()); - } - - self.last_pos = source!(self, block.span).lo(); - self.visit_block(block, inner_attrs, true) - } - - pub fn visit_item(&mut self, item: &ast::Item) { - skip_out_of_file_lines_range_visitor!(self, item.span); - - // This is where we bail out if there is a skip attribute. This is only - // complex in the module case. It is complex because the module could be - // in a separate file and there might be attributes in both files, but - // the AST lumps them all together. - let filtered_attrs; - let mut attrs = &item.attrs; - match item.node { - // Module is inline, in this case we treat it like any other item. - _ if !is_mod_decl(item) => { - if self.visit_attrs(&item.attrs, ast::AttrStyle::Outer) { - self.push_skipped_with_span(item.span()); - return; - } - } - // Module is not inline, but should be skipped. - ast::ItemKind::Mod(..) if contains_skip(&item.attrs) => { - return; - } - // Module is not inline and should not be skipped. We want - // to process only the attributes in the current file. - ast::ItemKind::Mod(..) => { - filtered_attrs = filter_inline_attrs(&item.attrs, item.span()); - // Assert because if we should skip it should be caught by - // the above case. - assert!(!self.visit_attrs(&filtered_attrs, ast::AttrStyle::Outer)); - attrs = &filtered_attrs; - } - _ => { - if self.visit_attrs(&item.attrs, ast::AttrStyle::Outer) { - self.push_skipped_with_span(item.span()); - return; - } - } - } - - match item.node { - ast::ItemKind::Use(ref tree) => self.format_import(item, tree), - ast::ItemKind::Impl(..) => { - let snippet = self.snippet(item.span); - let where_span_end = snippet - .find_uncommented("{") - .map(|x| (BytePos(x as u32)) + source!(self, item.span).lo()); - let rw = format_impl(&self.get_context(), item, self.block_indent, where_span_end); - self.push_rewrite(item.span, rw); - } - ast::ItemKind::Trait(..) => { - let rw = format_trait(&self.get_context(), item, self.block_indent); - self.push_rewrite(item.span, rw); - } - ast::ItemKind::TraitAlias(ref generics, ref ty_param_bounds) => { - let shape = Shape::indented(self.block_indent, self.config); - let rw = format_trait_alias( - &self.get_context(), - item.ident, - generics, - ty_param_bounds, - shape, - ); - self.push_rewrite(item.span, rw); - } - ast::ItemKind::ExternCrate(_) => { - let rw = rewrite_extern_crate(&self.get_context(), item); - self.push_rewrite(item.span, rw); - } - ast::ItemKind::Struct(..) | ast::ItemKind::Union(..) => { - self.visit_struct(&StructParts::from_item(item)); - } - ast::ItemKind::Enum(ref def, ref generics) => { - self.format_missing_with_indent(source!(self, item.span).lo()); - self.visit_enum(item.ident, &item.vis, def, generics, item.span); - self.last_pos = source!(self, item.span).hi(); - } - ast::ItemKind::Mod(ref module) => { - let is_inline = !is_mod_decl(item); - self.format_missing_with_indent(source!(self, item.span).lo()); - self.format_mod(module, &item.vis, item.span, item.ident, attrs, is_inline); - } - ast::ItemKind::Mac(ref mac) => { - self.visit_mac(mac, Some(item.ident), MacroPosition::Item); - } - ast::ItemKind::ForeignMod(ref foreign_mod) => { - self.format_missing_with_indent(source!(self, item.span).lo()); - self.format_foreign_mod(foreign_mod, item.span); - } - ast::ItemKind::Static(..) | ast::ItemKind::Const(..) => { - self.visit_static(&StaticParts::from_item(item)); - } - ast::ItemKind::Fn(ref decl, unsafety, constness, abi, ref generics, ref body) => { - self.visit_fn( - visit::FnKind::ItemFn(item.ident, unsafety, constness, abi, &item.vis, body), - generics, - decl, - item.span, - ast::Defaultness::Final, - Some(&item.attrs), - ) - } - ast::ItemKind::Ty(ref ty, ref generics) => { - let rewrite = rewrite_type_alias( - &self.get_context(), - self.block_indent, - item.ident, - ty, - generics, - &item.vis, - item.span, - ); - self.push_rewrite(item.span, rewrite); - } - ast::ItemKind::GlobalAsm(..) => { - let snippet = Some(self.snippet(item.span).to_owned()); - self.push_rewrite(item.span, snippet); - } - ast::ItemKind::MacroDef(ref def) => { - let rewrite = rewrite_macro_def( - &self.get_context(), - self.shape(), - self.block_indent, - def, - item.ident, - &item.vis, - item.span, - ); - self.push_rewrite(item.span, rewrite); - } - } - } - - pub fn visit_trait_item(&mut self, ti: &ast::TraitItem) { - skip_out_of_file_lines_range_visitor!(self, ti.span); - - if self.visit_attrs(&ti.attrs, ast::AttrStyle::Outer) { - self.push_skipped_with_span(ti.span()); - return; - } - - match ti.node { - ast::TraitItemKind::Const(..) => self.visit_static(&StaticParts::from_trait_item(ti)), - ast::TraitItemKind::Method(ref sig, None) => { - let indent = self.block_indent; - let rewrite = - self.rewrite_required_fn(indent, ti.ident, sig, &ti.generics, ti.span); - self.push_rewrite(ti.span, rewrite); - } - ast::TraitItemKind::Method(ref sig, Some(ref body)) => { - self.visit_fn( - visit::FnKind::Method(ti.ident, sig, None, body), - &ti.generics, - &sig.decl, - ti.span, - ast::Defaultness::Final, - Some(&ti.attrs), - ); - } - ast::TraitItemKind::Type(ref type_param_bounds, ref type_default) => { - let rewrite = rewrite_associated_type( - ti.ident, - type_default.as_ref(), - Some(type_param_bounds), - &self.get_context(), - self.block_indent, - ); - self.push_rewrite(ti.span, rewrite); - } - ast::TraitItemKind::Macro(ref mac) => { - self.visit_mac(mac, Some(ti.ident), MacroPosition::Item); - } - } - } - - pub fn visit_impl_item(&mut self, ii: &ast::ImplItem) { - skip_out_of_file_lines_range_visitor!(self, ii.span); - - if self.visit_attrs(&ii.attrs, ast::AttrStyle::Outer) { - self.push_skipped_with_span(ii.span()); - return; - } - - match ii.node { - ast::ImplItemKind::Method(ref sig, ref body) => { - self.visit_fn( - visit::FnKind::Method(ii.ident, sig, Some(&ii.vis), body), - &ii.generics, - &sig.decl, - ii.span, - ii.defaultness, - Some(&ii.attrs), - ); - } - ast::ImplItemKind::Const(..) => self.visit_static(&StaticParts::from_impl_item(ii)), - ast::ImplItemKind::Type(ref ty) => { - let rewrite = rewrite_associated_impl_type( - ii.ident, - ii.defaultness, - Some(ty), - None, - &self.get_context(), - self.block_indent, - ); - self.push_rewrite(ii.span, rewrite); - } - ast::ImplItemKind::Macro(ref mac) => { - self.visit_mac(mac, Some(ii.ident), MacroPosition::Item); - } - } - } - - fn visit_mac(&mut self, mac: &ast::Mac, ident: Option<ast::Ident>, pos: MacroPosition) { - skip_out_of_file_lines_range_visitor!(self, mac.span); - - // 1 = ; - let shape = self.shape().sub_width(1).unwrap(); - let rewrite = rewrite_macro(mac, ident, &self.get_context(), shape, pos); - self.push_rewrite(mac.span, rewrite); - } - - pub fn push_str(&mut self, s: &str) { - self.line_number += count_newlines(s); - self.buffer.push_str(s); - } - - fn push_rewrite_inner(&mut self, span: Span, rewrite: Option<String>) { - if let Some(ref s) = rewrite { - self.push_str(s); - } else { - let snippet = self.snippet(span); - self.push_str(snippet); - } - self.last_pos = source!(self, span).hi(); - } - - pub fn push_rewrite(&mut self, span: Span, rewrite: Option<String>) { - self.format_missing_with_indent(source!(self, span).lo()); - self.push_rewrite_inner(span, rewrite); - } - - pub fn push_skipped_with_span(&mut self, span: Span) { - self.format_missing_with_indent(source!(self, span).lo()); - let lo = self.line_number + 1; - self.push_rewrite_inner(span, None); - let hi = self.line_number + 1; - self.skipped_range.push((lo, hi)); - } - - pub fn from_context(ctx: &'a RewriteContext) -> FmtVisitor<'a> { - FmtVisitor::from_codemap(ctx.parse_session, ctx.config, ctx.snippet_provider) - } - - pub fn from_codemap( - parse_session: &'a ParseSess, - config: &'a Config, - snippet_provider: &'a SnippetProvider, - ) -> FmtVisitor<'a> { - FmtVisitor { - parse_session, - codemap: parse_session.codemap(), - buffer: String::with_capacity(snippet_provider.big_snippet.len() * 2), - last_pos: BytePos(0), - block_indent: Indent::empty(), - config, - is_if_else_block: false, - snippet_provider, - line_number: 0, - skipped_range: vec![], - } - } - - pub fn opt_snippet(&'b self, span: Span) -> Option<&'a str> { - self.snippet_provider.span_to_snippet(span) - } - - pub fn snippet(&'b self, span: Span) -> &'a str { - self.opt_snippet(span).unwrap() - } - - // Returns true if we should skip the following item. - pub fn visit_attrs(&mut self, attrs: &[ast::Attribute], style: ast::AttrStyle) -> bool { - if contains_skip(attrs) { - return true; - } - - let attrs: Vec<_> = attrs.iter().filter(|a| a.style == style).cloned().collect(); - if attrs.is_empty() { - return false; - } - - let rewrite = attrs.rewrite(&self.get_context(), self.shape()); - let span = mk_sp(attrs[0].span.lo(), attrs[attrs.len() - 1].span.hi()); - self.push_rewrite(span, rewrite); - - false - } - - fn reorder_items<F>(&mut self, items_left: &[&ast::Item], is_item: &F, in_group: bool) -> usize - where - F: Fn(&ast::Item) -> bool, - { - let mut last = self.codemap.lookup_line_range(items_left[0].span()); - let item_length = items_left - .iter() - .take_while(|ppi| { - is_item(&***ppi) && (!in_group || { - let current = self.codemap.lookup_line_range(ppi.span()); - let in_same_group = current.lo < last.hi + 2; - last = current; - in_same_group - }) - }) - .count(); - let items = &items_left[..item_length]; - - let at_least_one_in_file_lines = items - .iter() - .any(|item| !out_of_file_lines_range!(self, item.span)); - - if at_least_one_in_file_lines { - self.format_imports(items); - } else { - for item in items { - self.push_rewrite(item.span, None); - } - } - - item_length - } - - fn walk_items(&mut self, mut items_left: &[&ast::Item]) { - macro try_reorder_items_with($reorder: ident, $in_group: ident, $pred: ident) { - if self.config.$reorder() && $pred(&*items_left[0]) { - let used_items_len = - self.reorder_items(items_left, &$pred, self.config.$in_group()); - let (_, rest) = items_left.split_at(used_items_len); - items_left = rest; - continue; - } - } - - while !items_left.is_empty() { - // If the next item is a `use`, `extern crate` or `mod`, then extract it and any - // subsequent items that have the same item kind to be reordered within - // `format_imports`. Otherwise, just format the next item for output. - { - try_reorder_items_with!( - reorder_imports, - reorder_imports_in_group, - is_use_item_without_attr - ); - try_reorder_items_with!( - reorder_extern_crates, - reorder_extern_crates_in_group, - is_extern_crate_without_attr - ); - try_reorder_items_with!(reorder_modules, reorder_modules, is_mod_decl_without_attr); - } - // Reaching here means items were not reordered. There must be at least - // one item left in `items_left`, so calling `unwrap()` here is safe. - let (item, rest) = items_left.split_first().unwrap(); - self.visit_item(item); - items_left = rest; - } - } - - fn walk_mod_items(&mut self, m: &ast::Mod) { - self.walk_items(&ptr_vec_to_ref_vec(&m.items)); - } - - fn walk_stmts(&mut self, stmts: &[ast::Stmt]) { - fn to_stmt_item(stmt: &ast::Stmt) -> Option<&ast::Item> { - match stmt.node { - ast::StmtKind::Item(ref item) => Some(&**item), - _ => None, - } - } - - if stmts.is_empty() { - return; - } - - // Extract leading `use ...;`. - let items: Vec<_> = stmts - .iter() - .take_while(|stmt| to_stmt_item(stmt).map_or(false, is_use_item)) - .filter_map(|stmt| to_stmt_item(stmt)) - .collect(); - - if items.is_empty() { - self.visit_stmt(&stmts[0]); - self.walk_stmts(&stmts[1..]); - } else { - self.walk_items(&items); - self.walk_stmts(&stmts[items.len()..]); - } - } - - fn walk_block_stmts(&mut self, b: &ast::Block) { - self.walk_stmts(&b.stmts) - } - - fn format_mod( - &mut self, - m: &ast::Mod, - vis: &ast::Visibility, - s: Span, - ident: ast::Ident, - attrs: &[ast::Attribute], - is_internal: bool, - ) { - self.push_str(&*utils::format_visibility(vis)); - self.push_str("mod "); - self.push_str(&ident.to_string()); - - if is_internal { - match self.config.brace_style() { - BraceStyle::AlwaysNextLine => { - let sep_str = format!("\n{}{{", self.block_indent.to_string(self.config)); - self.push_str(&sep_str); - } - _ => self.push_str(" {"), - } - // Hackery to account for the closing }. - let mod_lo = self.codemap.span_after(source!(self, s), "{"); - let body_snippet = - self.snippet(mk_sp(mod_lo, source!(self, m.inner).hi() - BytePos(1))); - let body_snippet = body_snippet.trim(); - if body_snippet.is_empty() { - self.push_str("}"); - } else { - self.last_pos = mod_lo; - self.block_indent = self.block_indent.block_indent(self.config); - self.visit_attrs(attrs, ast::AttrStyle::Inner); - self.walk_mod_items(m); - self.format_missing_with_indent(source!(self, m.inner).hi() - BytePos(1)); - self.close_block(false); - } - self.last_pos = source!(self, m.inner).hi(); - } else { - self.push_str(";"); - self.last_pos = source!(self, s).hi(); - } - } - - pub fn format_separate_mod(&mut self, m: &ast::Mod, filemap: &codemap::FileMap) { - self.block_indent = Indent::empty(); - self.walk_mod_items(m); - self.format_missing_with_indent(filemap.end_pos); - } - - pub fn skip_empty_lines(&mut self, end_pos: BytePos) { - while let Some(pos) = self.codemap - .opt_span_after(mk_sp(self.last_pos, end_pos), "\n") - { - if let Some(snippet) = self.opt_snippet(mk_sp(self.last_pos, pos)) { - if snippet.trim().is_empty() { - self.last_pos = pos; - } else { - return; - } - } - } - } - - pub fn get_context(&self) -> RewriteContext { - RewriteContext { - parse_session: self.parse_session, - codemap: self.codemap, - config: self.config, - inside_macro: false, - use_block: false, - is_if_else_block: false, - force_one_line_chain: false, - snippet_provider: self.snippet_provider, - } - } -} - -impl Rewrite for ast::NestedMetaItem { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - match self.node { - ast::NestedMetaItemKind::MetaItem(ref meta_item) => meta_item.rewrite(context, shape), - ast::NestedMetaItemKind::Literal(ref l) => rewrite_literal(context, l, shape), - } - } -} - -impl Rewrite for ast::MetaItem { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - Some(match self.node { - ast::MetaItemKind::Word => String::from(&*self.name.as_str()), - ast::MetaItemKind::List(ref list) => { - let name = self.name.as_str(); - // 1 = `(`, 2 = `]` and `)` - let item_shape = shape - .visual_indent(0) - .shrink_left(name.len() + 1) - .and_then(|s| s.sub_width(2))?; - let items = itemize_list( - context.codemap, - list.iter(), - ")", - ",", - |nested_meta_item| nested_meta_item.span.lo(), - |nested_meta_item| nested_meta_item.span.hi(), - |nested_meta_item| nested_meta_item.rewrite(context, item_shape), - self.span.lo(), - self.span.hi(), - false, - ); - let item_vec = items.collect::<Vec<_>>(); - let fmt = ListFormatting { - tactic: DefinitiveListTactic::Mixed, - separator: ",", - trailing_separator: SeparatorTactic::Never, - separator_place: SeparatorPlace::Back, - shape: item_shape, - ends_with_newline: false, - preserve_newline: false, - config: context.config, - }; - format!("{}({})", name, write_list(&item_vec, &fmt)?) - } - ast::MetaItemKind::NameValue(ref literal) => { - let name = self.name.as_str(); - // 3 = ` = ` - let lit_shape = shape.shrink_left(name.len() + 3)?; - let value = rewrite_literal(context, literal, lit_shape)?; - format!("{} = {}", name, value) - } - }) - } -} - -impl Rewrite for ast::Attribute { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - let prefix = match self.style { - ast::AttrStyle::Inner => "#!", - ast::AttrStyle::Outer => "#", - }; - let snippet = context.snippet(self.span); - if self.is_sugared_doc { - let doc_shape = Shape { - width: cmp::min(shape.width, context.config.comment_width()) - .checked_sub(shape.indent.width()) - .unwrap_or(0), - ..shape - }; - rewrite_comment(snippet, false, doc_shape, context.config) - } else { - if contains_comment(snippet) { - return Some(snippet.to_owned()); - } - // 1 = `[` - let shape = shape.offset_left(prefix.len() + 1)?; - self.meta()? - .rewrite(context, shape) - .map(|rw| format!("{}[{}]", prefix, rw)) - } - } -} - -/// Returns the first group of attributes that fills the given predicate. -/// We consider two doc comments are in different group if they are separated by normal comments. -fn take_while_with_pred<'a, P>( - context: &RewriteContext, - attrs: &'a [ast::Attribute], - pred: P, -) -> &'a [ast::Attribute] -where - P: Fn(&ast::Attribute) -> bool, -{ - let mut last_index = 0; - let mut iter = attrs.iter().enumerate().peekable(); - while let Some((i, attr)) = iter.next() { - if !pred(attr) { - break; - } - if let Some(&(_, next_attr)) = iter.peek() { - // Extract comments between two attributes. - let span_between_attr = mk_sp(attr.span.hi(), next_attr.span.lo()); - let snippet = context.snippet(span_between_attr); - if count_newlines(snippet) >= 2 || snippet.contains('/') { - break; - } - } - last_index = i; - } - if last_index == 0 { - &[] - } else { - &attrs[..last_index + 1] - } -} - -fn rewrite_first_group_attrs( - context: &RewriteContext, - attrs: &[ast::Attribute], - shape: Shape, -) -> Option<(usize, String)> { - if attrs.is_empty() { - return Some((0, String::new())); - } - // Rewrite doc comments - let sugared_docs = take_while_with_pred(context, attrs, |a| a.is_sugared_doc); - if !sugared_docs.is_empty() { - let snippet = sugared_docs - .iter() - .map(|a| context.snippet(a.span)) - .collect::<Vec<_>>() - .join("\n"); - return Some(( - sugared_docs.len(), - rewrite_comment(&snippet, false, shape, context.config)?, - )); - } - // Rewrite `#[derive(..)]`s. - if context.config.merge_derives() { - let derives = take_while_with_pred(context, attrs, is_derive); - if !derives.is_empty() { - let mut derive_args = vec![]; - for derive in derives { - derive_args.append(&mut get_derive_args(context, derive)?); - } - return Some((derives.len(), format_derive(context, &derive_args, shape)?)); - } - } - // Rewrite the first attribute. - Some((1, attrs[0].rewrite(context, shape)?)) -} - -fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) { - // Look at before and after comment and see if there are any empty lines. - let comment_begin = comment.chars().position(|c| c == '/'); - let len = comment_begin.unwrap_or_else(|| comment.len()); - let mlb = count_newlines(&comment[..len]) > 1; - let mla = if comment_begin.is_none() { - mlb - } else { - let comment_end = comment.chars().rev().position(|c| !c.is_whitespace()); - let len = comment_end.unwrap(); - comment - .chars() - .rev() - .take(len) - .filter(|c| *c == '\n') - .count() > 1 - }; - (if mlb { "\n" } else { "" }, if mla { "\n" } else { "" }) -} - -impl<'a> Rewrite for [ast::Attribute] { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> { - if self.is_empty() { - return Some(String::new()); - } - let (first_group_len, first_group_str) = rewrite_first_group_attrs(context, self, shape)?; - if self.len() == 1 || first_group_len == self.len() { - Some(first_group_str) - } else { - let rest_str = self[first_group_len..].rewrite(context, shape)?; - let missing_span = mk_sp( - self[first_group_len - 1].span.hi(), - self[first_group_len].span.lo(), - ); - // Preserve an empty line before/after doc comments. - if self[0].is_sugared_doc || self[first_group_len].is_sugared_doc { - let snippet = context.snippet(missing_span); - let (mla, mlb) = has_newlines_before_after_comment(snippet); - let comment = ::comment::recover_missing_comment_in_span( - missing_span, - shape.with_max_width(context.config), - context, - 0, - )?; - let comment = if comment.is_empty() { - format!("\n{}", mlb) - } else { - format!("{}{}\n{}", mla, comment, mlb) - }; - Some(format!( - "{}{}{}{}", - first_group_str, - comment, - shape.indent.to_string(context.config), - rest_str - )) - } else { - combine_strs_with_missing_comments( - context, - &first_group_str, - &rest_str, - missing_span, - shape, - false, - ) - } - } - } -} - -// Format `#[derive(..)]`, using visual indent & mixed style when we need to go multiline. -fn format_derive(context: &RewriteContext, derive_args: &[&str], shape: Shape) -> Option<String> { - let mut result = String::with_capacity(128); - result.push_str("#[derive("); - // 11 = `#[derive()]` - let initial_budget = shape.width.checked_sub(11)?; - let mut budget = initial_budget; - let num = derive_args.len(); - for (i, a) in derive_args.iter().enumerate() { - // 2 = `, ` or `)]` - let width = a.len() + 2; - if width > budget { - if i > 0 { - // Remove trailing whitespace. - result.pop(); - } - result.push('\n'); - // 9 = `#[derive(` - result.push_str(&(shape.indent + 9).to_string(context.config)); - budget = initial_budget; - } else { - budget = budget.checked_sub(width).unwrap_or(0); - } - result.push_str(a); - if i != num - 1 { - result.push_str(", ") - } - } - result.push_str(")]"); - Some(result) -} - -fn is_derive(attr: &ast::Attribute) -> bool { - attr.check_name("derive") -} - -/// Returns the arguments of `#[derive(...)]`. -fn get_derive_args<'a>(context: &'a RewriteContext, attr: &ast::Attribute) -> Option<Vec<&'a str>> { - attr.meta_item_list().map(|meta_item_list| { - meta_item_list - .iter() - .map(|nested_meta_item| context.snippet(nested_meta_item.span)) - .collect() - }) -} - -// Rewrite `extern crate foo;` WITHOUT attributes. -pub fn rewrite_extern_crate(context: &RewriteContext, item: &ast::Item) -> Option<String> { - assert!(is_extern_crate(item)); - let new_str = context.snippet(item.span); - Some(if contains_comment(new_str) { - new_str.to_owned() - } else { - let no_whitespace = &new_str.split_whitespace().collect::<Vec<&str>>().join(" "); - String::from(&*Regex::new(r"\s;").unwrap().replace(no_whitespace, ";")) - }) -} - -fn get_attrs_from_stmt(stmt: &ast::Stmt) -> &[ast::Attribute] { - match stmt.node { - ast::StmtKind::Local(ref local) => &local.attrs, - ast::StmtKind::Item(ref item) => &item.attrs, - ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => &expr.attrs, - ast::StmtKind::Mac(ref mac) => &mac.2, - } -} |
