]> git.lizzy.rs Git - rust.git/blob - src/tools/rustfmt/src/attr.rs
Merge commit 'efa8f5521d3813cc897ba29ea0ef98c7aef66bb6' into rustfmt-subtree
[rust.git] / src / tools / rustfmt / src / attr.rs
1 //! Format attributes and meta items.
2
3 use rustc_ast::ast;
4 use rustc_ast::AstLike;
5 use rustc_span::{symbol::sym, Span, Symbol};
6
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};
13 use crate::overflow;
14 use crate::rewrite::{Rewrite, RewriteContext};
15 use crate::shape::Shape;
16 use crate::source_map::SpanUtils;
17 use crate::types::{rewrite_path, PathContext};
18 use crate::utils::{count_newlines, mk_sp};
19
20 mod doc_comment;
21
22 pub(crate) fn contains_name(attrs: &[ast::Attribute], name: Symbol) -> bool {
23     attrs.iter().any(|attr| attr.has_name(name))
24 }
25
26 pub(crate) fn first_attr_value_str_by_name(
27     attrs: &[ast::Attribute],
28     name: Symbol,
29 ) -> Option<Symbol> {
30     attrs
31         .iter()
32         .find(|attr| attr.has_name(name))
33         .and_then(|attr| attr.value_str())
34 }
35
36 /// Returns attributes on the given statement.
37 pub(crate) fn get_attrs_from_stmt(stmt: &ast::Stmt) -> &[ast::Attribute] {
38     stmt.attrs()
39 }
40
41 pub(crate) fn get_span_without_attrs(stmt: &ast::Stmt) -> Span {
42     match stmt.kind {
43         ast::StmtKind::Local(ref local) => local.span,
44         ast::StmtKind::Item(ref item) => item.span,
45         ast::StmtKind::Expr(ref expr) | ast::StmtKind::Semi(ref expr) => expr.span,
46         ast::StmtKind::MacCall(ref mac_stmt) => mac_stmt.mac.span(),
47         ast::StmtKind::Empty => stmt.span,
48     }
49 }
50
51 /// Returns attributes that are within `outer_span`.
52 pub(crate) fn filter_inline_attrs(
53     attrs: &[ast::Attribute],
54     outer_span: Span,
55 ) -> Vec<ast::Attribute> {
56     attrs
57         .iter()
58         .filter(|a| outer_span.lo() <= a.span.lo() && a.span.hi() <= outer_span.hi())
59         .cloned()
60         .collect()
61 }
62
63 fn is_derive(attr: &ast::Attribute) -> bool {
64     attr.has_name(sym::derive)
65 }
66
67 // The shape of the arguments to a function-like attribute.
68 fn argument_shape(
69     left: usize,
70     right: usize,
71     combine: bool,
72     shape: Shape,
73     context: &RewriteContext<'_>,
74 ) -> Option<Shape> {
75     match context.config.indent_style() {
76         IndentStyle::Block => {
77             if combine {
78                 shape.offset_left(left)
79             } else {
80                 Some(
81                     shape
82                         .block_indent(context.config.tab_spaces())
83                         .with_max_width(context.config),
84                 )
85             }
86         }
87         IndentStyle::Visual => shape
88             .visual_indent(0)
89             .shrink_left(left)
90             .and_then(|s| s.sub_width(right)),
91     }
92 }
93
94 fn format_derive(
95     derives: &[ast::Attribute],
96     shape: Shape,
97     context: &RewriteContext<'_>,
98 ) -> Option<String> {
99     // Collect all items from all attributes
100     let all_items = derives
101         .iter()
102         .map(|attr| {
103             // Parse the derive items and extract the span for each item; if any
104             // attribute is not parseable, none of the attributes will be
105             // reformatted.
106             let item_spans = attr.meta_item_list().map(|meta_item_list| {
107                 meta_item_list
108                     .into_iter()
109                     .map(|nested_meta_item| nested_meta_item.span())
110             })?;
111
112             let items = itemize_list(
113                 context.snippet_provider,
114                 item_spans,
115                 ")",
116                 ",",
117                 |span| span.lo(),
118                 |span| span.hi(),
119                 |span| Some(context.snippet(*span).to_owned()),
120                 // We update derive attribute spans to start after the opening '('
121                 // This helps us focus parsing to just what's inside #[derive(...)]
122                 context.snippet_provider.span_after(attr.span, "("),
123                 attr.span.hi(),
124                 false,
125             );
126
127             Some(items)
128         })
129         // Fail if any attribute failed.
130         .collect::<Option<Vec<_>>>()?
131         // Collect the results into a single, flat, Vec.
132         .into_iter()
133         .flatten()
134         .collect::<Vec<_>>();
135
136     // Collect formatting parameters.
137     let prefix = attr_prefix(&derives[0]);
138     let argument_shape = argument_shape(
139         "[derive()]".len() + prefix.len(),
140         ")]".len(),
141         false,
142         shape,
143         context,
144     )?;
145     let one_line_shape = shape
146         .offset_left("[derive()]".len() + prefix.len())?
147         .sub_width("()]".len())?;
148     let one_line_budget = one_line_shape.width;
149
150     let tactic = definitive_tactic(
151         &all_items,
152         ListTactic::HorizontalVertical,
153         Separator::Comma,
154         argument_shape.width,
155     );
156     let trailing_separator = match context.config.indent_style() {
157         // We always add the trailing comma and remove it if it is not needed.
158         IndentStyle::Block => SeparatorTactic::Always,
159         IndentStyle::Visual => SeparatorTactic::Never,
160     };
161
162     // Format the collection of items.
163     let fmt = ListFormatting::new(argument_shape, context.config)
164         .tactic(tactic)
165         .trailing_separator(trailing_separator)
166         .ends_with_newline(false);
167     let item_str = write_list(&all_items, &fmt)?;
168
169     debug!("item_str: '{}'", item_str);
170
171     // Determine if the result will be nested, i.e. if we're using the block
172     // indent style and either the items are on multiple lines or we've exceeded
173     // our budget to fit on a single line.
174     let nested = context.config.indent_style() == IndentStyle::Block
175         && (item_str.contains('\n') || item_str.len() > one_line_budget);
176
177     // Format the final result.
178     let mut result = String::with_capacity(128);
179     result.push_str(prefix);
180     result.push_str("[derive(");
181     if nested {
182         let nested_indent = argument_shape.indent.to_string_with_newline(context.config);
183         result.push_str(&nested_indent);
184         result.push_str(&item_str);
185         result.push_str(&shape.indent.to_string_with_newline(context.config));
186     } else if let SeparatorTactic::Always = context.config.trailing_comma() {
187         // Retain the trailing comma.
188         result.push_str(&item_str);
189     } else if item_str.ends_with(',') {
190         // Remove the trailing comma.
191         result.push_str(&item_str[..item_str.len() - 1]);
192     } else {
193         result.push_str(&item_str);
194     }
195     result.push_str(")]");
196
197     Some(result)
198 }
199
200 /// Returns the first group of attributes that fills the given predicate.
201 /// We consider two doc comments are in different group if they are separated by normal comments.
202 fn take_while_with_pred<'a, P>(
203     context: &RewriteContext<'_>,
204     attrs: &'a [ast::Attribute],
205     pred: P,
206 ) -> &'a [ast::Attribute]
207 where
208     P: Fn(&ast::Attribute) -> bool,
209 {
210     let mut len = 0;
211     let mut iter = attrs.iter().peekable();
212
213     while let Some(attr) = iter.next() {
214         if pred(attr) {
215             len += 1;
216         } else {
217             break;
218         }
219         if let Some(next_attr) = iter.peek() {
220             // Extract comments between two attributes.
221             let span_between_attr = mk_sp(attr.span.hi(), next_attr.span.lo());
222             let snippet = context.snippet(span_between_attr);
223             if count_newlines(snippet) >= 2 || snippet.contains('/') {
224                 break;
225             }
226         }
227     }
228
229     &attrs[..len]
230 }
231
232 /// Rewrite the any doc comments which come before any other attributes.
233 fn rewrite_initial_doc_comments(
234     context: &RewriteContext<'_>,
235     attrs: &[ast::Attribute],
236     shape: Shape,
237 ) -> Option<(usize, Option<String>)> {
238     if attrs.is_empty() {
239         return Some((0, None));
240     }
241     // Rewrite doc comments
242     let sugared_docs = take_while_with_pred(context, attrs, |a| a.is_doc_comment());
243     if !sugared_docs.is_empty() {
244         let snippet = sugared_docs
245             .iter()
246             .map(|a| context.snippet(a.span))
247             .collect::<Vec<_>>()
248             .join("\n");
249         return Some((
250             sugared_docs.len(),
251             Some(rewrite_doc_comment(
252                 &snippet,
253                 shape.comment(context.config),
254                 context.config,
255             )?),
256         ));
257     }
258
259     Some((0, None))
260 }
261
262 impl Rewrite for ast::NestedMetaItem {
263     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
264         match self {
265             ast::NestedMetaItem::MetaItem(ref meta_item) => meta_item.rewrite(context, shape),
266             ast::NestedMetaItem::Literal(ref l) => rewrite_literal(context, l, shape),
267         }
268     }
269 }
270
271 fn has_newlines_before_after_comment(comment: &str) -> (&str, &str) {
272     // Look at before and after comment and see if there are any empty lines.
273     let comment_begin = comment.find('/');
274     let len = comment_begin.unwrap_or_else(|| comment.len());
275     let mlb = count_newlines(&comment[..len]) > 1;
276     let mla = if comment_begin.is_none() {
277         mlb
278     } else {
279         comment
280             .chars()
281             .rev()
282             .take_while(|c| c.is_whitespace())
283             .filter(|&c| c == '\n')
284             .count()
285             > 1
286     };
287     (if mlb { "\n" } else { "" }, if mla { "\n" } else { "" })
288 }
289
290 impl Rewrite for ast::MetaItem {
291     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
292         Some(match self.kind {
293             ast::MetaItemKind::Word => {
294                 rewrite_path(context, PathContext::Type, None, &self.path, shape)?
295             }
296             ast::MetaItemKind::List(ref list) => {
297                 let path = rewrite_path(context, PathContext::Type, None, &self.path, shape)?;
298                 let has_trailing_comma = crate::expr::span_ends_with_comma(context, self.span);
299                 overflow::rewrite_with_parens(
300                     context,
301                     &path,
302                     list.iter(),
303                     // 1 = "]"
304                     shape.sub_width(1)?,
305                     self.span,
306                     context.config.attr_fn_like_width(),
307                     Some(if has_trailing_comma {
308                         SeparatorTactic::Always
309                     } else {
310                         SeparatorTactic::Never
311                     }),
312                 )?
313             }
314             ast::MetaItemKind::NameValue(ref literal) => {
315                 let path = rewrite_path(context, PathContext::Type, None, &self.path, shape)?;
316                 // 3 = ` = `
317                 let lit_shape = shape.shrink_left(path.len() + 3)?;
318                 // `rewrite_literal` returns `None` when `literal` exceeds max
319                 // width. Since a literal is basically unformattable unless it
320                 // is a string literal (and only if `format_strings` is set),
321                 // we might be better off ignoring the fact that the attribute
322                 // is longer than the max width and continue on formatting.
323                 // See #2479 for example.
324                 let value = rewrite_literal(context, literal, lit_shape)
325                     .unwrap_or_else(|| context.snippet(literal.span).to_owned());
326                 format!("{} = {}", path, value)
327             }
328         })
329     }
330 }
331
332 impl Rewrite for ast::Attribute {
333     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
334         let snippet = context.snippet(self.span);
335         if self.is_doc_comment() {
336             rewrite_doc_comment(snippet, shape.comment(context.config), context.config)
337         } else {
338             let should_skip = self
339                 .ident()
340                 .map(|s| context.skip_context.skip_attribute(&s.name.as_str()))
341                 .unwrap_or(false);
342             let prefix = attr_prefix(self);
343
344             if should_skip || contains_comment(snippet) {
345                 return Some(snippet.to_owned());
346             }
347
348             if let Some(ref meta) = self.meta() {
349                 // This attribute is possibly a doc attribute needing normalization to a doc comment
350                 if context.config.normalize_doc_attributes() && meta.has_name(sym::doc) {
351                     if let Some(ref literal) = meta.value_str() {
352                         let comment_style = match self.style {
353                             ast::AttrStyle::Inner => CommentStyle::Doc,
354                             ast::AttrStyle::Outer => CommentStyle::TripleSlash,
355                         };
356
357                         let literal_str = literal.as_str();
358                         let doc_comment_formatter =
359                             DocCommentFormatter::new(&*literal_str, comment_style);
360                         let doc_comment = format!("{}", doc_comment_formatter);
361                         return rewrite_doc_comment(
362                             &doc_comment,
363                             shape.comment(context.config),
364                             context.config,
365                         );
366                     }
367                 }
368
369                 // 1 = `[`
370                 let shape = shape.offset_left(prefix.len() + 1)?;
371                 Some(
372                     meta.rewrite(context, shape)
373                         .map_or_else(|| snippet.to_owned(), |rw| format!("{}[{}]", prefix, rw)),
374                 )
375             } else {
376                 Some(snippet.to_owned())
377             }
378         }
379     }
380 }
381
382 impl Rewrite for [ast::Attribute] {
383     fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> {
384         if self.is_empty() {
385             return Some(String::new());
386         }
387
388         // The current remaining attributes.
389         let mut attrs = self;
390         let mut result = String::new();
391
392         // This is not just a simple map because we need to handle doc comments
393         // (where we take as many doc comment attributes as possible) and possibly
394         // merging derives into a single attribute.
395         loop {
396             if attrs.is_empty() {
397                 return Some(result);
398             }
399
400             // Handle doc comments.
401             let (doc_comment_len, doc_comment_str) =
402                 rewrite_initial_doc_comments(context, attrs, shape)?;
403             if doc_comment_len > 0 {
404                 let doc_comment_str = doc_comment_str.expect("doc comments, but no result");
405                 result.push_str(&doc_comment_str);
406
407                 let missing_span = attrs
408                     .get(doc_comment_len)
409                     .map(|next| mk_sp(attrs[doc_comment_len - 1].span.hi(), next.span.lo()));
410                 if let Some(missing_span) = missing_span {
411                     let snippet = context.snippet(missing_span);
412                     let (mla, mlb) = has_newlines_before_after_comment(snippet);
413                     let comment = crate::comment::recover_missing_comment_in_span(
414                         missing_span,
415                         shape.with_max_width(context.config),
416                         context,
417                         0,
418                     )?;
419                     let comment = if comment.is_empty() {
420                         format!("\n{}", mlb)
421                     } else {
422                         format!("{}{}\n{}", mla, comment, mlb)
423                     };
424                     result.push_str(&comment);
425                     result.push_str(&shape.indent.to_string(context.config));
426                 }
427
428                 attrs = &attrs[doc_comment_len..];
429
430                 continue;
431             }
432
433             // Handle derives if we will merge them.
434             if context.config.merge_derives() && is_derive(&attrs[0]) {
435                 let derives = take_while_with_pred(context, attrs, is_derive);
436                 let derive_str = format_derive(derives, shape, context)?;
437                 result.push_str(&derive_str);
438
439                 let missing_span = attrs
440                     .get(derives.len())
441                     .map(|next| mk_sp(attrs[derives.len() - 1].span.hi(), next.span.lo()));
442                 if let Some(missing_span) = missing_span {
443                     let comment = crate::comment::recover_missing_comment_in_span(
444                         missing_span,
445                         shape.with_max_width(context.config),
446                         context,
447                         0,
448                     )?;
449                     result.push_str(&comment);
450                     if let Some(next) = attrs.get(derives.len()) {
451                         if next.is_doc_comment() {
452                             let snippet = context.snippet(missing_span);
453                             let (_, mlb) = has_newlines_before_after_comment(snippet);
454                             result.push_str(&mlb);
455                         }
456                     }
457                     result.push('\n');
458                     result.push_str(&shape.indent.to_string(context.config));
459                 }
460
461                 attrs = &attrs[derives.len()..];
462
463                 continue;
464             }
465
466             // If we get here, then we have a regular attribute, just handle one
467             // at a time.
468
469             let formatted_attr = attrs[0].rewrite(context, shape)?;
470             result.push_str(&formatted_attr);
471
472             let missing_span = attrs
473                 .get(1)
474                 .map(|next| mk_sp(attrs[0].span.hi(), next.span.lo()));
475             if let Some(missing_span) = missing_span {
476                 let comment = crate::comment::recover_missing_comment_in_span(
477                     missing_span,
478                     shape.with_max_width(context.config),
479                     context,
480                     0,
481                 )?;
482                 result.push_str(&comment);
483                 if let Some(next) = attrs.get(1) {
484                     if next.is_doc_comment() {
485                         let snippet = context.snippet(missing_span);
486                         let (_, mlb) = has_newlines_before_after_comment(snippet);
487                         result.push_str(&mlb);
488                     }
489                 }
490                 result.push('\n');
491                 result.push_str(&shape.indent.to_string(context.config));
492             }
493
494             attrs = &attrs[1..];
495         }
496     }
497 }
498
499 fn attr_prefix(attr: &ast::Attribute) -> &'static str {
500     match attr.style {
501         ast::AttrStyle::Inner => "#!",
502         ast::AttrStyle::Outer => "#",
503     }
504 }
505
506 pub(crate) trait MetaVisitor<'ast> {
507     fn visit_meta_item(&mut self, meta_item: &'ast ast::MetaItem) {
508         match meta_item.kind {
509             ast::MetaItemKind::Word => self.visit_meta_word(meta_item),
510             ast::MetaItemKind::List(ref list) => self.visit_meta_list(meta_item, list),
511             ast::MetaItemKind::NameValue(ref lit) => self.visit_meta_name_value(meta_item, lit),
512         }
513     }
514
515     fn visit_meta_list(
516         &mut self,
517         _meta_item: &'ast ast::MetaItem,
518         list: &'ast [ast::NestedMetaItem],
519     ) {
520         for nm in list {
521             self.visit_nested_meta_item(nm);
522         }
523     }
524
525     fn visit_meta_word(&mut self, _meta_item: &'ast ast::MetaItem) {}
526
527     fn visit_meta_name_value(&mut self, _meta_item: &'ast ast::MetaItem, _lit: &'ast ast::Lit) {}
528
529     fn visit_nested_meta_item(&mut self, nm: &'ast ast::NestedMetaItem) {
530         match nm {
531             ast::NestedMetaItem::MetaItem(ref meta_item) => self.visit_meta_item(meta_item),
532             ast::NestedMetaItem::Literal(ref lit) => self.visit_literal(lit),
533         }
534     }
535
536     fn visit_literal(&mut self, _lit: &'ast ast::Lit) {}
537 }