diff --git a/rustfmt-core/src/attr.rs b/rustfmt-core/src/attr.rs new file mode 100644 index 00000000000..5d22bd9ffe7 --- /dev/null +++ b/rustfmt-core/src/attr.rs @@ -0,0 +1,323 @@ +// 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 or the MIT license +// , 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 syntax::codemap::Span; + +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, + } +} + +/// Returns attributes that are within `outer_span`. +pub fn filter_inline_attrs(attrs: &[ast::Attribute], outer_span: Span) -> Vec { + attrs + .iter() + .filter(|a| outer_span.lo() <= a.span.lo() && a.span.hi() <= outer_span.hi()) + .cloned() + .collect() +} + +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> { + 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 { + 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] + } +} + +/// Rewrite the same kind of attributes at the same time. This includes doc +/// comments and derives. +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::>() + .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 { + 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 { + 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::>(); + 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)?; + // `rewrite_literal` returns `None` when `literal` exceeds max + // width. Since a literal is basically unformattable unless it + // is a string literal (and only if `format_strings` is set), + // we might be better off ignoring the fact that the attribute + // is longer than the max width and contiue on formatting. + // See #2479 for example. + let value = rewrite_literal(context, literal, lit_shape) + .unwrap_or_else(|| context.snippet(literal.span).to_owned()); + format!("{} = {}", name, value) + } + }) + } +} + +impl Rewrite for ast::Attribute { + fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option { + 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 { + 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, + ) + } + } + } +} diff --git a/rustfmt-core/src/items.rs b/rustfmt-core/src/items.rs index add3cdc0d79..3503546c010 100644 --- a/rustfmt-core/src/items.rs +++ b/rustfmt-core/src/items.rs @@ -14,6 +14,7 @@ use std::borrow::Cow; use std::cmp::min; use config::lists::*; +use regex::Regex; use syntax::{abi, ast, ptr, symbol}; use syntax::ast::{CrateSugar, ImplItem}; use syntax::codemap::{BytePos, Span}; @@ -2854,3 +2855,37 @@ pub fn rewrite_mod(item: &ast::Item) -> String { result.push(';'); result } + +/// Rewrite `extern crate foo;` WITHOUT attributes. +pub fn rewrite_extern_crate(context: &RewriteContext, item: &ast::Item) -> Option { + assert!(is_extern_crate(item)); + let new_str = context.snippet(item.span); + Some(if contains_comment(new_str) { + new_str.to_owned() + } else { + let no_whitespace = &new_str.split_whitespace().collect::>().join(" "); + String::from(&*Regex::new(r"\s;").unwrap().replace(no_whitespace, ";")) + }) +} + +/// Returns true for `mod foo;`, false for `mod foo { .. }`. +pub fn is_mod_decl(item: &ast::Item) -> bool { + match item.node { + ast::ItemKind::Mod(ref m) => m.inner.hi() != item.span.hi(), + _ => false, + } +} + +pub fn is_use_item(item: &ast::Item) -> bool { + match item.node { + ast::ItemKind::Use(_) => true, + _ => false, + } +} + +pub fn is_extern_crate(item: &ast::Item) -> bool { + match item.node { + ast::ItemKind::ExternCrate(..) => true, + _ => false, + } +} diff --git a/rustfmt-core/src/lib.rs b/rustfmt-core/src/lib.rs index 5697d6cab2d..6e9554f6855 100644 --- a/rustfmt-core/src/lib.rs +++ b/rustfmt-core/src/lib.rs @@ -53,6 +53,7 @@ pub use config::summary::Summary; #[macro_use] mod utils; +mod attr; mod chains; mod checkstyle; mod closures; diff --git a/rustfmt-core/src/reorder.rs b/rustfmt-core/src/reorder.rs index 51c081e6ba8..d4bc36fc83f 100644 --- a/rustfmt-core/src/reorder.rs +++ b/rustfmt-core/src/reorder.rs @@ -19,16 +19,17 @@ use config::{Config, lists::*}; use syntax::{ast, attr, codemap::Span}; +use attr::filter_inline_attrs; use codemap::LineRangeUtils; use comment::combine_strs_with_missing_comments; use imports::{path_to_imported_ident, rewrite_import}; -use items::rewrite_mod; +use items::{rewrite_extern_crate, rewrite_mod}; use lists::{itemize_list, write_list, ListFormatting}; use rewrite::{Rewrite, RewriteContext}; use shape::Shape; use spanned::Spanned; use utils::mk_sp; -use visitor::{filter_inline_attrs, rewrite_extern_crate, FmtVisitor}; +use visitor::FmtVisitor; use std::cmp::Ordering; diff --git a/rustfmt-core/src/visitor.rs b/rustfmt-core/src/visitor.rs index b3d3e3270e8..35102f58609 100644 --- a/rustfmt-core/src/visitor.rs +++ b/rustfmt-core/src/visitor.rs @@ -8,61 +8,24 @@ // 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::{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 items::{format_impl, format_trait, format_trait_alias, is_mod_decl, is_use_item, + rewrite_associated_impl_type, rewrite_associated_type, rewrite_extern_crate, + rewrite_type_alias, FnSig, StaticParts, StructParts}; use macros::{rewrite_macro, rewrite_macro_def, MacroPosition}; -use regex::Regex; use rewrite::{Rewrite, RewriteContext}; use shape::{Indent, Shape}; use spanned::Spanned; use utils::{self, contains_skip, count_newlines, inner_attributes, mk_sp, ptr_vec_to_ref_vec}; -/// Returns attributes that are within `outer_span`. -pub fn filter_inline_attrs(attrs: &[ast::Attribute], outer_span: Span) -> Vec { - attrs - .iter() - .filter(|a| outer_span.lo() <= a.span.lo() && a.span.hi() <= outer_span.hi()) - .cloned() - .collect() -} - -/// Returns true for `mod foo;`, false for `mod foo { .. }`. -fn is_mod_decl(item: &ast::Item) -> bool { - match item.node { - ast::ItemKind::Mod(ref m) => m.inner.hi() != item.span.hi(), - _ => false, - } -} - -fn is_use_item(item: &ast::Item) -> bool { - match item.node { - ast::ItemKind::Use(_) => true, - _ => false, - } -} - -fn is_extern_crate(item: &ast::Item) -> bool { - match item.node { - ast::ItemKind::ExternCrate(..) => true, - _ => false, - } -} - /// Creates a string slice corresponding to the specified span. pub struct SnippetProvider<'a> { /// A pointer to the content of the file we are formatting. @@ -735,295 +698,3 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { } } } - -impl Rewrite for ast::NestedMetaItem { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option { - 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 { - 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::>(); - 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 { - 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::>() - .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 { - 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 { - 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> { - 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 { - assert!(is_extern_crate(item)); - let new_str = context.snippet(item.span); - Some(if contains_comment(new_str) { - new_str.to_owned() - } else { - let no_whitespace = &new_str.split_whitespace().collect::>().join(" "); - 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, - } -} diff --git a/rustfmt-core/tests/source/issue-2479.rs b/rustfmt-core/tests/source/issue-2479.rs new file mode 100644 index 00000000000..df50236d05d --- /dev/null +++ b/rustfmt-core/tests/source/issue-2479.rs @@ -0,0 +1,2 @@ +// Long attributes. +# [ derive ( Clone , Copy , Debug , PartialEq ) ] pub enum POLARITYR { # [ doc = "Task mode: No effect on pin from OUT[n] task. Event mode: no IN[n] event generated on pin activity." ] NONE , # [ doc = "Task mode: Set pin from OUT[n] task. Event mode: Generate IN[n] event when rising edge on pin." ] LOTOHI , # [ doc = "Task mode: Clear pin from OUT[n] task. Event mode: Generate IN[n] event when falling edge on pin." ] HITOLO , # [ doc = "Task mode: Toggle pin from OUT[n]. Event mode: Generate IN[n] when any change on pin." ] TOGGLE } diff --git a/rustfmt-core/tests/target/issue-2479.rs b/rustfmt-core/tests/target/issue-2479.rs new file mode 100644 index 00000000000..3683ab22089 --- /dev/null +++ b/rustfmt-core/tests/target/issue-2479.rs @@ -0,0 +1,12 @@ +// Long attributes. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum POLARITYR { + #[doc = "Task mode: No effect on pin from OUT[n] task. Event mode: no IN[n] event generated on pin activity."] + NONE, + #[doc = "Task mode: Set pin from OUT[n] task. Event mode: Generate IN[n] event when rising edge on pin."] + LOTOHI, + #[doc = "Task mode: Clear pin from OUT[n] task. Event mode: Generate IN[n] event when falling edge on pin."] + HITOLO, + #[doc = "Task mode: Toggle pin from OUT[n]. Event mode: Generate IN[n] when any change on pin."] + TOGGLE, +}