--- /dev/null
+// Copyright 2018 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 attributes and meta items.
+
+use config::lists::*;
+use syntax::ast;
+
+use comment::{combine_strs_with_missing_comments, contains_comment, rewrite_doc_comment};
+use expr::rewrite_literal;
+use lists::{itemize_list, write_list, ListFormatting};
+use rewrite::{Rewrite, RewriteContext};
+use shape::Shape;
+use utils::{count_newlines, mk_sp};
+
+use std::cmp;
+
+/// Returns attributes on the given statement.
+pub fn get_attrs_from_stmt(stmt: &ast::Stmt) -> &[ast::Attribute] {
+ match stmt.node {
+ ast::StmtKind::Local(ref local) => &local.attrs,
+ ast::StmtKind::Item(ref item) => &item.attrs,
+ ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => &expr.attrs,
+ ast::StmtKind::Mac(ref mac) => &mac.2,
+ }
+}
+
+fn is_derive(attr: &ast::Attribute) -> bool {
+ attr.check_name("derive")
+}
+
+/// Returns the arguments of `#[derive(...)]`.
+fn get_derive_args<'a>(context: &'a RewriteContext, attr: &ast::Attribute) -> Option<Vec<&'a str>> {
+ attr.meta_item_list().map(|meta_item_list| {
+ meta_item_list
+ .iter()
+ .map(|nested_meta_item| context.snippet(nested_meta_item.span))
+ .collect()
+ })
+}
+
+// Format `#[derive(..)]`, using visual indent & mixed style when we need to go multiline.
+fn format_derive(context: &RewriteContext, derive_args: &[&str], shape: Shape) -> Option<String> {
+ let mut result = String::with_capacity(128);
+ result.push_str("#[derive(");
+ // 11 = `#[derive()]`
+ let initial_budget = shape.width.checked_sub(11)?;
+ let mut budget = initial_budget;
+ let num = derive_args.len();
+ for (i, a) in derive_args.iter().enumerate() {
+ // 2 = `, ` or `)]`
+ let width = a.len() + 2;
+ if width > budget {
+ if i > 0 {
+ // Remove trailing whitespace.
+ result.pop();
+ }
+ result.push('\n');
+ // 9 = `#[derive(`
+ result.push_str(&(shape.indent + 9).to_string(context.config));
+ budget = initial_budget;
+ } else {
+ budget = budget.checked_sub(width).unwrap_or(0);
+ }
+ result.push_str(a);
+ if i != num - 1 {
+ result.push_str(", ")
+ }
+ }
+ result.push_str(")]");
+ Some(result)
+}
+
+/// Returns the first group of attributes that fills the given predicate.
+/// We consider two doc comments are in different group if they are separated by normal comments.
+fn take_while_with_pred<'a, P>(
+ context: &RewriteContext,
+ attrs: &'a [ast::Attribute],
+ pred: P,
+) -> &'a [ast::Attribute]
+where
+ P: Fn(&ast::Attribute) -> bool,
+{
+ let mut last_index = 0;
+ let mut iter = attrs.iter().enumerate().peekable();
+ while let Some((i, attr)) = iter.next() {
+ if !pred(attr) {
+ break;
+ }
+ if let Some(&(_, next_attr)) = iter.peek() {
+ // Extract comments between two attributes.
+ let span_between_attr = mk_sp(attr.span.hi(), next_attr.span.lo());
+ let snippet = context.snippet(span_between_attr);
+ if count_newlines(snippet) >= 2 || snippet.contains('/') {
+ break;
+ }
+ }
+ last_index = i;
+ }
+ if last_index == 0 {
+ &[]
+ } else {
+ &attrs[..last_index + 1]
+ }
+}
+
+fn rewrite_first_group_attrs(
+ context: &RewriteContext,
+ attrs: &[ast::Attribute],
+ shape: Shape,
+) -> Option<(usize, String)> {
+ if attrs.is_empty() {
+ return Some((0, String::new()));
+ }
+ // Rewrite doc comments
+ let sugared_docs = take_while_with_pred(context, attrs, |a| a.is_sugared_doc);
+ if !sugared_docs.is_empty() {
+ let snippet = sugared_docs
+ .iter()
+ .map(|a| context.snippet(a.span))
+ .collect::<Vec<_>>()
+ .join("\n");
+ return Some((
+ sugared_docs.len(),
+ rewrite_doc_comment(&snippet, 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)?))
+}
+
+impl Rewrite for ast::NestedMetaItem {
+ fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
+ match self.node {
+ ast::NestedMetaItemKind::MetaItem(ref meta_item) => meta_item.rewrite(context, shape),
+ ast::NestedMetaItemKind::Literal(ref l) => rewrite_literal(context, l, shape),
+ }
+ }
+}
+
+fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) {
+ // Look at before and after comment and see if there are any empty lines.
+ let comment_begin = comment.chars().position(|c| c == '/');
+ let len = comment_begin.unwrap_or_else(|| comment.len());
+ let mlb = count_newlines(&comment[..len]) > 1;
+ let mla = if comment_begin.is_none() {
+ mlb
+ } else {
+ let comment_end = comment.chars().rev().position(|c| !c.is_whitespace());
+ let len = comment_end.unwrap();
+ comment
+ .chars()
+ .rev()
+ .take(len)
+ .filter(|c| *c == '\n')
+ .count() > 1
+ };
+ (if mlb { "\n" } else { "" }, if mla { "\n" } else { "" })
+}
+
+impl Rewrite for ast::MetaItem {
+ fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
+ Some(match self.node {
+ ast::MetaItemKind::Word => String::from(&*self.name.as_str()),
+ ast::MetaItemKind::List(ref list) => {
+ let name = self.name.as_str();
+ // 1 = `(`, 2 = `]` and `)`
+ let item_shape = shape
+ .visual_indent(0)
+ .shrink_left(name.len() + 1)
+ .and_then(|s| s.sub_width(2))?;
+ let items = itemize_list(
+ context.snippet_provider,
+ list.iter(),
+ ")",
+ ",",
+ |nested_meta_item| nested_meta_item.span.lo(),
+ |nested_meta_item| nested_meta_item.span.hi(),
+ |nested_meta_item| nested_meta_item.rewrite(context, item_shape),
+ self.span.lo(),
+ self.span.hi(),
+ false,
+ );
+ let item_vec = items.collect::<Vec<_>>();
+ let fmt = ListFormatting {
+ tactic: DefinitiveListTactic::Mixed,
+ separator: ",",
+ trailing_separator: SeparatorTactic::Never,
+ separator_place: SeparatorPlace::Back,
+ shape: item_shape,
+ ends_with_newline: false,
+ preserve_newline: false,
+ config: context.config,
+ };
+ format!("{}({})", name, write_list(&item_vec, &fmt)?)
+ }
+ ast::MetaItemKind::NameValue(ref literal) => {
+ let name = self.name.as_str();
+ // 3 = ` = `
+ let lit_shape = shape.shrink_left(name.len() + 3)?;
+ let value = rewrite_literal(context, literal, lit_shape)?;
+ format!("{} = {}", name, value)
+ }
+ })
+ }
+}
+
+impl Rewrite for ast::Attribute {
+ fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
+ let prefix = match self.style {
+ ast::AttrStyle::Inner => "#!",
+ ast::AttrStyle::Outer => "#",
+ };
+ let snippet = context.snippet(self.span);
+ if self.is_sugared_doc {
+ let doc_shape = Shape {
+ width: cmp::min(shape.width, context.config.comment_width())
+ .checked_sub(shape.indent.width())
+ .unwrap_or(0),
+ ..shape
+ };
+ rewrite_doc_comment(snippet, doc_shape, context.config)
+ } else {
+ if contains_comment(snippet) {
+ return Some(snippet.to_owned());
+ }
+ // 1 = `[`
+ let shape = shape.offset_left(prefix.len() + 1)?;
+ Some(
+ self.meta()
+ .and_then(|meta| meta.rewrite(context, shape))
+ .map_or_else(|| snippet.to_owned(), |rw| format!("{}[{}]", prefix, rw)),
+ )
+ }
+ }
+}
+
+impl<'a> Rewrite for [ast::Attribute] {
+ fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
+ if self.is_empty() {
+ return Some(String::new());
+ }
+ let (first_group_len, first_group_str) = rewrite_first_group_attrs(context, self, shape)?;
+ if self.len() == 1 || first_group_len == self.len() {
+ Some(first_group_str)
+ } else {
+ let rest_str = self[first_group_len..].rewrite(context, shape)?;
+ let missing_span = mk_sp(
+ self[first_group_len - 1].span.hi(),
+ self[first_group_len].span.lo(),
+ );
+ // Preserve an empty line before/after doc comments.
+ if self[0].is_sugared_doc || self[first_group_len].is_sugared_doc {
+ let snippet = context.snippet(missing_span);
+ let (mla, mlb) = has_newlines_before_after_comment(snippet);
+ let comment = ::comment::recover_missing_comment_in_span(
+ missing_span,
+ shape.with_max_width(context.config),
+ context,
+ 0,
+ )?;
+ let comment = if comment.is_empty() {
+ format!("\n{}", mlb)
+ } else {
+ format!("{}{}\n{}", mla, comment, mlb)
+ };
+ Some(format!(
+ "{}{}{}{}",
+ first_group_str,
+ comment,
+ shape.indent.to_string(context.config),
+ rest_str
+ ))
+ } else {
+ combine_strs_with_missing_comments(
+ context,
+ &first_group_str,
+ &rest_str,
+ missing_span,
+ shape,
+ false,
+ )
+ }
+ }
+ }
+}
// option. This file may not be copied, modified, or distributed
// except according to those terms.
-use std::cmp;
-
-use config::lists::*;
use syntax::{ast, visit};
use syntax::attr::HasAttrs;
use syntax::codemap::{self, BytePos, CodeMap, Pos, Span};
use syntax::parse::ParseSess;
+use attr::*;
use codemap::{LineRangeUtils, SpanUtils};
-use comment::{combine_strs_with_missing_comments, contains_comment, CodeCharKind,
- CommentCodeSlices, FindUncommented};
-use comment::rewrite_doc_comment;
+use comment::{contains_comment, CodeCharKind, CommentCodeSlices, FindUncommented};
use config::{BraceStyle, Config};
-use expr::rewrite_literal;
use items::{format_impl, format_trait, format_trait_alias, rewrite_associated_impl_type,
rewrite_associated_type, rewrite_type_alias, FnSig, StaticParts, StructParts};
-use lists::{itemize_list, write_list, ListFormatting};
use macros::{rewrite_macro, rewrite_macro_def, MacroPosition};
use regex::Regex;
use rewrite::{Rewrite, RewriteContext};
}
}
-impl Rewrite for ast::NestedMetaItem {
- fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
- match self.node {
- ast::NestedMetaItemKind::MetaItem(ref meta_item) => meta_item.rewrite(context, shape),
- ast::NestedMetaItemKind::Literal(ref l) => rewrite_literal(context, l, shape),
- }
- }
-}
-
-impl Rewrite for ast::MetaItem {
- fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
- Some(match self.node {
- ast::MetaItemKind::Word => String::from(&*self.name.as_str()),
- ast::MetaItemKind::List(ref list) => {
- let name = self.name.as_str();
- // 1 = `(`, 2 = `]` and `)`
- let item_shape = shape
- .visual_indent(0)
- .shrink_left(name.len() + 1)
- .and_then(|s| s.sub_width(2))?;
- let items = itemize_list(
- context.snippet_provider,
- list.iter(),
- ")",
- ",",
- |nested_meta_item| nested_meta_item.span.lo(),
- |nested_meta_item| nested_meta_item.span.hi(),
- |nested_meta_item| nested_meta_item.rewrite(context, item_shape),
- self.span.lo(),
- self.span.hi(),
- false,
- );
- let item_vec = items.collect::<Vec<_>>();
- let fmt = ListFormatting {
- tactic: DefinitiveListTactic::Mixed,
- separator: ",",
- trailing_separator: SeparatorTactic::Never,
- separator_place: SeparatorPlace::Back,
- shape: item_shape,
- ends_with_newline: false,
- preserve_newline: false,
- config: context.config,
- };
- format!("{}({})", name, write_list(&item_vec, &fmt)?)
- }
- ast::MetaItemKind::NameValue(ref literal) => {
- let name = self.name.as_str();
- // 3 = ` = `
- let lit_shape = shape.shrink_left(name.len() + 3)?;
- let value = rewrite_literal(context, literal, lit_shape)?;
- format!("{} = {}", name, value)
- }
- })
- }
-}
-
-impl Rewrite for ast::Attribute {
- fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
- let prefix = match self.style {
- ast::AttrStyle::Inner => "#!",
- ast::AttrStyle::Outer => "#",
- };
- let snippet = context.snippet(self.span);
- if self.is_sugared_doc {
- let doc_shape = Shape {
- width: cmp::min(shape.width, context.config.comment_width())
- .checked_sub(shape.indent.width())
- .unwrap_or(0),
- ..shape
- };
- rewrite_doc_comment(snippet, doc_shape, context.config)
- } else {
- if contains_comment(snippet) {
- return Some(snippet.to_owned());
- }
- // 1 = `[`
- let shape = shape.offset_left(prefix.len() + 1)?;
- Some(
- self.meta()
- .and_then(|meta| meta.rewrite(context, shape))
- .map_or_else(|| snippet.to_owned(), |rw| format!("{}[{}]", prefix, rw)),
- )
- }
- }
-}
-
-/// Returns the first group of attributes that fills the given predicate.
-/// We consider two doc comments are in different group if they are separated by normal comments.
-fn take_while_with_pred<'a, P>(
- context: &RewriteContext,
- attrs: &'a [ast::Attribute],
- pred: P,
-) -> &'a [ast::Attribute]
-where
- P: Fn(&ast::Attribute) -> bool,
-{
- let mut last_index = 0;
- let mut iter = attrs.iter().enumerate().peekable();
- while let Some((i, attr)) = iter.next() {
- if !pred(attr) {
- break;
- }
- if let Some(&(_, next_attr)) = iter.peek() {
- // Extract comments between two attributes.
- let span_between_attr = mk_sp(attr.span.hi(), next_attr.span.lo());
- let snippet = context.snippet(span_between_attr);
- if count_newlines(snippet) >= 2 || snippet.contains('/') {
- break;
- }
- }
- last_index = i;
- }
- if last_index == 0 {
- &[]
- } else {
- &attrs[..last_index + 1]
- }
-}
-
-fn rewrite_first_group_attrs(
- context: &RewriteContext,
- attrs: &[ast::Attribute],
- shape: Shape,
-) -> Option<(usize, String)> {
- if attrs.is_empty() {
- return Some((0, String::new()));
- }
- // Rewrite doc comments
- let sugared_docs = take_while_with_pred(context, attrs, |a| a.is_sugared_doc);
- if !sugared_docs.is_empty() {
- let snippet = sugared_docs
- .iter()
- .map(|a| context.snippet(a.span))
- .collect::<Vec<_>>()
- .join("\n");
- return Some((
- sugared_docs.len(),
- rewrite_doc_comment(&snippet, shape, context.config)?,
- ));
- }
- // Rewrite `#[derive(..)]`s.
- if context.config.merge_derives() {
- let derives = take_while_with_pred(context, attrs, is_derive);
- if !derives.is_empty() {
- let mut derive_args = vec![];
- for derive in derives {
- derive_args.append(&mut get_derive_args(context, derive)?);
- }
- return Some((derives.len(), format_derive(context, &derive_args, shape)?));
- }
- }
- // Rewrite the first attribute.
- Some((1, attrs[0].rewrite(context, shape)?))
-}
-
-fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) {
- // Look at before and after comment and see if there are any empty lines.
- let comment_begin = comment.chars().position(|c| c == '/');
- let len = comment_begin.unwrap_or_else(|| comment.len());
- let mlb = count_newlines(&comment[..len]) > 1;
- let mla = if comment_begin.is_none() {
- mlb
- } else {
- let comment_end = comment.chars().rev().position(|c| !c.is_whitespace());
- let len = comment_end.unwrap();
- comment
- .chars()
- .rev()
- .take(len)
- .filter(|c| *c == '\n')
- .count() > 1
- };
- (if mlb { "\n" } else { "" }, if mla { "\n" } else { "" })
-}
-
-impl<'a> Rewrite for [ast::Attribute] {
- fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
- if self.is_empty() {
- return Some(String::new());
- }
- let (first_group_len, first_group_str) = rewrite_first_group_attrs(context, self, shape)?;
- if self.len() == 1 || first_group_len == self.len() {
- Some(first_group_str)
- } else {
- let rest_str = self[first_group_len..].rewrite(context, shape)?;
- let missing_span = mk_sp(
- self[first_group_len - 1].span.hi(),
- self[first_group_len].span.lo(),
- );
- // Preserve an empty line before/after doc comments.
- if self[0].is_sugared_doc || self[first_group_len].is_sugared_doc {
- let snippet = context.snippet(missing_span);
- let (mla, mlb) = has_newlines_before_after_comment(snippet);
- let comment = ::comment::recover_missing_comment_in_span(
- missing_span,
- shape.with_max_width(context.config),
- context,
- 0,
- )?;
- let comment = if comment.is_empty() {
- format!("\n{}", mlb)
- } else {
- format!("{}{}\n{}", mla, comment, mlb)
- };
- Some(format!(
- "{}{}{}{}",
- first_group_str,
- comment,
- shape.indent.to_string(context.config),
- rest_str
- ))
- } else {
- combine_strs_with_missing_comments(
- context,
- &first_group_str,
- &rest_str,
- missing_span,
- shape,
- false,
- )
- }
- }
- }
-}
-
-// Format `#[derive(..)]`, using visual indent & mixed style when we need to go multiline.
-fn format_derive(context: &RewriteContext, derive_args: &[&str], shape: Shape) -> Option<String> {
- let mut result = String::with_capacity(128);
- result.push_str("#[derive(");
- // 11 = `#[derive()]`
- let initial_budget = shape.width.checked_sub(11)?;
- let mut budget = initial_budget;
- let num = derive_args.len();
- for (i, a) in derive_args.iter().enumerate() {
- // 2 = `, ` or `)]`
- let width = a.len() + 2;
- if width > budget {
- if i > 0 {
- // Remove trailing whitespace.
- result.pop();
- }
- result.push('\n');
- // 9 = `#[derive(`
- result.push_str(&(shape.indent + 9).to_string(context.config));
- budget = initial_budget;
- } else {
- budget = budget.checked_sub(width).unwrap_or(0);
- }
- result.push_str(a);
- if i != num - 1 {
- result.push_str(", ")
- }
- }
- result.push_str(")]");
- Some(result)
-}
-
-fn is_derive(attr: &ast::Attribute) -> bool {
- attr.check_name("derive")
-}
-
-/// Returns the arguments of `#[derive(...)]`.
-fn get_derive_args<'a>(context: &'a RewriteContext, attr: &ast::Attribute) -> Option<Vec<&'a str>> {
- attr.meta_item_list().map(|meta_item_list| {
- meta_item_list
- .iter()
- .map(|nested_meta_item| context.snippet(nested_meta_item.span))
- .collect()
- })
-}
-
// Rewrite `extern crate foo;` WITHOUT attributes.
pub fn rewrite_extern_crate(context: &RewriteContext, item: &ast::Item) -> Option<String> {
assert!(is_extern_crate(item));
String::from(&*Regex::new(r"\s;").unwrap().replace(no_whitespace, ";"))
})
}
-
-fn get_attrs_from_stmt(stmt: &ast::Stmt) -> &[ast::Attribute] {
- match stmt.node {
- ast::StmtKind::Local(ref local) => &local.attrs,
- ast::StmtKind::Item(ref item) => &item.attrs,
- ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => &expr.attrs,
- ast::StmtKind::Mac(ref mac) => &mac.2,
- }
-}