1 //! Format attributes and meta items.
4 use syntax::source_map::{BytePos, Span, DUMMY_SP};
5 use syntax::symbol::sym;
7 use self::doc_comment::DocCommentFormatter;
8 use crate::comment::{contains_comment, rewrite_doc_comment, CommentStyle};
9 use crate::config::lists::*;
10 use crate::config::IndentStyle;
11 use crate::expr::rewrite_literal;
12 use crate::lists::{definitive_tactic, itemize_list, write_list, ListFormatting, Separator};
14 use crate::rewrite::{Rewrite, RewriteContext};
15 use crate::shape::Shape;
16 use crate::types::{rewrite_path, PathContext};
17 use crate::utils::{count_newlines, mk_sp};
21 /// Returns attributes on the given statement.
22 pub(crate) fn get_attrs_from_stmt(stmt: &ast::Stmt) -> &[ast::Attribute] {
24 ast::StmtKind::Local(ref local) => &local.attrs,
25 ast::StmtKind::Item(ref item) => &item.attrs,
26 ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => &expr.attrs,
27 ast::StmtKind::Mac(ref mac) => &mac.2,
31 pub(crate) fn get_span_without_attrs(stmt: &ast::Stmt) -> Span {
33 ast::StmtKind::Local(ref local) => local.span,
34 ast::StmtKind::Item(ref item) => item.span,
35 ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => expr.span,
36 ast::StmtKind::Mac(ref mac) => {
37 let (ref mac, _, _) = **mac;
43 /// Returns attributes that are within `outer_span`.
44 pub(crate) fn filter_inline_attrs(
45 attrs: &[ast::Attribute],
47 ) -> Vec<ast::Attribute> {
50 .filter(|a| outer_span.lo() <= a.span.lo() && a.span.hi() <= outer_span.hi())
55 fn is_derive(attr: &ast::Attribute) -> bool {
56 attr.check_name(sym::derive)
59 /// Returns the arguments of `#[derive(...)]`.
60 fn get_derive_spans<'a>(attr: &'a ast::Attribute) -> Option<impl Iterator<Item = Span> + 'a> {
61 attr.meta_item_list().map(|meta_item_list| {
64 .map(|nested_meta_item| nested_meta_item.span())
68 // The shape of the arguments to a function-like attribute.
74 context: &RewriteContext<'_>,
76 match context.config.indent_style() {
77 IndentStyle::Block => {
79 shape.offset_left(left)
83 .block_indent(context.config.tab_spaces())
84 .with_max_width(context.config),
88 IndentStyle::Visual => shape
91 .and_then(|s| s.sub_width(right)),
99 context: &RewriteContext<'_>,
100 ) -> Option<String> {
101 let mut result = String::with_capacity(128);
102 result.push_str(prefix);
103 result.push_str("[derive(");
105 let argument_shape = argument_shape(10 + prefix.len(), 2, false, shape, context)?;
106 let item_str = format_arg_list(
110 |sp| Some(context.snippet(**sp).to_owned()),
114 // 10 = "[derive()]", 3 = "()" and "]"
115 shape.offset_left(10 + prefix.len())?.sub_width(3)?,
120 result.push_str(&item_str);
121 if item_str.starts_with('\n') {
123 result.push_str(&shape.indent.to_string_with_newline(context.config));
125 result.push_str(")]");
129 /// Returns the first group of attributes that fills the given predicate.
130 /// We consider two doc comments are in different group if they are separated by normal comments.
131 fn take_while_with_pred<'a, P>(
132 context: &RewriteContext<'_>,
133 attrs: &'a [ast::Attribute],
135 ) -> &'a [ast::Attribute]
137 P: Fn(&ast::Attribute) -> bool,
140 let mut iter = attrs.iter().peekable();
142 while let Some(attr) = iter.next() {
148 if let Some(next_attr) = iter.peek() {
149 // Extract comments between two attributes.
150 let span_between_attr = mk_sp(attr.span.hi(), next_attr.span.lo());
151 let snippet = context.snippet(span_between_attr);
152 if count_newlines(snippet) >= 2 || snippet.contains('/') {
161 /// Rewrite the any doc comments which come before any other attributes.
162 fn rewrite_initial_doc_comments(
163 context: &RewriteContext<'_>,
164 attrs: &[ast::Attribute],
166 ) -> Option<(usize, Option<String>)> {
167 if attrs.is_empty() {
168 return Some((0, None));
170 // Rewrite doc comments
171 let sugared_docs = take_while_with_pred(context, attrs, |a| a.is_sugared_doc);
172 if !sugared_docs.is_empty() {
173 let snippet = sugared_docs
175 .map(|a| context.snippet(a.span))
180 Some(rewrite_doc_comment(
182 shape.comment(context.config),
191 impl Rewrite for ast::NestedMetaItem {
192 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
194 ast::NestedMetaItem::MetaItem(ref meta_item) => meta_item.rewrite(context, shape),
195 ast::NestedMetaItem::Literal(ref l) => rewrite_literal(context, l, shape),
200 fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) {
201 // Look at before and after comment and see if there are any empty lines.
202 let comment_begin = comment.find('/');
203 let len = comment_begin.unwrap_or_else(|| comment.len());
204 let mlb = count_newlines(&comment[..len]) > 1;
205 let mla = if comment_begin.is_none() {
211 .take_while(|c| c.is_whitespace())
212 .filter(|&c| c == '\n')
216 (if mlb { "\n" } else { "" }, if mla { "\n" } else { "" })
219 impl Rewrite for ast::MetaItem {
220 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
221 Some(match self.node {
222 ast::MetaItemKind::Word => {
223 rewrite_path(context, PathContext::Type, None, &self.path, shape)?
225 ast::MetaItemKind::List(ref list) => {
226 let path = rewrite_path(context, PathContext::Type, None, &self.path, shape)?;
227 let has_trailing_comma = crate::expr::span_ends_with_comma(context, self.span);
228 overflow::rewrite_with_parens(
235 context.config.width_heuristics().attr_fn_like_width,
236 Some(if has_trailing_comma {
237 SeparatorTactic::Always
239 SeparatorTactic::Never
243 ast::MetaItemKind::NameValue(ref literal) => {
244 let path = rewrite_path(context, PathContext::Type, None, &self.path, shape)?;
246 let lit_shape = shape.shrink_left(path.len() + 3)?;
247 // `rewrite_literal` returns `None` when `literal` exceeds max
248 // width. Since a literal is basically unformattable unless it
249 // is a string literal (and only if `format_strings` is set),
250 // we might be better off ignoring the fact that the attribute
251 // is longer than the max width and contiue on formatting.
252 // See #2479 for example.
253 let value = rewrite_literal(context, literal, lit_shape)
254 .unwrap_or_else(|| context.snippet(literal.span).to_owned());
255 format!("{} = {}", path, value)
261 fn format_arg_list<I, T, F1, F2, F3>(
267 context: &RewriteContext<'_>,
269 one_line_shape: Shape,
270 one_line_limit: Option<usize>,
274 I: Iterator<Item = T>,
275 F1: Fn(&T) -> BytePos,
276 F2: Fn(&T) -> BytePos,
277 F3: Fn(&T) -> Option<String>,
279 let items = itemize_list(
280 context.snippet_provider,
291 let item_vec = items.collect::<Vec<_>>();
292 let tactic = if let Some(limit) = one_line_limit {
293 ListTactic::LimitedHorizontalVertical(limit)
295 ListTactic::HorizontalVertical
298 let tactic = definitive_tactic(&item_vec, tactic, Separator::Comma, shape.width);
299 let fmt = ListFormatting::new(shape, context.config)
301 .ends_with_newline(false);
302 let item_str = write_list(&item_vec, &fmt)?;
304 let one_line_budget = one_line_shape.width;
305 if context.config.indent_style() == IndentStyle::Visual
307 || (!item_str.contains('\n') && item_str.len() <= one_line_budget)
311 let nested_indent = shape.indent.to_string_with_newline(context.config);
312 Some(format!("{}{}", nested_indent, item_str))
316 impl Rewrite for ast::Attribute {
317 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
318 let snippet = context.snippet(self.span);
319 if self.is_sugared_doc {
320 rewrite_doc_comment(snippet, shape.comment(context.config), context.config)
322 let should_skip = self
324 .map(|s| context.skip_context.skip_attribute(&s.name.as_str()))
326 let prefix = attr_prefix(self);
328 if should_skip || contains_comment(snippet) {
329 return Some(snippet.to_owned());
332 if let Some(ref meta) = self.meta() {
333 // This attribute is possibly a doc attribute needing normalization to a doc comment
334 if context.config.normalize_doc_attributes() && meta.check_name(sym::doc) {
335 if let Some(ref literal) = meta.value_str() {
336 let comment_style = match self.style {
337 ast::AttrStyle::Inner => CommentStyle::Doc,
338 ast::AttrStyle::Outer => CommentStyle::TripleSlash,
341 let literal_str = literal.as_str();
342 let doc_comment_formatter =
343 DocCommentFormatter::new(literal_str.get(), comment_style);
344 let doc_comment = format!("{}", doc_comment_formatter);
345 return rewrite_doc_comment(
347 shape.comment(context.config),
354 let shape = shape.offset_left(prefix.len() + 1)?;
356 meta.rewrite(context, shape)
357 .map_or_else(|| snippet.to_owned(), |rw| format!("{}[{}]", prefix, rw)),
360 Some(snippet.to_owned())
366 impl<'a> Rewrite for [ast::Attribute] {
367 fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
369 return Some(String::new());
372 // The current remaining attributes.
373 let mut attrs = self;
374 let mut result = String::new();
376 // This is not just a simple map because we need to handle doc comments
377 // (where we take as many doc comment attributes as possible) and possibly
378 // merging derives into a single attribute.
380 if attrs.is_empty() {
384 // Handle doc comments.
385 let (doc_comment_len, doc_comment_str) =
386 rewrite_initial_doc_comments(context, attrs, shape)?;
387 if doc_comment_len > 0 {
388 let doc_comment_str = doc_comment_str.expect("doc comments, but no result");
389 result.push_str(&doc_comment_str);
391 let missing_span = attrs
392 .get(doc_comment_len)
393 .map(|next| mk_sp(attrs[doc_comment_len - 1].span.hi(), next.span.lo()));
394 if let Some(missing_span) = missing_span {
395 let snippet = context.snippet(missing_span);
396 let (mla, mlb) = has_newlines_before_after_comment(snippet);
397 let comment = crate::comment::recover_missing_comment_in_span(
399 shape.with_max_width(context.config),
403 let comment = if comment.is_empty() {
406 format!("{}{}\n{}", mla, comment, mlb)
408 result.push_str(&comment);
409 result.push_str(&shape.indent.to_string(context.config));
412 attrs = &attrs[doc_comment_len..];
417 // Handle derives if we will merge them.
418 if context.config.merge_derives() && is_derive(&attrs[0]) {
419 let derives = take_while_with_pred(context, attrs, is_derive);
420 let derive_spans: Vec<_> = derives
422 .filter_map(get_derive_spans)
426 format_derive(&derive_spans, attr_prefix(&attrs[0]), shape, context)?;
427 result.push_str(&derive_str);
429 let missing_span = attrs
431 .map(|next| mk_sp(attrs[derives.len() - 1].span.hi(), next.span.lo()));
432 if let Some(missing_span) = missing_span {
433 let comment = crate::comment::recover_missing_comment_in_span(
435 shape.with_max_width(context.config),
439 result.push_str(&comment);
440 if let Some(next) = attrs.get(derives.len()) {
441 if next.is_sugared_doc {
442 let snippet = context.snippet(missing_span);
443 let (_, mlb) = has_newlines_before_after_comment(snippet);
444 result.push_str(&mlb);
448 result.push_str(&shape.indent.to_string(context.config));
451 attrs = &attrs[derives.len()..];
456 // If we get here, then we have a regular attribute, just handle one
459 let formatted_attr = attrs[0].rewrite(context, shape)?;
460 result.push_str(&formatted_attr);
462 let missing_span = attrs
464 .map(|next| mk_sp(attrs[0].span.hi(), next.span.lo()));
465 if let Some(missing_span) = missing_span {
466 let comment = crate::comment::recover_missing_comment_in_span(
468 shape.with_max_width(context.config),
472 result.push_str(&comment);
473 if let Some(next) = attrs.get(1) {
474 if next.is_sugared_doc {
475 let snippet = context.snippet(missing_span);
476 let (_, mlb) = has_newlines_before_after_comment(snippet);
477 result.push_str(&mlb);
481 result.push_str(&shape.indent.to_string(context.config));
489 fn attr_prefix(attr: &ast::Attribute) -> &'static str {
491 ast::AttrStyle::Inner => "#!",
492 ast::AttrStyle::Outer => "#",
496 pub(crate) trait MetaVisitor<'ast> {
497 fn visit_meta_item(&mut self, meta_item: &'ast ast::MetaItem) {
498 match meta_item.node {
499 ast::MetaItemKind::Word => self.visit_meta_word(meta_item),
500 ast::MetaItemKind::List(ref list) => self.visit_meta_list(meta_item, list),
501 ast::MetaItemKind::NameValue(ref lit) => self.visit_meta_name_value(meta_item, lit),
507 _meta_item: &'ast ast::MetaItem,
508 list: &'ast [ast::NestedMetaItem],
511 self.visit_nested_meta_item(nm);
515 fn visit_meta_word(&mut self, _meta_item: &'ast ast::MetaItem) {}
517 fn visit_meta_name_value(&mut self, _meta_item: &'ast ast::MetaItem, _lit: &'ast ast::Lit) {}
519 fn visit_nested_meta_item(&mut self, nm: &'ast ast::NestedMetaItem) {
521 ast::NestedMetaItem::MetaItem(ref meta_item) => self.visit_meta_item(meta_item),
522 ast::NestedMetaItem::Literal(ref lit) => self.visit_literal(lit),
526 fn visit_literal(&mut self, _lit: &'ast ast::Lit) {}