diff --git a/README.md b/README.md
index b2caff5fc556..cc5fb979c527 100644
--- a/README.md
+++ b/README.md
@@ -179,6 +179,18 @@ needs to be specified in `rustfmt.toml`, e.g., with `edition = "2018"`.
## Tips
* For things you do not want rustfmt to mangle, use `#[rustfmt::skip]`
+* To prevent rustfmt from formatting a macro,
+ use `#[rustfmt::skip::macros(target_macro_name)]`
+
+ Example:
+
+ ```rust
+ #[rustfmt::skip::macros(html)]
+ fn main() {
+ let macro_result1 = html! {
+ Hello
+ }.to_string();
+ ```
* When you run rustfmt, place a file named `rustfmt.toml` or `.rustfmt.toml` in
target file directory or its parents to override the default settings of
rustfmt. You can generate a file containing the default configuration with
diff --git a/src/formatting.rs b/src/formatting.rs
index a23c312bd2ae..545d2fe279f7 100644
--- a/src/formatting.rs
+++ b/src/formatting.rs
@@ -15,6 +15,7 @@ use syntax::source_map::{FilePathMapping, SourceMap, Span, DUMMY_SP};
use crate::comment::{CharClasses, FullCodeCharKind};
use crate::config::{Config, FileName, Verbosity};
use crate::issues::BadIssueSeeker;
+use crate::utils::{count_newlines, get_skip_macro_names};
use crate::visitor::{FmtVisitor, SnippetProvider};
use crate::{modules, source_file, ErrorKind, FormatReport, Input, Session};
@@ -153,6 +154,10 @@ impl<'a, T: FormatHandler + 'a> FormatContext<'a, T> {
&snippet_provider,
self.report.clone(),
);
+ visitor
+ .skip_macro_names
+ .borrow_mut()
+ .append(&mut get_skip_macro_names(&self.krate.attrs));
// Format inner attributes if available.
if !self.krate.attrs.is_empty() && is_root {
@@ -168,10 +173,7 @@ impl<'a, T: FormatHandler + 'a> FormatContext<'a, T> {
visitor.format_separate_mod(module, &*source_file);
};
- debug_assert_eq!(
- visitor.line_number,
- crate::utils::count_newlines(&visitor.buffer)
- );
+ debug_assert_eq!(visitor.line_number, count_newlines(&visitor.buffer));
// For some reason, the source_map does not include terminating
// newlines so we must add one on for each file. This is sad.
diff --git a/src/lib.rs b/src/lib.rs
index b1090e95ff37..7e465ffdcfe2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -94,7 +94,7 @@ pub enum ErrorKind {
/// Used deprecated skip attribute.
#[fail(display = "`rustfmt_skip` is deprecated; use `rustfmt::skip`")]
DeprecatedAttr,
- /// Used a rustfmt:: attribute other than skip.
+ /// Used a rustfmt:: attribute other than skip or skip::macros.
#[fail(display = "invalid attribute")]
BadAttr,
/// An io error during reading or writing.
diff --git a/src/macros.rs b/src/macros.rs
index 95504a23abaa..18df24b45c8c 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -208,12 +208,21 @@ pub fn rewrite_macro(
shape: Shape,
position: MacroPosition,
) -> Option {
- let guard = InsideMacroGuard::inside_macro_context(context);
- let result = rewrite_macro_inner(mac, extra_ident, context, shape, position, guard.is_nested);
- if result.is_none() {
- context.macro_rewrite_failure.replace(true);
+ let should_skip = context
+ .skip_macro_names
+ .borrow()
+ .contains(&context.snippet(mac.node.path.span).to_owned());
+ if should_skip {
+ None
+ } else {
+ let guard = InsideMacroGuard::inside_macro_context(context);
+ let result =
+ rewrite_macro_inner(mac, extra_ident, context, shape, position, guard.is_nested);
+ if result.is_none() {
+ context.macro_rewrite_failure.replace(true);
+ }
+ result
}
- result
}
fn check_keyword<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option {
diff --git a/src/rewrite.rs b/src/rewrite.rs
index df147e0c4271..c736c2535a61 100644
--- a/src/rewrite.rs
+++ b/src/rewrite.rs
@@ -39,6 +39,7 @@ pub struct RewriteContext<'a> {
// Used for `format_snippet`
pub(crate) macro_rewrite_failure: RefCell,
pub(crate) report: FormatReport,
+ pub skip_macro_names: RefCell>,
}
impl<'a> RewriteContext<'a> {
diff --git a/src/test/mod.rs b/src/test/mod.rs
index 61d7c88884a8..0628094fdeff 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -22,6 +22,7 @@ const SKIP_FILE_WHITE_LIST: &[&str] = &[
// We want to make sure that the `skip_children` is correctly working,
// so we do not want to test this file directly.
"configs/skip_children/foo/mod.rs",
+ "issue-3434/no_entry.rs",
];
fn is_file_skip(path: &Path) -> bool {
diff --git a/src/utils.rs b/src/utils.rs
index e42b5c8c7d18..743a86276129 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -603,6 +603,26 @@ pub(crate) fn unicode_str_width(s: &str) -> usize {
s.width()
}
+pub fn get_skip_macro_names(attrs: &[ast::Attribute]) -> Vec {
+ let mut skip_macro_names = vec![];
+ for attr in attrs {
+ // syntax::ast::Path is implemented partialEq
+ // but it is designed for segments.len() == 1
+ if format!("{}", attr.path) != "rustfmt::skip::macros" {
+ continue;
+ }
+
+ if let Some(list) = attr.meta_item_list() {
+ for spanned in list {
+ if let Some(name) = spanned.name() {
+ skip_macro_names.push(name.to_string());
+ }
+ }
+ }
+ }
+ skip_macro_names
+}
+
#[cfg(test)]
mod test {
use super::*;
diff --git a/src/visitor.rs b/src/visitor.rs
index 56a27889c201..8688c5b78271 100644
--- a/src/visitor.rs
+++ b/src/visitor.rs
@@ -20,8 +20,8 @@ use crate::shape::{Indent, Shape};
use crate::source_map::{LineRangeUtils, SpanUtils};
use crate::spanned::Spanned;
use crate::utils::{
- self, contains_skip, count_newlines, inner_attributes, mk_sp, ptr_vec_to_ref_vec,
- rewrite_ident, stmt_expr, DEPR_SKIP_ANNOTATION,
+ self, contains_skip, count_newlines, get_skip_macro_names, inner_attributes, mk_sp,
+ ptr_vec_to_ref_vec, rewrite_ident, stmt_expr, DEPR_SKIP_ANNOTATION,
};
use crate::{ErrorKind, FormatReport, FormattingError};
@@ -66,6 +66,7 @@ pub struct FmtVisitor<'a> {
pub skipped_range: Vec<(usize, usize)>,
pub macro_rewrite_failure: bool,
pub(crate) report: FormatReport,
+ pub skip_macro_names: RefCell>,
}
impl<'a> Drop for FmtVisitor<'a> {
@@ -296,25 +297,32 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
// the AST lumps them all together.
let filtered_attrs;
let mut attrs = &item.attrs;
- match item.node {
+ let temp_skip_macro_names = self.skip_macro_names.clone();
+ self.skip_macro_names
+ .borrow_mut()
+ .append(&mut get_skip_macro_names(&attrs));
+
+ let should_visit_node_again = match item.node {
// For use items, skip rewriting attributes. Just check for a skip attribute.
ast::ItemKind::Use(..) => {
if contains_skip(attrs) {
self.push_skipped_with_span(attrs.as_slice(), item.span(), item.span());
- return;
+ false
+ } else {
+ true
}
}
// Module is inline, in this case we treat it like any other item.
_ if !is_mod_decl(item) => {
if self.visit_attrs(&item.attrs, ast::AttrStyle::Outer) {
self.push_skipped_with_span(item.attrs.as_slice(), item.span(), item.span());
- return;
+ false
+ } else {
+ true
}
}
// Module is not inline, but should be skipped.
- ast::ItemKind::Mod(..) if contains_skip(&item.attrs) => {
- return;
- }
+ ast::ItemKind::Mod(..) if contains_skip(&item.attrs) => false,
// Module is not inline and should not be skipped. We want
// to process only the attributes in the current file.
ast::ItemKind::Mod(..) => {
@@ -323,121 +331,127 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
// the above case.
assert!(!self.visit_attrs(&filtered_attrs, ast::AttrStyle::Outer));
attrs = &filtered_attrs;
+ true
}
_ => {
if self.visit_attrs(&item.attrs, ast::AttrStyle::Outer) {
self.push_skipped_with_span(item.attrs.as_slice(), item.span(), item.span());
- return;
+ false
+ } else {
+ true
}
}
- }
+ };
- match item.node {
- ast::ItemKind::Use(ref tree) => self.format_import(item, tree),
- ast::ItemKind::Impl(..) => {
- let snippet = self.snippet(item.span);
- let where_span_end = snippet
- .find_uncommented("{")
- .map(|x| BytePos(x as u32) + source!(self, item.span).lo());
- let block_indent = self.block_indent;
- let rw =
- self.with_context(|ctx| format_impl(&ctx, item, block_indent, where_span_end));
- self.push_rewrite(item.span, rw);
- }
- ast::ItemKind::Trait(..) => {
- let block_indent = self.block_indent;
- let rw = self.with_context(|ctx| format_trait(&ctx, item, block_indent));
- self.push_rewrite(item.span, rw);
- }
- ast::ItemKind::TraitAlias(ref generics, ref generic_bounds) => {
- let shape = Shape::indented(self.block_indent, self.config);
- let rw = format_trait_alias(
- &self.get_context(),
- item.ident,
- &item.vis,
- generics,
- generic_bounds,
- shape,
- );
- self.push_rewrite(item.span, rw);
- }
- ast::ItemKind::ExternCrate(_) => {
- let rw = rewrite_extern_crate(&self.get_context(), item);
- self.push_rewrite(item.span, rw);
- }
- 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.visit_enum(item.ident, &item.vis, def, generics, item.span);
- self.last_pos = source!(self, item.span).hi();
- }
- ast::ItemKind::Mod(ref module) => {
- let is_inline = !is_mod_decl(item);
- self.format_missing_with_indent(source!(self, item.span).lo());
- self.format_mod(module, &item.vis, item.span, item.ident, attrs, is_inline);
- }
- ast::ItemKind::Mac(ref mac) => {
- self.visit_mac(mac, Some(item.ident), MacroPosition::Item);
- }
- ast::ItemKind::ForeignMod(ref foreign_mod) => {
- self.format_missing_with_indent(source!(self, item.span).lo());
- self.format_foreign_mod(foreign_mod, item.span);
- }
- ast::ItemKind::Static(..) | ast::ItemKind::Const(..) => {
- self.visit_static(&StaticParts::from_item(item));
- }
- ast::ItemKind::Fn(ref decl, ref fn_header, ref generics, ref body) => {
- let inner_attrs = inner_attributes(&item.attrs);
- self.visit_fn(
- visit::FnKind::ItemFn(item.ident, fn_header, &item.vis, body),
- generics,
- decl,
- item.span,
- ast::Defaultness::Final,
- Some(&inner_attrs),
- )
- }
- ast::ItemKind::Ty(ref ty, ref generics) => {
- let rewrite = rewrite_type_alias(
- &self.get_context(),
- self.block_indent,
- item.ident,
- ty,
- generics,
- &item.vis,
- );
- self.push_rewrite(item.span, rewrite);
- }
- ast::ItemKind::Existential(ref generic_bounds, ref generics) => {
- let rewrite = rewrite_existential_type(
- &self.get_context(),
- self.block_indent,
- item.ident,
- generic_bounds,
- generics,
- &item.vis,
- );
- self.push_rewrite(item.span, rewrite);
- }
- ast::ItemKind::GlobalAsm(..) => {
- let snippet = Some(self.snippet(item.span).to_owned());
- self.push_rewrite(item.span, snippet);
- }
- ast::ItemKind::MacroDef(ref def) => {
- let rewrite = rewrite_macro_def(
- &self.get_context(),
- self.shape(),
- self.block_indent,
- def,
- item.ident,
- &item.vis,
- item.span,
- );
- self.push_rewrite(item.span, rewrite);
- }
+ if should_visit_node_again {
+ match item.node {
+ ast::ItemKind::Use(ref tree) => self.format_import(item, tree),
+ ast::ItemKind::Impl(..) => {
+ let snippet = self.snippet(item.span);
+ let where_span_end = snippet
+ .find_uncommented("{")
+ .map(|x| BytePos(x as u32) + source!(self, item.span).lo());
+ let block_indent = self.block_indent;
+ let rw = self
+ .with_context(|ctx| format_impl(&ctx, item, block_indent, where_span_end));
+ self.push_rewrite(item.span, rw);
+ }
+ ast::ItemKind::Trait(..) => {
+ let block_indent = self.block_indent;
+ let rw = self.with_context(|ctx| format_trait(&ctx, item, block_indent));
+ self.push_rewrite(item.span, rw);
+ }
+ ast::ItemKind::TraitAlias(ref generics, ref generic_bounds) => {
+ let shape = Shape::indented(self.block_indent, self.config);
+ let rw = format_trait_alias(
+ &self.get_context(),
+ item.ident,
+ &item.vis,
+ generics,
+ generic_bounds,
+ shape,
+ );
+ self.push_rewrite(item.span, rw);
+ }
+ ast::ItemKind::ExternCrate(_) => {
+ let rw = rewrite_extern_crate(&self.get_context(), item);
+ self.push_rewrite(item.span, rw);
+ }
+ 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.visit_enum(item.ident, &item.vis, def, generics, item.span);
+ self.last_pos = source!(self, item.span).hi();
+ }
+ ast::ItemKind::Mod(ref module) => {
+ let is_inline = !is_mod_decl(item);
+ self.format_missing_with_indent(source!(self, item.span).lo());
+ self.format_mod(module, &item.vis, item.span, item.ident, attrs, is_inline);
+ }
+ ast::ItemKind::Mac(ref mac) => {
+ self.visit_mac(mac, Some(item.ident), MacroPosition::Item);
+ }
+ ast::ItemKind::ForeignMod(ref foreign_mod) => {
+ self.format_missing_with_indent(source!(self, item.span).lo());
+ self.format_foreign_mod(foreign_mod, item.span);
+ }
+ ast::ItemKind::Static(..) | ast::ItemKind::Const(..) => {
+ self.visit_static(&StaticParts::from_item(item));
+ }
+ ast::ItemKind::Fn(ref decl, ref fn_header, ref generics, ref body) => {
+ let inner_attrs = inner_attributes(&item.attrs);
+ self.visit_fn(
+ visit::FnKind::ItemFn(item.ident, fn_header, &item.vis, body),
+ generics,
+ decl,
+ item.span,
+ ast::Defaultness::Final,
+ Some(&inner_attrs),
+ )
+ }
+ ast::ItemKind::Ty(ref ty, ref generics) => {
+ let rewrite = rewrite_type_alias(
+ &self.get_context(),
+ self.block_indent,
+ item.ident,
+ ty,
+ generics,
+ &item.vis,
+ );
+ self.push_rewrite(item.span, rewrite);
+ }
+ ast::ItemKind::Existential(ref generic_bounds, ref generics) => {
+ let rewrite = rewrite_existential_type(
+ &self.get_context(),
+ self.block_indent,
+ item.ident,
+ generic_bounds,
+ generics,
+ &item.vis,
+ );
+ self.push_rewrite(item.span, rewrite);
+ }
+ ast::ItemKind::GlobalAsm(..) => {
+ let snippet = Some(self.snippet(item.span).to_owned());
+ self.push_rewrite(item.span, snippet);
+ }
+ ast::ItemKind::MacroDef(ref def) => {
+ let rewrite = rewrite_macro_def(
+ &self.get_context(),
+ self.shape(),
+ self.block_indent,
+ def,
+ item.ident,
+ &item.vis,
+ item.span,
+ );
+ self.push_rewrite(item.span, rewrite);
+ }
+ };
}
+ self.skip_macro_names = temp_skip_macro_names;
}
pub fn visit_trait_item(&mut self, ti: &ast::TraitItem) {
@@ -592,6 +606,10 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
ctx.snippet_provider,
ctx.report.clone(),
);
+ visitor
+ .skip_macro_names
+ .borrow_mut()
+ .append(&mut ctx.skip_macro_names.borrow().clone());
visitor.set_parent_context(ctx);
visitor
}
@@ -616,6 +634,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
skipped_range: vec![],
macro_rewrite_failure: false,
report,
+ skip_macro_names: RefCell::new(vec![]),
}
}
@@ -640,10 +659,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
ErrorKind::DeprecatedAttr,
)],
);
- } else if attr.path.segments[0].ident.to_string() == "rustfmt"
- && (attr.path.segments.len() == 1
- || attr.path.segments[1].ident.to_string() != "skip")
- {
+ } else if self.is_unknown_rustfmt_attr(&attr.path.segments) {
let file_name = self.source_map.span_to_filename(attr.span).into();
self.report.append(
file_name,
@@ -671,6 +687,20 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
false
}
+ fn is_unknown_rustfmt_attr(&self, segments: &[ast::PathSegment]) -> bool {
+ if segments[0].ident.to_string() != "rustfmt" {
+ return false;
+ }
+
+ match segments.len() {
+ 2 => segments[1].ident.to_string() != "skip",
+ 3 => {
+ segments[1].ident.to_string() != "skip" || segments[2].ident.to_string() != "macros"
+ }
+ _ => false,
+ }
+ }
+
fn walk_mod_items(&mut self, m: &ast::Mod) {
self.visit_items_with_reordering(&ptr_vec_to_ref_vec(&m.items));
}
@@ -817,6 +847,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
snippet_provider: self.snippet_provider,
macro_rewrite_failure: RefCell::new(false),
report: self.report.clone(),
+ skip_macro_names: self.skip_macro_names.clone(),
}
}
}
diff --git a/tests/source/issue-3434/lib.rs b/tests/source/issue-3434/lib.rs
new file mode 100644
index 000000000000..7e396b383888
--- /dev/null
+++ b/tests/source/issue-3434/lib.rs
@@ -0,0 +1,57 @@
+#![rustfmt::skip::macros(skip_macro_mod)]
+
+mod no_entry;
+
+#[rustfmt::skip::macros(html, skip_macro)]
+fn main() {
+ let macro_result1 = html! {
+this should be skipped
+ }
+ .to_string();
+
+ let macro_result2 = not_skip_macro! {
+this should be mangled
+ }
+ .to_string();
+
+ skip_macro! {
+this should be skipped
+};
+
+ foo();
+}
+
+fn foo() {
+ let macro_result1 = html! {
+this should be mangled
+ }
+ .to_string();
+}
+
+fn bar() {
+ let macro_result1 = skip_macro_mod! {
+this should be skipped
+ }
+ .to_string();
+}
+
+fn visitor_made_from_same_context() {
+ let pair = (
+ || {
+ foo!(
+this should be mangled
+ );
+ skip_macro_mod!(
+this should be skipped
+ );
+ },
+ || {
+ foo!(
+this should be mangled
+ );
+ skip_macro_mod!(
+this should be skipped
+ );
+ },
+ );
+}
diff --git a/tests/source/issue-3434/no_entry.rs b/tests/source/issue-3434/no_entry.rs
new file mode 100644
index 000000000000..0838829fed34
--- /dev/null
+++ b/tests/source/issue-3434/no_entry.rs
@@ -0,0 +1,18 @@
+#[rustfmt::skip::macros(another_macro)]
+fn foo() {
+ another_macro!(
+This should be skipped.
+ );
+}
+
+fn bar() {
+ skip_macro_mod!(
+This should be skipped.
+ );
+}
+
+fn baz() {
+ let macro_result1 = no_skip_macro! {
+this should be mangled
+ }.to_string();
+}
diff --git a/tests/source/issue-3434/not_skip_macro.rs b/tests/source/issue-3434/not_skip_macro.rs
new file mode 100644
index 000000000000..1d7d73c523d6
--- /dev/null
+++ b/tests/source/issue-3434/not_skip_macro.rs
@@ -0,0 +1,8 @@
+#[this::is::not::skip::macros(ouch)]
+
+fn main() {
+ let macro_result1 = ouch! {
+this should be mangled
+ }
+ .to_string();
+}
diff --git a/tests/target/issue-3434/lib.rs b/tests/target/issue-3434/lib.rs
new file mode 100644
index 000000000000..2fd7aea21c75
--- /dev/null
+++ b/tests/target/issue-3434/lib.rs
@@ -0,0 +1,57 @@
+#![rustfmt::skip::macros(skip_macro_mod)]
+
+mod no_entry;
+
+#[rustfmt::skip::macros(html, skip_macro)]
+fn main() {
+ let macro_result1 = html! {
+this should be skipped
+ }
+ .to_string();
+
+ let macro_result2 = not_skip_macro! {
+ this should be mangled
+ }
+ .to_string();
+
+ skip_macro! {
+this should be skipped
+};
+
+ foo();
+}
+
+fn foo() {
+ let macro_result1 = html! {
+ this should be mangled
+ }
+ .to_string();
+}
+
+fn bar() {
+ let macro_result1 = skip_macro_mod! {
+this should be skipped
+ }
+ .to_string();
+}
+
+fn visitor_made_from_same_context() {
+ let pair = (
+ || {
+ foo!(
+ this should be mangled
+ );
+ skip_macro_mod!(
+this should be skipped
+ );
+ },
+ || {
+ foo!(
+ this should be mangled
+ );
+ skip_macro_mod!(
+this should be skipped
+ );
+ },
+ );
+}
diff --git a/tests/target/issue-3434/no_entry.rs b/tests/target/issue-3434/no_entry.rs
new file mode 100644
index 000000000000..a2ecf2c2f99b
--- /dev/null
+++ b/tests/target/issue-3434/no_entry.rs
@@ -0,0 +1,19 @@
+#[rustfmt::skip::macros(another_macro)]
+fn foo() {
+ another_macro!(
+This should be skipped.
+ );
+}
+
+fn bar() {
+ skip_macro_mod!(
+This should be skipped.
+ );
+}
+
+fn baz() {
+ let macro_result1 = no_skip_macro! {
+ this should be mangled
+ }
+ .to_string();
+}
diff --git a/tests/target/issue-3434/not_skip_macro.rs b/tests/target/issue-3434/not_skip_macro.rs
new file mode 100644
index 000000000000..c90d09744b28
--- /dev/null
+++ b/tests/target/issue-3434/not_skip_macro.rs
@@ -0,0 +1,8 @@
+#[this::is::not::skip::macros(ouch)]
+
+fn main() {
+ let macro_result1 = ouch! {
+ this should be mangled
+ }
+ .to_string();
+}