use expr::rewrite_literal;
use spanned::Spanned;
use codemap::{LineRangeUtils, SpanUtils};
-use comment::{contains_comment, recover_missing_comment_in_span, remove_trailing_white_spaces,
+use comment::{combine_strs_with_missing_comments, contains_comment, remove_trailing_white_spaces,
CodeCharKind, CommentCodeSlices, FindUncommented};
use comment::rewrite_comment;
use config::{BraceStyle, Config};
-use items::{format_impl, format_struct, format_struct_struct, format_trait,
- rewrite_associated_impl_type, rewrite_associated_type, rewrite_static,
- rewrite_type_alias, FnSig};
+use items::{format_impl, format_trait, rewrite_associated_impl_type, rewrite_associated_type,
+ rewrite_type_alias, FnSig, StaticParts, StructParts};
use lists::{itemize_list, write_list, DefinitiveListTactic, ListFormatting, SeparatorPlace,
SeparatorTactic};
use macros::{rewrite_macro, MacroPosition};
}
}
- let mut unindent_comment = self.is_if_else_block && !b.stmts.is_empty();
- if unindent_comment {
+ let unindent_comment = (self.is_if_else_block && !b.stmts.is_empty()) && {
let end_pos = source!(self, b.span).hi() - brace_compensation - remove_len;
let snippet = self.snippet(mk_sp(self.last_pos, end_pos));
- unindent_comment = snippet.contains("//") || snippet.contains("/*");
- }
+ snippet.contains("//") || snippet.contains("/*")
+ };
// FIXME: we should compress any newlines here to just one
if unindent_comment {
self.block_indent = self.block_indent.block_unindent(self.config);
generics: &ast::Generics,
fd: &ast::FnDecl,
s: Span,
- _: ast::NodeId,
defaultness: ast::Defaultness,
inner_attrs: Option<&[ast::Attribute]>,
) {
let indent = self.block_indent;
let block;
let rewrite = match fk {
- visit::FnKind::ItemFn(ident, _, _, _, _, b) => {
- block = b;
- self.rewrite_fn(
- indent,
- ident,
- &FnSig::from_fn_kind(&fk, generics, fd, defaultness),
- mk_sp(s.lo(), b.span.lo()),
- b,
- )
- }
- visit::FnKind::Method(ident, _, _, b) => {
+ visit::FnKind::ItemFn(ident, _, _, _, _, b) | visit::FnKind::Method(ident, _, _, b) => {
block = b;
self.rewrite_fn(
indent,
let rw = rewrite_extern_crate(&self.get_context(), item);
self.push_rewrite(item.span, rw);
}
- ast::ItemKind::Struct(ref def, ref generics) => {
- let rewrite = format_struct(
- &self.get_context(),
- "struct ",
- item.ident,
- &item.vis,
- def,
- Some(generics),
- item.span,
- self.block_indent,
- None,
- ).map(|s| match *def {
- ast::VariantData::Tuple(..) => s + ";",
- _ => s,
- });
- self.push_rewrite(item.span, rewrite);
+ ast::ItemKind::Struct(..) | ast::ItemKind::Union(..) => {
+ self.visit_struct(&StructParts::from_item(item));
}
ast::ItemKind::Enum(ref def, ref generics) => {
self.format_missing_with_indent(source!(self, item.span).lo());
self.format_missing_with_indent(source!(self, item.span).lo());
self.format_foreign_mod(foreign_mod, item.span);
}
- ast::ItemKind::Static(ref ty, mutability, ref expr) => {
- let rewrite = rewrite_static(
- "static",
- &item.vis,
- item.ident,
- ty,
- mutability,
- Some(expr),
- self.block_indent,
- item.span,
- &self.get_context(),
- );
- self.push_rewrite(item.span, rewrite);
- }
- ast::ItemKind::Const(ref ty, ref expr) => {
- let rewrite = rewrite_static(
- "const",
- &item.vis,
- item.ident,
- ty,
- ast::Mutability::Immutable,
- Some(expr),
- self.block_indent,
- item.span,
- &self.get_context(),
- );
- self.push_rewrite(item.span, rewrite);
+ ast::ItemKind::Static(..) | ast::ItemKind::Const(..) => {
+ self.visit_static(&StaticParts::from_item(item));
}
ast::ItemKind::AutoImpl(..) => {
// FIXME(#78): format impl definitions.
generics,
decl,
item.span,
- item.id,
ast::Defaultness::Final,
Some(&item.attrs),
)
);
self.push_rewrite(item.span, rewrite);
}
- ast::ItemKind::Union(ref def, ref generics) => {
- let rewrite = format_struct_struct(
- &self.get_context(),
- "union ",
- item.ident,
- &item.vis,
- def.fields(),
- Some(generics),
- item.span,
- self.block_indent,
- None,
- );
- self.push_rewrite(item.span, rewrite);
- }
ast::ItemKind::GlobalAsm(..) => {
let snippet = Some(self.snippet(item.span));
self.push_rewrite(item.span, snippet);
}
match ti.node {
- ast::TraitItemKind::Const(ref ty, ref expr_opt) => {
- let rewrite = rewrite_static(
- "const",
- &ast::Visibility::Inherited,
- ti.ident,
- ty,
- ast::Mutability::Immutable,
- expr_opt.as_ref(),
- self.block_indent,
- ti.span,
- &self.get_context(),
- );
- self.push_rewrite(ti.span, rewrite);
- }
+ ast::TraitItemKind::Const(..) => self.visit_static(&StaticParts::from_trait_item(ti)),
ast::TraitItemKind::Method(ref sig, None) => {
let indent = self.block_indent;
let rewrite =
&ti.generics,
&sig.decl,
ti.span,
- ti.id,
ast::Defaultness::Final,
Some(&ti.attrs),
);
&ii.generics,
&sig.decl,
ii.span,
- ii.id,
ii.defaultness,
Some(&ii.attrs),
);
}
- ast::ImplItemKind::Const(ref ty, ref expr) => {
- let rewrite = rewrite_static(
- "const",
- &ii.vis,
- ii.ident,
- ty,
- ast::Mutability::Immutable,
- Some(expr),
- self.block_indent,
- ii.span,
- &self.get_context(),
- );
- self.push_rewrite(ii.span, rewrite);
- }
+ ast::ImplItemKind::Const(..) => self.visit_static(&StaticParts::from_impl_item(ii)),
ast::ImplItemKind::Type(ref ty) => {
let rewrite = rewrite_associated_impl_type(
ii.ident,
}
}
+ pub fn opt_snippet(&self, span: Span) -> Option<String> {
+ self.codemap.span_to_snippet(span).ok()
+ }
+
pub fn snippet(&self, span: Span) -> String {
match self.codemap.span_to_snippet(span) {
Ok(s) => s,
self.format_missing_with_indent(filemap.end_pos);
}
+ pub fn skip_empty_lines(&mut self, end_pos: BytePos) {
+ while let Some(pos) = self.codemap
+ .opt_span_after(mk_sp(self.last_pos, end_pos), "\n")
+ {
+ if let Some(snippet) = self.opt_snippet(mk_sp(self.last_pos, pos)) {
+ if snippet.trim().is_empty() {
+ self.last_pos = pos;
+ } else {
+ return;
+ }
+ }
+ }
+ }
+
pub fn get_context(&self) -> RewriteContext {
RewriteContext {
parse_session: self.parse_session,
}
}
+/// Returns the first group of attributes that fills the given predicate.
+/// We consider two doc comments are in different group if they are separated by normal comments.
+fn take_while_with_pred<'a, P>(
+ context: &RewriteContext,
+ attrs: &'a [ast::Attribute],
+ pred: P,
+) -> &'a [ast::Attribute]
+where
+ P: Fn(&ast::Attribute) -> bool,
+{
+ let mut last_index = 0;
+ let mut iter = attrs.iter().enumerate().peekable();
+ while let Some((i, attr)) = iter.next() {
+ if !pred(attr) {
+ break;
+ }
+ if let Some(&(_, next_attr)) = iter.peek() {
+ // Extract comments between two attributes.
+ let span_between_attr = mk_sp(attr.span.hi(), next_attr.span.lo());
+ let snippet = context.snippet(span_between_attr);
+ if snippet.chars().filter(|c| *c == '\n').count() >= 2 || snippet.contains('/') {
+ break;
+ }
+ }
+ last_index = i;
+ }
+ if last_index == 0 {
+ &[]
+ } else {
+ &attrs[..last_index + 1]
+ }
+}
+
+fn rewrite_first_group_attrs(
+ context: &RewriteContext,
+ attrs: &[ast::Attribute],
+ shape: Shape,
+) -> Option<(usize, String)> {
+ if attrs.is_empty() {
+ return Some((0, String::new()));
+ }
+ // Rewrite doc comments
+ let sugared_docs = take_while_with_pred(context, attrs, |a| a.is_sugared_doc);
+ if !sugared_docs.is_empty() {
+ let snippet = sugared_docs
+ .iter()
+ .map(|a| context.snippet(a.span))
+ .collect::<Vec<_>>()
+ .join("\n");
+ return Some((
+ sugared_docs.len(),
+ rewrite_comment(&snippet, false, shape, context.config)?,
+ ));
+ }
+ // Rewrite `#[derive(..)]`s.
+ if context.config.merge_derives() {
+ let derives = take_while_with_pred(context, attrs, is_derive);
+ if !derives.is_empty() {
+ let mut derive_args = vec![];
+ for derive in derives {
+ derive_args.append(&mut get_derive_args(context, derive)?);
+ }
+ return Some((
+ derives.len(),
+ format_derive(context, &derive_args, shape)?,
+ ));
+ }
+ }
+ // Rewrite the first attribute.
+ Some((1, attrs[0].rewrite(context, shape)?))
+}
+
+fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) {
+ // Look at before and after comment and see if there are any empty lines.
+ let comment_begin = comment.chars().position(|c| c == '/');
+ let len = comment_begin.unwrap_or_else(|| comment.len());
+ let mlb = comment.chars().take(len).filter(|c| *c == '\n').count() > 1;
+ let mla = if comment_begin.is_none() {
+ mlb
+ } else {
+ let comment_end = comment.chars().rev().position(|c| !c.is_whitespace());
+ let len = comment_end.unwrap();
+ comment
+ .chars()
+ .rev()
+ .take(len)
+ .filter(|c| *c == '\n')
+ .count() > 1
+ };
+ (if mlb { "\n" } else { "" }, if mla { "\n" } else { "" })
+}
+
impl<'a> Rewrite for [ast::Attribute] {
fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
if self.is_empty() {
return Some(String::new());
}
- let mut result = String::with_capacity(128);
- let indent = shape.indent.to_string(context.config);
-
- let mut derive_args = Vec::new();
-
- let mut iter = self.iter().enumerate().peekable();
- let mut insert_new_line = true;
- let mut is_prev_sugared_doc = false;
- while let Some((i, a)) = iter.next() {
- let a_str = a.rewrite(context, shape)?;
-
- // Write comments and blank lines between attributes.
- if i > 0 {
- let comment = context.snippet(mk_sp(self[i - 1].span.hi(), a.span.lo()));
- // This particular horror show is to preserve line breaks in between doc
- // comments. An alternative would be to force such line breaks to start
- // with the usual doc comment token.
- let (multi_line_before, multi_line_after) = if a.is_sugared_doc
- || is_prev_sugared_doc
- {
- // Look at before and after comment and see if there are any empty lines.
- let comment_begin = comment.chars().position(|c| c == '/');
- let len = comment_begin.unwrap_or_else(|| comment.len());
- let mlb = comment.chars().take(len).filter(|c| *c == '\n').count() > 1;
- let mla = if comment_begin.is_none() {
- mlb
- } else {
- let comment_end = comment.chars().rev().position(|c| !c.is_whitespace());
- let len = comment_end.unwrap();
- comment
- .chars()
- .rev()
- .take(len)
- .filter(|c| *c == '\n')
- .count() > 1
- };
- (mlb, mla)
- } else {
- (false, false)
- };
-
- let comment = recover_missing_comment_in_span(
- mk_sp(self[i - 1].span.hi(), a.span.lo()),
+ let (first_group_len, first_group_str) = rewrite_first_group_attrs(context, self, shape)?;
+ if self.len() == 1 || first_group_len == self.len() {
+ Some(first_group_str)
+ } else {
+ let rest_str = self[first_group_len..].rewrite(context, shape)?;
+ let missing_span = mk_sp(
+ self[first_group_len - 1].span.hi(),
+ self[first_group_len].span.lo(),
+ );
+ // Preserve an empty line before/after doc comments.
+ if self[0].is_sugared_doc || self[first_group_len].is_sugared_doc {
+ let snippet = context.snippet(missing_span);
+ let (mla, mlb) = has_newlines_before_after_comment(&snippet);
+ let comment = ::comment::recover_missing_comment_in_span(
+ missing_span,
shape.with_max_width(context.config),
context,
0,
)?;
-
- if !comment.is_empty() {
- if multi_line_before {
- result.push('\n');
- }
- result.push_str(&comment);
- result.push('\n');
- if multi_line_after {
- result.push('\n')
- }
- } else if insert_new_line {
- result.push('\n');
- if multi_line_after {
- result.push('\n')
- }
- }
-
- if derive_args.is_empty() {
- result.push_str(&indent);
- }
-
- insert_new_line = true;
- }
-
- // Write the attribute itself.
- if context.config.merge_derives() {
- // If the attribute is `#[derive(...)]`, take the arguments.
- if let Some(mut args) = get_derive_args(context, a) {
- derive_args.append(&mut args);
- match iter.peek() {
- // If the next attribute is `#[derive(...)]` as well, skip rewriting.
- Some(&(_, next_attr)) if is_derive(next_attr) => insert_new_line = false,
- // If not, rewrite the merged derives.
- _ => {
- result.push_str(&format_derive(context, &derive_args, shape)?);
- derive_args.clear();
- }
- }
+ let comment = if comment.is_empty() {
+ format!("\n{}", mlb)
} else {
- result.push_str(&a_str);
- }
+ format!("{}{}\n{}", mla, comment, mlb)
+ };
+ Some(format!(
+ "{}{}{}{}",
+ first_group_str,
+ comment,
+ shape.indent.to_string(context.config),
+ rest_str
+ ))
} else {
- result.push_str(&a_str);
+ combine_strs_with_missing_comments(
+ context,
+ &first_group_str,
+ &rest_str,
+ missing_span,
+ shape,
+ false,
+ )
}
-
- is_prev_sugared_doc = a.is_sugared_doc;
}
- Some(result)
}
}