diff --git a/src/comment.rs b/src/comment.rs new file mode 100644 index 00000000000..a1338d5b369 --- /dev/null +++ b/src/comment.rs @@ -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 or the MIT license +// , 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; +} + +impl FindUncommented for str { + fn find_uncommented(&self, pat: &str) -> Option { + 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) { + 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 { + 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.")); +} diff --git a/src/config.rs b/src/config.rs index 23285167689..8bcf12854a8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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, diff --git a/src/default.toml b/src/default.toml index 75ab3a60164..2d5ba9c03ee 100644 --- a/src/default.toml +++ b/src/default.toml @@ -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" diff --git a/src/expr.rs b/src/expr.rs index dae64891ab3..d66630e5179 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -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 { match self.node { @@ -33,20 +32,22 @@ impl Rewrite for ast::Expr { } } 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 @@ impl Rewrite for ast::Expr { } } -fn rewrite_string_lit(context: &RewriteContext, s: &str, span: Span, width: usize, offset: usize) -> Option { - // 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 { // 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], + span: Span, width: usize, offset: usize) - -> Option -{ + -> Option { 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 { @@ -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 { 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 { @@ -230,43 +232,43 @@ fn rewrite_field(context: &RewriteContext, field: &ast::Field, width: usize, off fn rewrite_tuple_lit(context: &RewriteContext, items: &[ptr::P], + span: Span, width: usize, offset: usize) -> Option { - // 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))) +} diff --git a/src/imports.rs b/src/imports.rs index d4fe81218b8..de33be849ba 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -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 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { false } ) { - Some(("self".to_owned(), String::new())) + Some(ListItem::from_str("self")) } else { None }; @@ -102,12 +95,13 @@ impl<'a> FmtVisitor<'a> { 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 { diff --git a/src/issues.rs b/src/issues.rs index 9ee70e1aadc..0efe0c31c78 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -223,7 +223,6 @@ impl BadIssueSeeker { #[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())); } diff --git a/src/items.rs b/src/items.rs index 2dfa8a5fe65..17941dcd242 100644 --- a/src/items.rs +++ b/src/items.rs @@ -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 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { constness, abi, vis, - span_end, + span, newline_brace); // Prepare for the function body by possibly adding a newline and indent. @@ -68,7 +70,7 @@ impl<'a> FmtVisitor<'a> { -> 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 @@ impl<'a> FmtVisitor<'a> { &sig.constness, &sig.abi, ast::Visibility::Inherited, - span_end, + span, false); // Re-attach semicolon @@ -98,7 +100,7 @@ impl<'a> FmtVisitor<'a> { constness: &ast::Constness, abi: &abi::Abi, vis: ast::Visibility, - span_end: BytePos, + span: Span, newline_brace: bool) -> String { @@ -131,7 +133,8 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { // 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 @@ impl<'a> FmtVisitor<'a> { } // 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 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { } // 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(&self, - prefix: Vec, - mut it: I, - separator: &str, - terminator: &str, - get_lo: F1, - get_hi: F2, - next_span_start: BytePos) - -> Vec - where I: Iterator, - 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 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { }; 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 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { 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 @@ impl<'a> FmtVisitor<'a> { 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 } diff --git a/src/lib.rs b/src/lib.rs index dfcf4c70424..5ee68e70cf5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,6 +62,8 @@ mod expr; 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. diff --git a/src/lists.rs b/src/lists.rs index 9f1006c39f0..adabb00566c 100644 --- a/src/lists.rs +++ b/src/lists.rs @@ -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, + // Item should include attributes and doc comments + pub item: String, + pub post_comment: Option +} + +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: 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(codemap: &CodeMap, + prefix: Vec, + 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 + where I: Iterator, + 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) -> 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 + } } diff --git a/src/string.rs b/src/string.rs new file mode 100644 index 00000000000..d474fdb468e --- /dev/null +++ b/src/string.rs @@ -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 or the MIT license +// , 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 { + s.find(a).and_then(|i| { + match s.find(b) { + Some(j) if j <= i => None, + _ => Some(i) + } + }) +} diff --git a/src/utils.rs b/src/utils.rs index 795c0d98309..de17f989f7a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -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; -} +use comment::FindUncommented; -impl FindUncommented for str { - fn find_uncommented(&self, pat: &str) -> Option { - 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) { - 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 diff --git a/src/visitor.rs b/src/visitor.rs index 88b5d4b18dc..ebd59f0cf20 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -15,7 +15,6 @@ use syntax::visit; use utils; use config::Config; -use SKIP_ANNOTATION; use changes::ChangeSet; use rewrite::{Rewrite, RewriteContext}; @@ -120,7 +119,7 @@ impl<'a, 'v> visit::Visitor<'v> for FmtVisitor<'a> { 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 @@ impl<'a, 'v> visit::Visitor<'v> for FmtVisitor<'a> { &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 @@ impl<'a> FmtVisitor<'a> { 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 { + 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 @@ impl<'a> FmtVisitor<'a> { } } - Some(result) - } -} - -fn is_skip(meta_item: &ast::MetaItem) -> bool { - match meta_item.node { - ast::MetaItem_::MetaWord(ref s) => *s == SKIP_ANNOTATION, - _ => false, + result } } diff --git a/tests/config/small_tabs.toml b/tests/config/small_tabs.toml index 68824838644..da39ac1a8f4 100644 --- a/tests/config/small_tabs.toml +++ b/tests/config/small_tabs.toml @@ -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" diff --git a/tests/source/fn-simple.rs b/tests/source/fn-simple.rs new file mode 100644 index 00000000000..9bcf3166722 --- /dev/null +++ b/tests/source/fn-simple.rs @@ -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) +} diff --git a/tests/source/multiple.rs b/tests/source/multiple.rs index 024817353da..04ecb2bc734 100644 --- a/tests/source/multiple.rs +++ b/tests/source/multiple.rs @@ -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() }; -} diff --git a/tests/source/struct_lits.rs b/tests/source/struct_lits.rs new file mode 100644 index 00000000000..b7e1a854b0b --- /dev/null +++ b/tests/source/struct_lits.rs @@ -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, } +} diff --git a/tests/source/structs.rs b/tests/source/structs.rs new file mode 100644 index 00000000000..ace13fe2889 --- /dev/null +++ b/tests/source/structs.rs @@ -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 (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 +); diff --git a/tests/target/comments-fn.rs b/tests/target/comments-fn.rs index 748a6edb6ca..0b21d84074a 100644 --- a/tests/target/comments-fn.rs +++ b/tests/target/comments-fn.rs @@ -2,13 +2,13 @@ // Comment on foo. fn foo(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 */ diff --git a/tests/target/enum.rs b/tests/target/enum.rs index 2f7b3bb66a9..8c534ef89c8 100644 --- a/tests/target/enum.rs +++ b/tests/target/enum.rs @@ -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 + } +} diff --git a/tests/target/fn-simple.rs b/tests/target/fn-simple.rs new file mode 100644 index 00000000000..eb133d568ed --- /dev/null +++ b/tests/target/fn-simple.rs @@ -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) +} diff --git a/tests/target/multiple.rs b/tests/target/multiple.rs index 0e89af29c15..0fb0c94e3a8 100644 --- a/tests/target/multiple.rs +++ b/tests/target/multiple.rs @@ -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() }; -} diff --git a/tests/target/struct_lits.rs b/tests/target/struct_lits.rs index 1d32171d7d0..c46909825a1 100644 --- a/tests/target/struct_lits.rs +++ b/tests/target/struct_lits.rs @@ -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, } } diff --git a/tests/target/structs.rs b/tests/target/structs.rs index 507c9eba46b..84b7334008d 100644 --- a/tests/target/structs.rs +++ b/tests/target/structs.rs @@ -15,6 +15,32 @@ pub struct Foo { struct Bar; +struct NewType(Type, OtherType); + +struct NewInt(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); diff --git a/tests/target/tuple.rs b/tests/target/tuple.rs index 3655cab0663..2d126cacf96 100644 --- a/tests/target/tuple.rs +++ b/tests/target/tuple.rs @@ -8,4 +8,8 @@ fn foo() { aaaaaaaaaaaaaaaaaaaaaaaaa, aaaa); let a = (a,); + + let b = (// This is a comment + b, // Comment + b /* Trailing comment */); }