Merge pull request #93 from marcusklaas/tuple-structs

Format tuple like structs
This commit is contained in:
Nick Cameron 2015-06-25 14:18:19 -07:00
commit 8cef6785bf
24 changed files with 1210 additions and 576 deletions

202
src/comment.rs Normal file
View File

@ -0,0 +1,202 @@
// 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 comments.
use string::{StringFormat, rewrite_string};
use utils::make_indent;
pub fn rewrite_comment(orig: &str, block_style: bool, width: usize, offset: usize) -> String {
let s = orig.trim();
// Edge case: block comments. Let's not trim their lines (for now).
let opener = if block_style { "/* " } else { "// " };
let closer = if block_style { " */" } else { "" };
let line_start = if block_style { " * " } else { "// " };
let max_chars = width.checked_sub(closer.len()).unwrap_or(1)
.checked_sub(opener.len()).unwrap_or(1);
let fmt = StringFormat {
opener: "",
closer: "",
line_start: line_start,
line_end: "",
width: max_chars,
offset: offset + opener.len() - line_start.len(),
trim_end: true
};
let indent_str = make_indent(offset);
let line_breaks = s.chars().filter(|&c| c == '\n').count();
let (_, mut s) = s.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)];
}
line.trim_right_matches(' ')
})
.map(left_trim_comment_line)
.fold((true, opener.to_owned()), |(first, mut acc), line| {
if !first {
acc.push('\n');
acc.push_str(&indent_str);
acc.push_str(line_start);
}
if line.len() > max_chars {
acc.push_str(&rewrite_string(line, &fmt));
} else {
acc.push_str(line);
}
(false, acc)
});
s.push_str(closer);
s
}
fn left_trim_comment_line<'a>(line: &'a str) -> &'a str {
if line.starts_with("/* ") || line.starts_with("// ") {
&line[3..]
} else if line.starts_with("/*") || line.starts_with("* ") || line.starts_with("//") {
&line[2..]
} else if line.starts_with("*") {
&line[1..]
} else {
line
}
}
#[test]
fn format_comments() {
assert_eq!("/* test */", rewrite_comment(" //test", true, 100, 100));
assert_eq!("// comment\n// on a", rewrite_comment("// comment on a", false, 10, 0));
assert_eq!("// A multi line comment\n // between args.",
rewrite_comment("// A multi line comment\n // between args.",
false,
60,
12));
let input = "// comment";
let expected_output = "/* com\n \
* men\n \
* t */";
assert_eq!(expected_output, rewrite_comment(input, true, 9, 69));
}
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();
let mut possible_comment = false;
for (i, b) in self.char_indices() {
match needle_iter.next() {
Some(c) => {
if b != c {
needle_iter = pat.chars();
}
},
None => return Some(i - pat.len())
}
if possible_comment && (b == '/' || b == '*') {
return find_comment_end(&self[(i-1)..])
.and_then(|end| {
self[(end + i - 1)..].find_uncommented(pat)
.map(|idx| idx + end + i - 1)
});
}
possible_comment = b == '/';
}
// Handle case where the pattern is a suffix of the search string
match needle_iter.next() {
Some(_) => None,
None => Some(self.len() - pat.len())
}
}
}
#[test]
fn test_find_uncommented() {
fn check(haystack: &str, needle: &str, expected: Option<usize>) {
println!("haystack {:?}, needle: {:?}", haystack, needle);
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("π? /**/ π is nice!", "π is nice", Some(9));
check("/*sup yo? \n sup*/ sup", "p", Some(20));
check("hel/*lohello*/lo", "hello", None);
check("acb", "ab", None);
}
// 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> {
if s.starts_with("//") {
s.find('\n').map(|idx| idx + 1)
} else {
// Block comment
let mut levels = 0;
let mut prev_char = 'a';
for (i, mut c) in s.char_indices() {
if c == '*' && prev_char == '/' {
levels += 1;
c = 'a'; // Invalidate prev_char
} else if c == '/' && prev_char == '*' {
levels -= 1;
if levels == 0 {
return Some(i + 1);
}
c = 'a';
}
prev_char = c;
}
None
}
}
#[test]
fn comment_end() {
assert_eq!(Some(6), find_comment_end("// hi\n"));
assert_eq!(Some(9), find_comment_end("/* sup */ "));
assert_eq!(Some(9), find_comment_end("/*/**/ */ "));
assert_eq!(Some(6), find_comment_end("/*/ */ weird!"));
assert_eq!(None, find_comment_end("/* hi /* test */"));
assert_eq!(None, find_comment_end("// hi /* test */"));
assert_eq!(Some(9), find_comment_end("// hi /*\n."));
}

View File

@ -24,7 +24,7 @@ pub struct Config {
pub fn_brace_style: BraceStyle,
pub fn_return_indent: ReturnIndent,
pub fn_args_paren_newline: bool,
pub struct_trailing_comma: bool,
pub struct_trailing_comma: SeparatorTactic,
pub struct_lit_trailing_comma: SeparatorTactic,
pub enum_trailing_comma: bool,
pub report_todo: ReportTactic,

View File

@ -6,7 +6,7 @@ newline_style = "Unix"
fn_brace_style = "SameLineWhere"
fn_return_indent = "WithArgs"
fn_args_paren_newline = true
struct_trailing_comma = true
struct_trailing_comma = "Vertical"
struct_lit_trailing_comma = "Vertical"
enum_trailing_comma = true
report_todo = "Always"

View File

@ -8,17 +8,16 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use utils::*;
use lists::{write_list, ListFormatting, SeparatorTactic, ListTactic};
use rewrite::{Rewrite, RewriteContext};
use lists::{write_list, itemize_list, ListFormatting, SeparatorTactic, ListTactic};
use string::{StringFormat, rewrite_string};
use utils::span_after;
use syntax::{ast, ptr};
use syntax::codemap::{Pos, Span};
use syntax::codemap::{Pos, Span, BytePos};
use syntax::parse::token;
use syntax::print::pprust;
use MIN_STRING;
impl Rewrite for ast::Expr {
fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
match self.node {
@ -33,20 +32,22 @@ fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Opti
}
}
ast::Expr_::ExprCall(ref callee, ref args) => {
return rewrite_call(context, callee, args, width, offset);
return rewrite_call(context, callee, args, self.span, width, offset);
}
ast::Expr_::ExprParen(ref subexpr) => {
return rewrite_paren(context, subexpr, width, offset);
}
ast::Expr_::ExprStruct(ref path, ref fields, ref base) => {
return rewrite_struct_lit(context, path,
fields,
base.as_ref().map(|e| &**e),
width,
offset);
return rewrite_struct_lit(context,
path,
fields,
base.as_ref().map(|e| &**e),
self.span,
width,
offset);
}
ast::Expr_::ExprTup(ref items) => {
return rewrite_tuple_lit(context, items, width, offset);
return rewrite_tuple_lit(context, items, self.span, width, offset);
}
_ => {}
}
@ -55,9 +56,12 @@ fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Opti
}
}
fn rewrite_string_lit(context: &RewriteContext, s: &str, span: Span, width: usize, offset: usize) -> Option<String> {
// FIXME I bet this stomps unicode escapes in the source string
fn rewrite_string_lit(context: &RewriteContext,
s: &str,
span: Span,
width: usize,
offset: usize)
-> Option<String> {
// Check if there is anything to fix: we always try to fixup multi-line
// strings, or if the string is too long for the line.
let l_loc = context.codemap.lookup_char_pos(span.lo);
@ -65,102 +69,65 @@ fn rewrite_string_lit(context: &RewriteContext, s: &str, span: Span, width: usiz
if l_loc.line == r_loc.line && r_loc.col.to_usize() <= context.config.max_width {
return context.codemap.span_to_snippet(span).ok();
}
let fmt = StringFormat {
opener: "\"",
closer: "\"",
line_start: " ",
line_end: "\\",
width: width,
offset: offset,
trim_end: false
};
// TODO if lo.col > IDEAL - 10, start a new line (need cur indent for that)
let s = s.escape_default();
let offset = offset + 1;
let indent = make_indent(offset);
let indent = &indent;
let mut cur_start = 0;
let mut result = String::with_capacity(round_up_to_power_of_two(s.len()));
result.push('"');
loop {
let max_chars = if cur_start == 0 {
// First line.
width - 2 // 2 = " + \
} else {
context.config.max_width - offset - 1 // 1 = either \ or ;
};
let mut cur_end = cur_start + max_chars;
if cur_end >= s.len() {
result.push_str(&s[cur_start..]);
break;
}
// Make sure we're on a char boundary.
cur_end = next_char(&s, cur_end);
// Push cur_end left until we reach whitespace
while !s.char_at(cur_end-1).is_whitespace() {
cur_end = prev_char(&s, cur_end);
if cur_end - cur_start < MIN_STRING {
// We can't break at whitespace, fall back to splitting
// anywhere that doesn't break an escape sequence
cur_end = next_char(&s, cur_start + max_chars);
while s.char_at(prev_char(&s, cur_end)) == '\\' {
cur_end = prev_char(&s, cur_end);
}
break;
}
}
// Make sure there is no whitespace to the right of the break.
while cur_end < s.len() && s.char_at(cur_end).is_whitespace() {
cur_end = next_char(&s, cur_end+1);
}
result.push_str(&s[cur_start..cur_end]);
result.push_str("\\\n");
result.push_str(indent);
cur_start = cur_end;
}
result.push('"');
Some(result)
Some(rewrite_string(&s.escape_default(), &fmt))
}
fn rewrite_call(context: &RewriteContext,
callee: &ast::Expr,
args: &[ptr::P<ast::Expr>],
span: Span,
width: usize,
offset: usize)
-> Option<String>
{
-> Option<String> {
debug!("rewrite_call, width: {}, offset: {}", width, offset);
// TODO using byte lens instead of char lens (and probably all over the place too)
let callee_str = try_opt!(callee.rewrite(context, width, offset));
debug!("rewrite_call, callee_str: `{:?}`", callee_str);
debug!("rewrite_call, callee_str: `{}`", callee_str);
if args.len() == 0 {
return Some(format!("{}()", callee_str));
}
// 2 is for parens.
let remaining_width = width - callee_str.len() - 2;
let offset = callee_str.len() + 1 + offset;
let arg_count = args.len();
let args_str = if arg_count > 0 {
let args_rewritten: Vec<_> =
try_opt!(args.iter()
.map(|arg| arg.rewrite(context, remaining_width, offset)
.map(|arg_str| (arg_str, String::new())))
.collect());
let fmt = ListFormatting {
tactic: ListTactic::HorizontalVertical,
separator: ",",
trailing_separator: SeparatorTactic::Never,
indent: offset,
h_width: remaining_width,
v_width: remaining_width,
};
write_list(&args_rewritten, &fmt)
} else {
String::new()
let items = itemize_list(context.codemap,
Vec::new(),
args.iter(),
",",
")",
|item| item.span.lo,
|item| item.span.hi,
// Take old span when rewrite fails.
|item| item.rewrite(context, remaining_width, offset)
.unwrap_or(context.codemap.span_to_snippet(item.span)
.unwrap()),
callee.span.hi + BytePos(1),
span.hi);
let fmt = ListFormatting {
tactic: ListTactic::HorizontalVertical,
separator: ",",
trailing_separator: SeparatorTactic::Never,
indent: offset,
h_width: remaining_width,
v_width: remaining_width,
ends_with_newline: true,
};
Some(format!("{}({})", callee_str, args_str))
Some(format!("{}({})", callee_str, write_list(&items, &fmt)))
}
fn rewrite_paren(context: &RewriteContext, subexpr: &ast::Expr, width: usize, offset: usize) -> Option<String> {
@ -172,34 +139,68 @@ fn rewrite_paren(context: &RewriteContext, subexpr: &ast::Expr, width: usize, of
subexpr_str.map(|s| format!("({})", s))
}
fn rewrite_struct_lit(context: &RewriteContext,
path: &ast::Path,
fields: &[ast::Field],
base: Option<&ast::Expr>,
width: usize,
offset: usize)
fn rewrite_struct_lit<'a>(context: &RewriteContext,
path: &ast::Path,
fields: &'a [ast::Field],
base: Option<&'a ast::Expr>,
span: Span,
width: usize,
offset: usize)
-> Option<String>
{
debug!("rewrite_struct_lit: width {}, offset {}", width, offset);
assert!(fields.len() > 0 || base.is_some());
enum StructLitField<'a> {
Regular(&'a ast::Field),
Base(&'a ast::Expr)
}
let path_str = pprust::path_to_string(path);
// Foo { a: Foo } - indent is +3, width is -5.
let indent = offset + path_str.len() + 3;
let budget = width - (path_str.len() + 5);
let field_strs: Vec<_> =
try_opt!(fields.iter()
.map(|field| rewrite_field(context, field, budget, indent))
.chain(base.iter()
.map(|expr| expr.rewrite(context,
// 2 = ".."
budget - 2,
indent + 2)
.map(|s| format!("..{}", s))))
.collect());
// FIXME comments
let field_strs: Vec<_> = field_strs.into_iter().map(|s| (s, String::new())).collect();
let field_iter = fields.into_iter().map(StructLitField::Regular)
.chain(base.into_iter().map(StructLitField::Base));
let items = itemize_list(context.codemap,
Vec::new(),
field_iter,
",",
"}",
|item| {
match *item {
StructLitField::Regular(ref field) => field.span.lo,
// 2 = ..
StructLitField::Base(ref expr) => expr.span.lo - BytePos(2)
}
},
|item| {
match *item {
StructLitField::Regular(ref field) => field.span.hi,
StructLitField::Base(ref expr) => expr.span.hi
}
},
|item| {
match *item {
StructLitField::Regular(ref field) => {
rewrite_field(context, &field, budget, indent)
.unwrap_or(context.codemap.span_to_snippet(field.span)
.unwrap())
},
StructLitField::Base(ref expr) => {
// 2 = ..
expr.rewrite(context, budget - 2, indent + 2)
.map(|s| format!("..{}", s))
.unwrap_or(context.codemap.span_to_snippet(expr.span)
.unwrap())
}
}
},
span_after(span, "{", context.codemap),
span.hi);
let fmt = ListFormatting {
tactic: ListTactic::HorizontalVertical,
separator: ",",
@ -211,14 +212,15 @@ fn rewrite_struct_lit(context: &RewriteContext,
indent: indent,
h_width: budget,
v_width: budget,
ends_with_newline: true,
};
let fields_str = write_list(&field_strs, &fmt);
let fields_str = write_list(&items, &fmt);
Some(format!("{} {{ {} }}", path_str, fields_str))
// FIXME if the usual multi-line layout is too wide, we should fall back to
// Foo {
// a: ...,
// }
// FIXME if the usual multi-line layout is too wide, we should fall back to
// Foo {
// a: ...,
// }
}
fn rewrite_field(context: &RewriteContext, field: &ast::Field, width: usize, offset: usize) -> Option<String> {
@ -230,43 +232,43 @@ fn rewrite_field(context: &RewriteContext, field: &ast::Field, width: usize, off
fn rewrite_tuple_lit(context: &RewriteContext,
items: &[ptr::P<ast::Expr>],
span: Span,
width: usize,
offset: usize)
-> Option<String> {
// opening paren
let indent = offset + 1;
// In case of length 1, need a trailing comma
if items.len() == 1 {
return items[0].rewrite(context, width - 3, indent).map(|s| format!("({},)", s));
}
// Only last line has width-1 as budget, other may take max_width
let item_strs: Vec<_> =
try_opt!(items.iter()
.enumerate()
.map(|(i, item)| {
let rem_width = if i == items.len() - 1 {
width - 2
} else {
context.config.max_width - indent - 2
};
item.rewrite(context, rem_width, indent)
})
.collect());
let tactics = if item_strs.iter().any(|s| s.contains('\n')) {
ListTactic::Vertical
} else {
ListTactic::HorizontalVertical
};
// FIXME handle comments
let item_strs: Vec<_> = item_strs.into_iter().map(|s| (s, String::new())).collect();
let fmt = ListFormatting {
tactic: tactics,
separator: ",",
trailing_separator: SeparatorTactic::Never,
indent: indent,
h_width: width - 2,
v_width: width - 2,
};
let item_str = write_list(&item_strs, &fmt);
Some(format!("({})", item_str))
}
let indent = offset + 1;
let items = itemize_list(context.codemap,
Vec::new(),
items.into_iter(),
",",
")",
|item| item.span.lo,
|item| item.span.hi,
|item| item.rewrite(context,
context.config.max_width - indent - 2,
indent)
.unwrap_or(context.codemap.span_to_snippet(item.span)
.unwrap()),
span.lo + BytePos(1), // Remove parens
span.hi - BytePos(1));
// In case of length 1, need a trailing comma
let trailing_separator_tactic = if items.len() == 1 {
SeparatorTactic::Always
} else {
SeparatorTactic::Never
};
let fmt = ListFormatting {
tactic: ListTactic::HorizontalVertical,
separator: ",",
trailing_separator: trailing_separator_tactic,
indent: indent,
h_width: width - 2,
v_width: width - 2,
ends_with_newline: true,
};
Some(format!("({})", write_list(&items, &fmt)))
}

View File

@ -9,7 +9,7 @@
// except according to those terms.
use visitor::FmtVisitor;
use lists::{write_list, ListFormatting, SeparatorTactic, ListTactic};
use lists::{write_list, ListItem, ListFormatting, SeparatorTactic, ListTactic};
use utils::format_visibility;
use syntax::ast;
@ -65,16 +65,8 @@ pub fn rewrite_use_list(&mut self,
let used_width = indent + 2;
// Break as early as possible when we've blown our budget.
let remaining_line_budget = if used_width > one_line_budget {
0
} else {
one_line_budget - used_width
};
let remaining_multi_budget = if used_width > multi_line_budget {
0
} else {
multi_line_budget - used_width
};
let remaining_line_budget = one_line_budget.checked_sub(used_width).unwrap_or(0);
let remaining_multi_budget = multi_line_budget.checked_sub(used_width).unwrap_or(0);
let fmt = ListFormatting {
tactic: ListTactic::Mixed,
@ -83,6 +75,7 @@ pub fn rewrite_use_list(&mut self,
indent: block_indent + indent,
h_width: remaining_line_budget,
v_width: remaining_multi_budget,
ends_with_newline: true,
};
// TODO handle any comments inbetween items.
@ -94,7 +87,7 @@ pub fn rewrite_use_list(&mut self,
false
}
) {
Some(("self".to_owned(), String::new()))
Some(ListItem::from_str("self"))
} else {
None
};
@ -102,12 +95,13 @@ pub fn rewrite_use_list(&mut self,
let items: Vec<_> = head.into_iter().chain(path_list.iter().filter_map(|vpi| {
match vpi.node {
ast::PathListItem_::PathListIdent{ name, .. } => {
Some((token::get_ident(name).to_string(), String::new()))
Some(ListItem::from_str(token::get_ident(name).to_string()))
}
// Skip `self`, because we added it above.
ast::PathListItem_::PathListMod{ .. } => None,
}
})).collect();
Some(if path_str.len() == 0 {
format!("{}use {{{}}};", vis, write_list(&items, &fmt))
} else {

View File

@ -223,7 +223,6 @@ fn inspect_number(&mut self,
#[test]
fn find_unnumbered_issue() {
fn check_fail(text: &str, failing_pos: usize) {
println!("{:?}", text);
let mut seeker = BadIssueSeeker::new(ReportTactic::Unnumbered, ReportTactic::Unnumbered);
assert_eq!(Some(failing_pos), text.chars().position(|c| seeker.inspect(c).is_some()));
}

View File

@ -11,9 +11,11 @@
// Formatting top-level items - functions, structs, enums, traits, impls.
use {ReturnIndent, BraceStyle};
use utils::{format_visibility, make_indent, FindUncommented};
use lists::{write_list, ListFormatting, SeparatorTactic, ListTactic};
use utils::{format_visibility, make_indent, contains_skip, span_after};
use lists::{write_list, itemize_list, ListItem, ListFormatting, SeparatorTactic, ListTactic};
use comment::FindUncommented;
use visitor::FmtVisitor;
use syntax::{ast, abi};
use syntax::codemap::{self, Span, BytePos};
use syntax::print::pprust;
@ -30,7 +32,7 @@ pub fn rewrite_fn(&mut self,
constness: &ast::Constness,
abi: &abi::Abi,
vis: ast::Visibility,
span_end: BytePos)
span: Span)
-> String
{
let newline_brace = self.newline_for_brace(&generics.where_clause);
@ -44,7 +46,7 @@ pub fn rewrite_fn(&mut self,
constness,
abi,
vis,
span_end,
span,
newline_brace);
// Prepare for the function body by possibly adding a newline and indent.
@ -68,7 +70,7 @@ pub fn rewrite_required_fn(&mut self,
-> String
{
// Drop semicolon or it will be interpreted as comment
let span_end = span.hi - BytePos(1);
let span = codemap::mk_sp(span.lo, span.hi - BytePos(1));
let mut result = self.rewrite_fn_base(indent,
ident,
@ -79,7 +81,7 @@ pub fn rewrite_required_fn(&mut self,
&sig.constness,
&sig.abi,
ast::Visibility::Inherited,
span_end,
span,
false);
// Re-attach semicolon
@ -98,7 +100,7 @@ fn rewrite_fn_base(&mut self,
constness: &ast::Constness,
abi: &abi::Abi,
vis: ast::Visibility,
span_end: BytePos,
span: Span,
newline_brace: bool)
-> String
{
@ -131,7 +133,8 @@ fn rewrite_fn_base(&mut self,
let generics_indent = indent + result.len();
result.push_str(&self.rewrite_generics(generics,
generics_indent,
span_for_return(&fd.output).lo));
codemap::mk_sp(span.lo,
span_for_return(&fd.output).lo)));
let ret_str = self.rewrite_return(&fd.output);
@ -162,7 +165,8 @@ fn rewrite_fn_base(&mut self,
one_line_budget,
multi_line_budget,
arg_indent,
span_for_return(&fd.output)));
codemap::mk_sp(span_after(span, "(", self.codemap),
span_for_return(&fd.output).lo)));
result.push(')');
// Return type.
@ -189,7 +193,7 @@ fn rewrite_fn_base(&mut self,
// Comment between return type and the end of the decl.
let snippet_lo = fd.output.span().hi;
if where_clause.predicates.len() == 0 {
let snippet_hi = span_end;
let snippet_hi = span.hi;
let snippet = self.snippet(codemap::mk_sp(snippet_lo, snippet_hi));
let snippet = snippet.trim();
if snippet.len() > 0 {
@ -204,7 +208,9 @@ fn rewrite_fn_base(&mut self,
}
// Where clause.
result.push_str(&self.rewrite_where_clause(where_clause, indent, span_end));
result.push_str(&self.rewrite_where_clause(where_clause,
indent,
span.hi));
result
}
@ -215,7 +221,7 @@ fn rewrite_args(&self,
one_line_budget: usize,
multi_line_budget: usize,
arg_indent: usize,
ret_span: Span)
span: Span)
-> String
{
let mut arg_item_strs: Vec<_> = args.iter().map(|a| self.rewrite_fn_input(a)).collect();
@ -262,89 +268,50 @@ fn rewrite_args(&self,
}
// Comments between args
let mut arg_comments = Vec::new();
let mut arg_items = Vec::new();
if min_args == 2 {
arg_comments.push("".to_owned());
arg_items.push(ListItem::from_str(""));
}
// TODO 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 {
arg_comments = self.make_comments_for_list(arg_comments,
args[min_args-1..].iter(),
",",
")",
|arg| arg.pat.span.lo,
|arg| arg.ty.span.hi,
ret_span.lo);
let comment_span_start = if min_args == 2 {
span_after(span, ",", self.codemap)
} else {
span.lo
};
arg_items = itemize_list(self.codemap,
arg_items,
args[min_args-1..].iter(),
",",
")",
|arg| arg.pat.span.lo,
|arg| arg.ty.span.hi,
|_| String::new(),
comment_span_start,
span.hi);
}
debug!("comments: {:?}", arg_comments);
assert_eq!(arg_item_strs.len(), arg_items.len());
// If there are // comments, keep them multi-line.
let mut list_tactic = ListTactic::HorizontalVertical;
if arg_comments.iter().any(|c| c.contains("//")) {
list_tactic = ListTactic::Vertical;
for (item, arg) in arg_items.iter_mut().zip(arg_item_strs) {
item.item = arg;
}
assert_eq!(arg_item_strs.len(), arg_comments.len());
let arg_strs: Vec<_> = arg_item_strs.into_iter().zip(arg_comments.into_iter()).collect();
let fmt = ListFormatting {
tactic: list_tactic,
tactic: ListTactic::HorizontalVertical,
separator: ",",
trailing_separator: SeparatorTactic::Never,
indent: arg_indent,
h_width: one_line_budget,
v_width: multi_line_budget,
ends_with_newline: true,
};
write_list(&arg_strs, &fmt)
}
// Gets comments in between items of a list.
fn make_comments_for_list<T, I, F1, F2>(&self,
prefix: Vec<String>,
mut it: I,
separator: &str,
terminator: &str,
get_lo: F1,
get_hi: F2,
next_span_start: BytePos)
-> Vec<String>
where I: Iterator<Item=T>,
F1: Fn(&T) -> BytePos,
F2: Fn(&T) -> BytePos
{
let mut result = prefix;
let mut prev_end = get_hi(&it.next().unwrap());
for item in it {
let cur_start = get_lo(&item);
let snippet = self.snippet(codemap::mk_sp(prev_end, cur_start));
let mut snippet = snippet.trim();
let white_space: &[_] = &[' ', '\t'];
if snippet.starts_with(separator) {
snippet = snippet[separator.len()..].trim_matches(white_space);
} else if snippet.ends_with(separator) {
snippet = snippet[..snippet.len()-separator.len()].trim_matches(white_space);
}
result.push(snippet.to_owned());
prev_end = get_hi(&item);
}
// Get the last commment.
// FIXME If you thought the crap with the commas was ugly, just wait.
// This is awful. We're going to look from the last item span to the
// start of the return type span, then we drop everything after the
// first closing paren.
// The fix is comments in the AST or a span for the closing paren.
let snippet = self.snippet(codemap::mk_sp(prev_end, next_span_start));
let snippet = snippet.trim();
let snippet = &snippet[..snippet.find_uncommented(terminator).unwrap_or(snippet.len())];
let snippet = snippet.trim();
result.push(snippet.to_owned());
result
write_list(&arg_items, &fmt)
}
fn compute_budgets_for_args(&self,
@ -412,12 +379,16 @@ pub fn visit_enum(&mut self,
generics: &ast::Generics,
span: Span)
{
let header_str = self.format_header("enum", ident, vis);
let header_str = self.format_header("enum ", ident, vis);
self.changes.push_str_span(span, &header_str);
let enum_snippet = self.snippet(span);
let body_start = span.lo + BytePos(enum_snippet.find_uncommented("{").unwrap() as u32 + 1);
let generics_str = self.format_generics(generics, body_start);
let generics_str = self.format_generics(generics,
" {",
self.block_indent + self.config.tab_spaces,
codemap::mk_sp(span.lo,
body_start));
self.changes.push_str_span(span, &generics_str);
self.last_pos = body_start;
@ -447,75 +418,185 @@ fn visit_variant(&mut self,
return;
}
if let ast::VariantKind::TupleVariantKind(ref types) = field.node.kind {
self.format_missing_with_indent(field.span.lo);
self.format_missing_with_indent(field.span.lo);
let vis = format_visibility(field.node.vis);
self.changes.push_str_span(field.span, vis);
let name = field.node.name.to_string();
self.changes.push_str_span(field.span, &name);
match field.node.kind {
ast::VariantKind::TupleVariantKind(ref types) => {
let vis = format_visibility(field.node.vis);
self.changes.push_str_span(field.span, vis);
let name = field.node.name.to_string();
self.changes.push_str_span(field.span, &name);
let mut result = String::new();
let mut result = String::new();
if types.len() > 0 {
let comments = self.make_comments_for_list(Vec::new(),
types.iter().map(|arg| arg.ty.span),
",",
")",
|span| span.lo,
|span| span.hi,
next_span_start);
if types.len() > 0 {
let items = itemize_list(self.codemap,
Vec::new(),
types.iter(),
",",
")",
|arg| arg.ty.span.lo,
|arg| arg.ty.span.hi,
|arg| pprust::ty_to_string(&arg.ty),
span_after(field.span, "(", self.codemap),
next_span_start);
let type_strings: Vec<_> = types.iter()
.map(|arg| pprust::ty_to_string(&arg.ty))
.zip(comments.into_iter())
.collect();
result.push('(');
result.push('(');
let indent = self.block_indent
+ vis.len()
+ field.node.name.to_string().len()
+ 1; // Open paren
let indent = self.block_indent
+ vis.len()
+ field.node.name.to_string().len()
+ 1; // 1 = (
let comma_cost = if self.config.enum_trailing_comma { 1 } else { 0 };
let budget = self.config.ideal_width - indent - comma_cost - 1; // 1 = )
let comma_cost = if self.config.enum_trailing_comma { 1 } else { 0 };
let budget = self.config.ideal_width - indent - comma_cost - 1; // 1 = )
let fmt = ListFormatting {
tactic: ListTactic::HorizontalVertical,
separator: ",",
trailing_separator: SeparatorTactic::Never,
indent: indent,
h_width: budget,
v_width: budget,
ends_with_newline: false,
};
result.push_str(&write_list(&items, &fmt));
result.push(')');
}
let fmt = ListFormatting {
tactic: ListTactic::HorizontalVertical,
separator: ",",
trailing_separator: SeparatorTactic::Never,
indent: indent,
h_width: budget,
v_width: budget,
};
result.push_str(&write_list(&type_strings, &fmt));
result.push(')');
}
if let Some(ref expr) = field.node.disr_expr {
result.push_str(" = ");
let expr_snippet = self.snippet(expr.span);
result.push_str(&expr_snippet);
if let Some(ref expr) = field.node.disr_expr {
result.push_str(" = ");
let expr_snippet = self.snippet(expr.span);
result.push_str(&expr_snippet);
// Make sure we do not exceed column limit
// 4 = " = ,"
assert!(self.config.max_width >= vis.len() + name.len() + expr_snippet.len() + 4,
"Enum variant exceeded column limit");
}
// Make sure we do not exceed column limit
// 4 = " = ,"
assert!(self.config.max_width >= vis.len() + name.len() + expr_snippet.len() + 4,
"Enum variant exceeded column limit");
}
self.changes.push_str_span(field.span, &result);
self.changes.push_str_span(field.span, &result);
if !last_field || self.config.enum_trailing_comma {
self.changes.push_str_span(field.span, ",");
}
},
ast::VariantKind::StructVariantKind(ref struct_def) => {
let result = self.format_struct("",
field.node.name,
field.node.vis,
struct_def,
None,
field.span,
self.block_indent);
if !last_field || self.config.enum_trailing_comma {
self.changes.push_str_span(field.span, ",");
self.changes.push_str_span(field.span, &result)
}
}
// TODO: deal with struct-like variants
self.last_pos = field.span.hi + BytePos(1);
}
fn format_struct(&self,
item_name: &str,
ident: ast::Ident,
vis: ast::Visibility,
struct_def: &ast::StructDef,
generics: Option<&ast::Generics>,
span: Span,
offset: usize) -> String
{
let mut result = String::with_capacity(1024);
let header_str = self.format_header(item_name, ident, vis);
result.push_str(&header_str);
if struct_def.fields.len() == 0 {
result.push(';');
return result;
}
let is_tuple = match struct_def.fields[0].node.kind {
ast::StructFieldKind::NamedField(..) => false,
ast::StructFieldKind::UnnamedField(..) => true
};
let (opener, terminator) = if is_tuple { ("(", ")") } else { (" {", "}") };
let generics_str = match generics {
Some(g) => self.format_generics(g,
opener,
offset + header_str.len(),
codemap::mk_sp(span.lo,
struct_def.fields[0].span.lo)),
None => opener.to_owned()
};
result.push_str(&generics_str);
let items = itemize_list(self.codemap,
Vec::new(),
struct_def.fields.iter(),
",",
terminator,
|field| {
// Include attributes and doc comments,
// if present
if field.node.attrs.len() > 0 {
field.node.attrs[0].span.lo
} else {
field.span.lo
}
},
|field| field.node.ty.span.hi,
|field| self.format_field(field),
span_after(span, opener.trim(), self.codemap),
span.hi);
// 2 terminators and a semicolon
let used_budget = offset + header_str.len() + generics_str.len() + 3;
// Conservative approximation
let single_line_cost = (span.hi - struct_def.fields[0].span.lo).0;
let break_line = !is_tuple ||
generics_str.contains('\n') ||
single_line_cost as usize + used_budget > self.config.max_width;
if break_line {
let indentation = make_indent(offset + self.config.tab_spaces);
result.push('\n');
result.push_str(&indentation);
}
let tactic = if break_line { ListTactic::Vertical } else { ListTactic::Horizontal };
// 1 = ,
let budget = self.config.ideal_width - offset + self.config.tab_spaces - 1;
let fmt = ListFormatting {
tactic: tactic,
separator: ",",
trailing_separator: self.config.struct_trailing_comma,
indent: offset + self.config.tab_spaces,
h_width: self.config.max_width,
v_width: budget,
ends_with_newline: false,
};
result.push_str(&write_list(&items, &fmt));
if break_line {
result.push('\n');
result.push_str(&make_indent(offset));
}
result.push_str(terminator);
if is_tuple {
result.push(';');
}
result
}
pub fn visit_struct(&mut self,
ident: ast::Ident,
vis: ast::Visibility,
@ -523,34 +604,16 @@ pub fn visit_struct(&mut self,
generics: &ast::Generics,
span: Span)
{
let header_str = self.format_header("struct", ident, vis);
self.changes.push_str_span(span, &header_str);
if struct_def.fields.len() == 0 {
assert!(generics.where_clause.predicates.len() == 0,
"No-field struct with where clause?");
assert!(generics.lifetimes.len() == 0, "No-field struct with generics?");
assert!(generics.ty_params.len() == 0, "No-field struct with generics?");
self.changes.push_str_span(span, ";");
return;
}
let generics_str = self.format_generics(generics, struct_def.fields[0].span.lo);
self.changes.push_str_span(span, &generics_str);
let struct_snippet = self.snippet(span);
// This will drop the comment in between the header and body.
self.last_pos = span.lo + BytePos(struct_snippet.find_uncommented("{").unwrap() as u32 + 1);
self.block_indent += self.config.tab_spaces;
for (i, f) in struct_def.fields.iter().enumerate() {
self.visit_field(f, i == struct_def.fields.len() - 1, span.lo, &struct_snippet);
}
self.block_indent -= self.config.tab_spaces;
self.format_missing_with_indent(span.lo + BytePos(struct_snippet.rfind('}').unwrap() as u32));
self.changes.push_str_span(span, "}");
let indent = self.block_indent;
let result = self.format_struct("struct ",
ident,
vis,
struct_def,
Some(generics),
span,
indent);
self.changes.push_str_span(span, &result);
self.last_pos = span.hi;
}
fn format_header(&self,
@ -559,42 +622,37 @@ fn format_header(&self,
vis: ast::Visibility)
-> String
{
format!("{}{} {}", format_visibility(vis), item_name, &token::get_ident(ident))
format!("{}{}{}", format_visibility(vis), item_name, &token::get_ident(ident))
}
fn format_generics(&self,
generics: &ast::Generics,
span_end: BytePos)
opener: &str,
offset: usize,
span: Span)
-> String
{
let mut result = self.rewrite_generics(generics, self.block_indent, span_end);
let mut result = self.rewrite_generics(generics, offset, span);
if generics.where_clause.predicates.len() > 0 {
if generics.where_clause.predicates.len() > 0 || result.contains('\n') {
result.push_str(&self.rewrite_where_clause(&generics.where_clause,
self.block_indent,
span_end));
self.block_indent,
span.hi));
result.push_str(&make_indent(self.block_indent));
result.push_str("\n{");
result.push('\n');
result.push_str(opener.trim());
} else {
result.push_str(" {");
result.push_str(opener);
}
result
}
// Field of a struct
fn visit_field(&mut self,
field: &ast::StructField,
last_field: bool,
// These two args are for missing spans hacks.
struct_start: BytePos,
struct_snippet: &str)
{
if self.visit_attrs(&field.node.attrs) {
return;
fn format_field(&self, field: &ast::StructField) -> String {
if contains_skip(&field.node.attrs) {
return self.snippet(codemap::mk_sp(field.node.attrs[0].span.lo, field.span.hi));
}
self.format_missing_with_indent(field.span.lo);
let name = match field.node.kind {
ast::StructFieldKind::NamedField(ident, _) => Some(token::get_ident(ident)),
@ -606,38 +664,20 @@ fn visit_field(&mut self,
};
let typ = pprust::ty_to_string(&field.node.ty);
let mut field_str = match name {
Some(name) => {
let budget = self.config.ideal_width - self.block_indent;
// 3 is being conservative and assuming that there will be a trailing comma.
if self.block_indent + vis.len() + name.len() + typ.len() + 3 > budget {
format!("{}{}:\n{}{}",
vis,
name,
&make_indent(self.block_indent + self.config.tab_spaces),
typ)
} else {
format!("{}{}: {}", vis, name, typ)
}
}
None => format!("{}{}", vis, typ),
};
if !last_field || self.config.struct_trailing_comma {
field_str.push(',');
let indent = self.block_indent + self.config.tab_spaces;
let mut attr_str = self.rewrite_attrs(&field.node.attrs, indent);
if attr_str.len() > 0 {
attr_str.push('\n');
attr_str.push_str(&make_indent(indent));
}
self.changes.push_str_span(field.span, &field_str);
// This hack makes sure we only add comments etc. after the comma, and
// makes sure we don't repeat any commas.
let hi = field.span.hi;
let comma_pos = match struct_snippet[(hi.0 - struct_start.0) as usize..].find_uncommented(",") {
Some(i) => i,
None => 0,
};
self.last_pos = hi + BytePos(comma_pos as u32 + 1);
match name {
Some(name) => format!("{}{}{}: {}", attr_str, vis, name, typ),
None => format!("{}{}{}", attr_str, vis, typ)
}
}
fn rewrite_generics(&self, generics: &ast::Generics, indent: usize, span_end: BytePos) -> String {
fn rewrite_generics(&self, generics: &ast::Generics, offset: usize, span: Span) -> String {
// FIXME convert bounds to where clauses where they get too big or if
// there is a where clause at all.
let mut result = String::new();
@ -647,7 +687,7 @@ fn rewrite_generics(&self, generics: &ast::Generics, indent: usize, span_end: By
return result;
}
let budget = self.config.max_width - indent - 2;
let budget = self.config.max_width - offset - 2;
// TODO might need to insert a newline if the generics are really long
result.push('<');
@ -665,30 +705,32 @@ fn rewrite_generics(&self, generics: &ast::Generics, indent: usize, span_end: By
codemap::mk_sp(l.lifetime.span.lo, hi)
});
let ty_spans = tys.iter().map(span_for_ty_param);
let comments = self.make_comments_for_list(Vec::new(),
lt_spans.chain(ty_spans),
",",
">",
|sp| sp.lo,
|sp| sp.hi,
span_end);
// If there are // comments, keep them multi-line.
let mut list_tactic = ListTactic::HorizontalVertical;
if comments.iter().any(|c| c.contains("//")) {
list_tactic = ListTactic::Vertical;
let mut items = itemize_list(self.codemap,
Vec::new(),
lt_spans.chain(ty_spans),
",",
">",
|sp| sp.lo,
|sp| sp.hi,
|_| String::new(),
span_after(span, "<", self.codemap),
span.hi);
for (item, ty) in items.iter_mut().zip(lt_strs.chain(ty_strs)) {
item.item = ty;
}
let generics_strs: Vec<_> = lt_strs.chain(ty_strs).zip(comments.into_iter()).collect();
let fmt = ListFormatting {
tactic: list_tactic,
tactic: ListTactic::HorizontalVertical,
separator: ",",
trailing_separator: SeparatorTactic::Never,
indent: indent + 1,
indent: offset + 1,
h_width: budget,
v_width: budget,
ends_with_newline: true,
};
result.push_str(&write_list(&generics_strs, &fmt));
result.push_str(&write_list(&items, &fmt));
result.push('>');
@ -710,18 +752,17 @@ fn rewrite_where_clause(&self,
result.push_str(&make_indent(indent + 4));
result.push_str("where ");
let comments = self.make_comments_for_list(Vec::new(),
where_clause.predicates.iter(),
",",
"{",
|pred| span_for_where_pred(pred).lo,
|pred| span_for_where_pred(pred).hi,
span_end);
let where_strs: Vec<_> = where_clause.predicates.iter()
.map(|p| (self.rewrite_pred(p)))
.zip(comments.into_iter())
.collect();
let span_start = span_for_where_pred(&where_clause.predicates[0]).lo;
let items = itemize_list(self.codemap,
Vec::new(),
where_clause.predicates.iter(),
",",
"{",
|pred| span_for_where_pred(pred).lo,
|pred| span_for_where_pred(pred).hi,
|pred| self.rewrite_pred(pred),
span_start,
span_end);
let budget = self.config.ideal_width + self.config.leeway - indent - 10;
let fmt = ListFormatting {
@ -731,8 +772,9 @@ fn rewrite_where_clause(&self,
indent: indent + 10,
h_width: budget,
v_width: budget,
ends_with_newline: true,
};
result.push_str(&write_list(&where_strs, &fmt));
result.push_str(&write_list(&items, &fmt));
result
}

View File

@ -62,6 +62,8 @@
mod imports;
mod issues;
mod rewrite;
mod string;
mod comment;
const MIN_STRING: usize = 10;
// When we get scoped annotations, we should have rustfmt::skip.

View File

@ -8,7 +8,13 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use utils::make_indent;
use std::cmp;
use syntax::codemap::{self, CodeMap, BytePos};
use utils::{round_up_to_power_of_two, make_indent};
use comment::{FindUncommented, rewrite_comment, find_comment_end};
use string::before;
#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum ListTactic {
@ -41,11 +47,38 @@ pub struct ListFormatting<'a> {
pub h_width: usize,
// Available width if we layout vertically
pub v_width: usize,
// 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
}
// Format a list of strings into a string.
// Precondition: all strings in items are trimmed.
pub fn write_list<'b>(items: &[(String, String)], formatting: &ListFormatting<'b>) -> String {
pub struct ListItem {
pub pre_comment: Option<String>,
// Item should include attributes and doc comments
pub item: String,
pub post_comment: Option<String>
}
impl ListItem {
pub fn is_multiline(&self) -> bool {
self.item.contains('\n') ||
self.pre_comment.is_some() ||
self.post_comment.as_ref().map(|s| s.contains('\n')).unwrap_or(false)
}
pub fn from_str<S: Into<String>>(s: S) -> ListItem {
ListItem {
pre_comment: None,
item: s.into(),
post_comment: None
}
}
}
// Format a list of commented items into a string.
// FIXME: this has grown into a monstrosity
// TODO: add unit tests
pub fn write_list<'b>(items: &[ListItem], formatting: &ListFormatting<'b>) -> String {
if items.len() == 0 {
return String::new();
}
@ -68,7 +101,7 @@ pub fn write_list<'b>(items: &[(String, String)], formatting: &ListFormatting<'b
debug!("write_list: total_width: {}, total_sep_len: {}, h_width: {}",
total_width, total_sep_len, formatting.h_width);
tactic = if fits_single &&
!items.iter().any(|&(ref s, _)| s.contains('\n')) {
!items.iter().any(ListItem::is_multiline) {
ListTactic::Horizontal
} else {
ListTactic::Vertical
@ -81,6 +114,11 @@ pub fn write_list<'b>(items: &[(String, String)], formatting: &ListFormatting<'b
tactic = ListTactic::Horizontal;
}
// Switch to vertical mode if we find non-block comments.
if items.iter().any(has_line_pre_comment) {
tactic = ListTactic::Vertical;
}
// Now that we know how we will layout, we can decide for sure if there
// will be a trailing separator.
let trailing_separator = needs_trailing_separator(formatting.trailing_separator, tactic);
@ -92,13 +130,16 @@ pub fn write_list<'b>(items: &[(String, String)], formatting: &ListFormatting<'b
} else {
total_width + items.len() * (formatting.indent + 1)
};
let mut result = String::with_capacity(alloc_width);
let mut result = String::with_capacity(round_up_to_power_of_two(alloc_width));
let mut line_len = 0;
let indent_str = &make_indent(formatting.indent);
for (i, &(ref item, ref comment)) in items.iter().enumerate() {
for (i, item) in items.iter().enumerate() {
let first = i == 0;
let separate = i != items.len() - 1 || trailing_separator;
let last = i == items.len() - 1;
let separate = !last || trailing_separator;
let item_sep_len = if separate { sep_len } else { 0 };
let item_width = item.item.len() + item_sep_len;
match tactic {
ListTactic::Horizontal if !first => {
@ -109,12 +150,9 @@ pub fn write_list<'b>(items: &[(String, String)], formatting: &ListFormatting<'b
result.push_str(indent_str);
}
ListTactic::Mixed => {
let mut item_width = item.len();
if separate {
item_width += sep_len;
}
let total_width = total_item_width(item) + item_sep_len;
if line_len > 0 && line_len + item_width > formatting.v_width {
if line_len > 0 && line_len + total_width > formatting.v_width {
result.push('\n');
result.push_str(indent_str);
line_len = 0;
@ -125,35 +163,160 @@ pub fn write_list<'b>(items: &[(String, String)], formatting: &ListFormatting<'b
line_len += 1;
}
line_len += item_width;
line_len += total_width;
}
_ => {}
}
result.push_str(item);
// Pre-comments
if let Some(ref comment) = item.pre_comment {
result.push_str(&rewrite_comment(comment,
// Block style in non-vertical mode
tactic != ListTactic::Vertical,
// Width restriction is only
// relevant in vertical mode.
formatting.v_width,
formatting.indent));
if tactic != ListTactic::Vertical && comment.len() > 0 {
if !comment.starts_with('\n') {
if tactic == ListTactic::Vertical {
result.push('\n');
result.push_str(indent_str);
} else {
result.push(' ');
}
result.push_str(comment);
}
result.push_str(&item.item);
// Post-comments
if tactic != ListTactic::Vertical && item.post_comment.is_some() {
let formatted_comment = rewrite_comment(item.post_comment.as_ref().unwrap(),
true,
formatting.v_width,
0);
result.push(' ');
result.push_str(&formatted_comment);
}
if separate {
result.push_str(formatting.separator);
}
if tactic == ListTactic::Vertical && comment.len() > 0 {
if !comment.starts_with('\n') {
result.push(' ');
}
result.push_str(comment);
if tactic == ListTactic::Vertical && item.post_comment.is_some() {
// 1 = space between item and comment.
let width = formatting.v_width.checked_sub(item_width + 1).unwrap_or(1);
let offset = formatting.indent + item_width + 1;
let comment = item.post_comment.as_ref().unwrap();
// 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;
let formatted_comment = rewrite_comment(comment, block_style, width, offset);
result.push(' ');
result.push_str(&formatted_comment);
}
}
result
}
fn has_line_pre_comment(item: &ListItem) -> bool {
match item.pre_comment {
Some(ref comment) => comment.starts_with("//"),
None => false
}
}
// Turns a list into a vector of items with associated comments.
// TODO: we probably do not want to take a terminator any more. Instead, we
// should demand a proper span end.
pub fn itemize_list<T, I, F1, F2, F3>(codemap: &CodeMap,
prefix: Vec<ListItem>,
it: I,
separator: &str,
terminator: &str,
get_lo: F1,
get_hi: F2,
get_item_string: F3,
mut prev_span_end: BytePos,
next_span_start: BytePos)
-> Vec<ListItem>
where I: Iterator<Item=T>,
F1: Fn(&T) -> BytePos,
F2: Fn(&T) -> BytePos,
F3: Fn(&T) -> String
{
let mut result = prefix;
let mut new_it = it.peekable();
let white_space: &[_] = &[' ', '\t'];
while let Some(item) = new_it.next() {
// Pre-comment
let pre_snippet = codemap.span_to_snippet(codemap::mk_sp(prev_span_end,
get_lo(&item)))
.unwrap();
let pre_snippet = pre_snippet.trim();
let pre_comment = if pre_snippet.len() > 0 {
Some(pre_snippet.to_owned())
} else {
None
};
// Post-comment
let next_start = match new_it.peek() {
Some(ref next_item) => get_lo(next_item),
None => next_span_start
};
let post_snippet = codemap.span_to_snippet(codemap::mk_sp(get_hi(&item),
next_start))
.unwrap();
let comment_end = match new_it.peek() {
Some(..) => {
if let Some(start) = before(&post_snippet, "/*", "\n") {
// Block-style post-comment. Either before or after the separator.
cmp::max(find_comment_end(&post_snippet[start..]).unwrap() + start,
post_snippet.find_uncommented(separator).unwrap() + separator.len())
} else if let Some(idx) = post_snippet.find('\n') {
idx + 1
} else {
post_snippet.len()
}
},
None => {
post_snippet.find_uncommented(terminator)
.unwrap_or(post_snippet.len())
}
};
prev_span_end = get_hi(&item) + BytePos(comment_end as u32);
let mut post_snippet = post_snippet[..comment_end].trim();
if post_snippet.starts_with(separator) {
post_snippet = post_snippet[separator.len()..]
.trim_matches(white_space);
} else if post_snippet.ends_with(separator) {
post_snippet = post_snippet[..post_snippet.len()-separator.len()]
.trim_matches(white_space);
}
result.push(ListItem {
pre_comment: pre_comment,
item: get_item_string(&item),
post_comment: if post_snippet.len() > 0 {
Some(post_snippet.to_owned())
} else {
None
}
});
}
result
}
fn needs_trailing_separator(separator_tactic: SeparatorTactic, list_tactic: ListTactic) -> bool {
match separator_tactic {
SeparatorTactic::Always => true,
@ -162,16 +325,25 @@ fn needs_trailing_separator(separator_tactic: SeparatorTactic, list_tactic: List
}
}
fn calculate_width(items:&[(String, String)]) -> usize {
let missed_width = items.iter().map(|&(_, ref s)| {
let text_len = s.trim().len();
if text_len > 0 {
// We'll put a space before any comment.
text_len + 1
} else {
text_len
}
}).fold(0, |a, l| a + l);
let item_width = items.iter().map(|&(ref s, _)| s.len()).fold(0, |a, l| a + l);
missed_width + item_width
fn calculate_width(items: &[ListItem]) -> usize {
items.iter().map(total_item_width).fold(0, |a, l| a + l)
}
fn total_item_width(item: &ListItem) -> usize {
comment_len(&item.pre_comment) + comment_len(&item.post_comment) + item.item.len()
}
fn comment_len(comment: &Option<String>) -> usize {
match comment {
&Some(ref 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
}
}

103
src/string.rs Normal file
View File

@ -0,0 +1,103 @@
// 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 utils::{make_indent, next_char, prev_char, round_up_to_power_of_two};
use MIN_STRING;
pub struct StringFormat<'a> {
pub opener: &'a str,
pub closer: &'a str,
pub line_start: &'a str,
pub line_end: &'a str,
pub width: usize,
pub offset: usize,
pub trim_end: bool,
}
// TODO: simplify this!
pub fn rewrite_string<'a>(s: &str, fmt: &StringFormat<'a>) -> String {
// FIXME I bet this stomps unicode escapes in the source string
// TODO if lo.col > IDEAL - 10, start a new line (need cur indent for that)
let indent = make_indent(fmt.offset);
let indent = &indent;
let mut cur_start = 0;
let mut result = String::with_capacity(round_up_to_power_of_two(s.len()));
result.push_str(fmt.opener);
let ender_length = fmt.line_end.len();
let max_chars = fmt.width.checked_sub(fmt.opener.len()).unwrap_or(0)
.checked_sub(ender_length).unwrap_or(1);
loop {
let mut cur_end = cur_start + max_chars;
if cur_end >= s.len() {
result.push_str(&s[cur_start..]);
break;
}
// Make sure we're on a char boundary.
cur_end = next_char(&s, cur_end);
// Push cur_end left until we reach whitespace.
while !s.char_at(cur_end - 1).is_whitespace() {
cur_end = prev_char(&s, cur_end);
if cur_end - cur_start < MIN_STRING {
// We can't break at whitespace, fall back to splitting
// anywhere that doesn't break an escape sequence.
cur_end = next_char(&s, cur_start + max_chars);
while s.char_at(prev_char(&s, cur_end)) == '\\' {
cur_end = prev_char(&s, cur_end);
}
break;
}
}
// Make sure there is no whitespace to the right of the break.
while cur_end < s.len() && s.char_at(cur_end).is_whitespace() {
cur_end = next_char(&s, cur_end + 1);
}
let line: &str = if fmt.trim_end {
&s[cur_start..cur_end].trim_right_matches(char::is_whitespace)
} else {
&s[cur_start..cur_end]
};
result.push_str(line);
result.push_str(fmt.line_end);
result.push('\n');
result.push_str(indent);
result.push_str(fmt.line_start);
cur_start = cur_end;
}
result.push_str(fmt.closer);
result
}
#[inline]
// Checks if a appears before b in given string and, if so, returns the index of
// a.
// FIXME: could be more generic
pub fn before<'x>(s: &'x str, a: &str, b: &str) -> Option<usize> {
s.find(a).and_then(|i| {
match s.find(b) {
Some(j) if j <= i => None,
_ => Some(i)
}
})
}

View File

@ -8,72 +8,18 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use syntax::ast::Visibility;
use syntax::ast::{Visibility, Attribute, MetaItem, MetaItem_};
use syntax::codemap::{CodeMap, Span, BytePos};
pub trait FindUncommented {
fn find_uncommented(&self, pat: &str) -> Option<usize>;
}
use comment::FindUncommented;
impl FindUncommented for str {
fn find_uncommented(&self, pat: &str) -> Option<usize> {
let mut needle_iter = pat.chars();
let mut possible_comment = false;
use SKIP_ANNOTATION;
for (i, b) in self.char_indices() {
match needle_iter.next() {
Some(c) => {
if b != c {
needle_iter = pat.chars();
}
},
None => return Some(i - pat.len())
}
#[inline]
pub fn span_after(original: Span, needle: &str, codemap: &CodeMap) -> BytePos {
let snippet = codemap.span_to_snippet(original).unwrap();
if possible_comment {
if b == '/' {
return self[(i+1)..].find('\n')
.and_then(|end| {
self[(end + i + 2)..].find_uncommented(pat)
.map(|idx| idx + end + i + 2)
});
} else if b == '*' {
return self[(i+1)..].find("*/")
.and_then(|end| {
self[(end + i + 3)..].find_uncommented(pat)
.map(|idx| idx + end + i + 3)
});
} else {
possible_comment = false;
}
} else {
possible_comment = b == '/';
}
}
// Handle case where the pattern is a suffix of the search string
match needle_iter.next() {
Some(_) => None,
None => Some(self.len() - pat.len())
}
}
}
#[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("π? /**/ π is nice!", "π is nice", Some(9));
check("/*sup yo? \n sup*/ sup", "p", Some(20));
check("hel/*lohello*/lo", "hello", None);
check("acb", "ab", None);
original.lo + BytePos(snippet.find_uncommented(needle).unwrap() as u32 + 1)
}
#[inline]
@ -114,6 +60,18 @@ pub fn format_visibility(vis: Visibility) -> &'static str {
}
}
fn is_skip(meta_item: &MetaItem) -> bool {
match meta_item.node {
MetaItem_::MetaWord(ref s) => *s == SKIP_ANNOTATION,
_ => false,
}
}
#[inline]
pub fn contains_skip(attrs: &[Attribute]) -> bool {
attrs.iter().any(|a| is_skip(&a.node.value))
}
#[inline]
#[cfg(target_pointer_width="64")]
// Based on the trick layed out at

View File

@ -15,7 +15,6 @@
use utils;
use config::Config;
use SKIP_ANNOTATION;
use changes::ChangeSet;
use rewrite::{Rewrite, RewriteContext};
@ -120,7 +119,7 @@ fn visit_fn(&mut self,
constness,
abi,
vis,
b.span.lo);
codemap::mk_sp(s.lo, b.span.lo));
self.changes.push_str_span(s, &new_fn);
}
visit::FkMethod(ident, ref sig, vis) => {
@ -133,7 +132,7 @@ fn visit_fn(&mut self,
&sig.constness,
&sig.abi,
vis.unwrap_or(ast::Visibility::Inherited),
b.span.lo);
codemap::mk_sp(s.lo, b.span.lo));
self.changes.push_str_span(s, &new_fn);
}
visit::FkFnBlock(..) => {}
@ -305,26 +304,22 @@ pub fn visit_attrs(&mut self, attrs: &[ast::Attribute]) -> bool {
let first = &attrs[0];
self.format_missing_with_indent(first.span.lo);
match self.rewrite_attrs(attrs, self.block_indent) {
Some(s) => {
self.changes.push_str_span(first.span, &s);
let last = attrs.last().unwrap();
self.last_pos = last.span.hi;
false
}
None => true
if utils::contains_skip(attrs) {
true
} else {
let rewrite = self.rewrite_attrs(attrs, self.block_indent);
self.changes.push_str_span(first.span, &rewrite);
let last = attrs.last().unwrap();
self.last_pos = last.span.hi;
false
}
}
fn rewrite_attrs(&self, attrs: &[ast::Attribute], indent: usize) -> Option<String> {
pub fn rewrite_attrs(&self, attrs: &[ast::Attribute], indent: usize) -> String {
let mut result = String::new();
let indent = utils::make_indent(indent);
for (i, a) in attrs.iter().enumerate() {
if is_skip(&a.node.value) {
return None;
}
let a_str = self.snippet(a.span);
if i > 0 {
@ -351,13 +346,6 @@ fn rewrite_attrs(&self, attrs: &[ast::Attribute], indent: usize) -> Option<Strin
}
}
Some(result)
}
}
fn is_skip(meta_item: &ast::MetaItem) -> bool {
match meta_item.node {
ast::MetaItem_::MetaWord(ref s) => *s == SKIP_ANNOTATION,
_ => false,
result
}
}

View File

@ -6,7 +6,7 @@ newline_style = "Unix"
fn_brace_style = "SameLineWhere"
fn_return_indent = "WithArgs"
fn_args_paren_newline = true
struct_trailing_comma = true
struct_trailing_comma = "Vertical"
struct_lit_trailing_comma = "Vertical"
enum_trailing_comma = true
report_todo = "Always"

11
tests/source/fn-simple.rs Normal file
View File

@ -0,0 +1,11 @@
fn simple(/*pre-comment on a function!?*/ i: i32/*yes, it's possible! */
,response: NoWay /* hose */) {"cool"}
fn weird_comment(/* /*/ double level */ comment */ x: Hello /*/*/* tripple, even */*/*/,
// Does this work?
y: World
) {
simple(/* does this preserve comments now? */ 42, NoWay)
}

View File

@ -43,7 +43,7 @@ fn qux(a: dadsfa, // Comment 1
/// Blah blah blah.
impl Bar {
fn foo(&mut self, a: sdfsdfcccccccccccccccccccccccccccccccccccccccccccccccccccccccc, // comment on a
fn foo(&mut self, a: sdfsdfcccccccccccccccccccccccccccccccccccccccccccccccccc, // comment on a
b: sdfasdfsdfasfs /*closing comment*/ ) -> isize {}
/// Blah blah blah.
@ -107,21 +107,3 @@ fn main() {
let x = "Hello!!!!!!!!! abcd abcd abcd abcd abcd abcd\n abcd abcd abcd abcd abcd abcd abcd abcd abcd \
abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd \
abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd"; }
fn struct_lits() {
let x = Bar;
// Comment
let y = Foo { a: x };
Foo { a: foo() /* comment*/, /* comment*/ b: bar(), ..something };
Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: foo(), b: bar(), };
Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo {
// Comment
a: foo(), // Comment
// Comment
b: bar(), // Comment
};
Foo { a: Bar,
b: foo() };
}

View File

@ -0,0 +1,37 @@
// Struct literal expressions.
fn main() {
let x = Bar;
// Comment
let y = Foo {a: x };
Foo { a: foo() /* comment*/, /* comment*/ b: bar(), ..something };
Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: foo(), b: bar(), };
Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo {
// Comment
a: foo(), // Comment
// Comment
b: bar(), // Comment
};
Foo { a:Bar,
b:foo() };
A {
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor.
first: item(),
// Praesent et diam eget libero egestas mattis sit amet vitae augue.
// Nam tincidunt congue enim, ut porta lorem lacinia consectetur.
second: Item
};
Diagram { /* o This graph demonstrates how
* / \ significant whitespace is
* o o preserved.
* /|\ \
* o o o o */
graph: G, }
}

70
tests/source/structs.rs Normal file
View File

@ -0,0 +1,70 @@
/// A Doc comment
#[AnAttribute]
pub struct Foo {
#[rustfmt_skip]
f : SomeType, // Comment beside a field
f: SomeType, // Comment beside a field
// Comment on a field
#[AnAttribute]
g: SomeOtherType,
/// A doc comment on a field
h: AThirdType,
pub i: TypeForPublicField
}
struct Bar;
struct NewType(Type, OtherType);
struct
NewInt <T: Copy>(pub i32, SomeType /* inline comment */, T /* sup */
);
struct Qux<'a,
N: Clone + 'a,
E: Clone + 'a,
G: Labeller<'a, N, E> + GraphWalk<'a, N, E>,
W: Write + Copy>
(
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, // Comment
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB,
#[AnAttr]
// Comment
/// Testdoc
G,
pub W,
);
struct Tuple(/*Comment 1*/ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,
/* Comment 2 */ BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB,);
// With a where clause and generics.
pub struct Foo<'a, Y: Baz>
where X: Whatever
{
f: SomeType, // Comment beside a field
}
struct Baz {
a: A, // Comment A
b: B, // Comment B
c: C, // Comment C
}
struct Baz
{
// Comment A
a: A,
// Comment B
b: B,
// Comment C
c: C,}
// Will this be a one-liner?
struct Tuple(
A, //Comment
B
);

View File

@ -2,13 +2,13 @@
// Comment on foo.
fn foo<F, G>(a: aaaaaaaaaaaaa, // A comment
b: bbbbbbbbbbbbb, /* a second comment */
b: bbbbbbbbbbbbb, // a second comment
c: ccccccccccccc,
// Newline comment
d: ddddddddddddd,
// A multi line comment
// between args.
e: eeeeeeeeeeeee /* comment before paren*/)
e: eeeeeeeeeeeee /* comment before paren */)
-> bar
where F: Foo, // COmment after where clause
G: Goo /* final comment */

View File

@ -3,7 +3,7 @@
#[atrr]
pub enum Test {
A,
B(u32, A /* comment */),
B(u32, A /* comment */, SomeType),
/// Doc comment
C,
}
@ -27,8 +27,18 @@ enum Bar {
}
enum LongVariants {
First(LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONG, // small comment
First(LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONG, // comment
VARIANT),
// This is the second variant
Second,
}
enum StructLikeVariants {
Normal(u32, String),
StructLike {
x: i32, // Test comment
// Pre-comment
#[Attr50]
y: SomeType, // Aanother Comment
}
}

16
tests/target/fn-simple.rs Normal file
View File

@ -0,0 +1,16 @@
fn simple(// pre-comment on a function!?
i: i32, // yes, it's possible!
response: NoWay /* hose */) {
"cool"
}
fn weird_comment(// /*/ double level */ comment
x: Hello, // /*/* tripple, even */*/
// Does this work?
y: World) {
simple(// does this preserve comments now?
42,
NoWay)
}

View File

@ -36,10 +36,10 @@ fn foo()
hello!()
}
fn baz<'a: 'b, /* comment on 'a */
fn baz<'a: 'b, // comment on 'a
T: SomsssssssssssssssssssssssssssssssssssssssssssssssssssssseType /* comment on T */>
(a: A,
b: B, /* comment on b */
b: B, // comment on b
c: C)
-> Bob {
#[attr1]
@ -65,8 +65,9 @@ fn qux(a: dadsfa, // Comment 1
/// Blah blah blah.
impl Bar {
fn foo(&mut self,
a: sdfsdfcccccccccccccccccccccccccccccccccccccccccccccccccccccccc, // comment on a
b: sdfasdfsdfasfs /*closing comment*/)
a: sdfsdfcccccccccccccccccccccccccccccccccccccccccccccccccc, /* commen
* t on a */
b: sdfasdfsdfasfs /* closing comment */)
-> isize {
}
@ -138,17 +139,3 @@ fn main() {
abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd abcd \
abcd";
}
fn struct_lits() {
let x = Bar;
// Comment
let y = Foo { a: x };
Foo { a: foo(), b: bar(), ..something };
Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: foo(),
b: bar(), };
Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: foo(),
b: bar(), };
Foo { a: Bar, b: foo() };
}

View File

@ -6,19 +6,45 @@ fn main() {
// Comment
let y = Foo { a: x };
Foo { a: Bar, b: foo() };
Foo { a: foo(), // comment
// comment
b: bar(),
..something };
Foo { a: foo(), b: bar(), ..something };
Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: foo(), b: bar() };
Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: foo(),
b: bar(), };
Fooooooooooooooooooooooooooooooooooooooooooooooooooooo { a: foo(),
b: bar(),
c: bar(),
d: bar(),
e: bar(),
f: bar(),
..baz() };
Foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo { // Comment
a: foo(), /* C
* o
* m
* m
* e
* n
* t */
// Comment
b: bar(), /* C
* o
* m
* m
* e
* n
* t */ };
Foo { a: Bar, b: foo() };
A { // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit
// amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante
// hendrerit. Donec et mollis dolor.
first: item(),
// Praesent et diam eget libero egestas mattis sit amet vitae augue.
// Nam tincidunt congue enim, ut porta lorem lacinia consectetur.
second: Item, };
Diagram { // o This graph demonstrates how
// / \ significant whitespace is
// o o preserved.
// /|\ \
// o o o o
graph: G, }
}

View File

@ -15,6 +15,32 @@ pub struct Foo {
struct Bar;
struct NewType(Type, OtherType);
struct NewInt<T: Copy>(pub i32, SomeType /* inline comment */, T /* sup */);
struct Qux<'a,
N: Clone + 'a,
E: Clone + 'a,
G: Labeller<'a, N, E> + GraphWalk<'a, N, E>,
W: Write + Copy>
(
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, // Comment
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB,
#[AnAttr]
// Comment
/// Testdoc
G,
pub W,
);
struct Tuple(
// Comment 1
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,
// Comment 2
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB,
);
// With a where clause and generics.
pub struct Foo<'a, Y: Baz>
where X: Whatever
@ -36,3 +62,6 @@ struct Baz {
// Comment C
c: C,
}
// Will this be a one-liner?
struct Tuple(A /* Comment */, B);

View File

@ -8,4 +8,8 @@ fn foo() {
aaaaaaaaaaaaaaaaaaaaaaaaa,
aaaa);
let a = (a,);
let b = (// This is a comment
b, // Comment
b /* Trailing comment */);
}