Repository: rust-lang/rustfmt (rust)

Issue:
rustfmt adds trailing spaces to macro rules
```rust
macro_rules! foo {}
foo::bar!();
```
rustfmt --check
```
Diff in /home/matthias/vcs/github/rust/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0062_mod_contents.rs at line 1:
-macro_rules! foo {}
+macro_rules! foo {
+
+}
 foo::bar!();

error[internal]: left behind trailing whitespace
 --> /home/matthias/vcs/github/rust/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0062_mod_contents.rs:2:2:1
  |
2 |
  | ^^^^
  |

warning: rustfmt has failed to format. See previous 1 errors.
```

`rustfmt 1.6.0-nightly (f88a8b7 2023-08-08)`

Pull request:
Improve formatting of empty macro_rules! definitions
Fixes #5882

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index e867f2cb..bcac61ef 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -588,9 +588,9 @@ dependencies = [
  "toml",
  "tracing",
  "tracing-subscriber",
+ "unicode-properties",
  "unicode-segmentation",
  "unicode-width",
- "unicode_categories",
 ]
 
 [[package]]
@@ -852,22 +852,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
 
 [[package]]
-name = "unicode-segmentation"
-version = "1.9.0"
+name = "unicode-properties"
+version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
+checksum = "c7f91c8b21fbbaa18853c3d0801c78f4fc94cdb976699bb03e832e75f7fd22f0"
 
 [[package]]
-name = "unicode-width"
-version = "0.1.9"
+name = "unicode-segmentation"
+version = "1.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
 
 [[package]]
-name = "unicode_categories"
-version = "0.1.1"
+name = "unicode-width"
+version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
 [[package]]
 name = "utf8parse"
diff --git a/Cargo.toml b/Cargo.toml
index f4c4bab3..5a800e95 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -54,7 +54,7 @@ tracing = "0.1.37"
 tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
 unicode-segmentation = "1.9"
 unicode-width = "0.1"
-unicode_categories = "0.1"
+unicode-properties = { version = "0.1", default-features = false, features = ["general-category"] }
 
 rustfmt-config_proc_macro = { version = "0.3", path = "config_proc_macro" }
 
diff --git a/src/bin/main.rs b/src/bin/main.rs
index 2ff67d27..97d31852 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -422,7 +422,7 @@ are 1-based and inclusive of both end points. Specifying an empty array
 will result in no files being formatted. For example,
 
 ```
-rustfmt --file-lines '[
+rustfmt src/lib.rs src/foo.rs --file-lines '[
     {{\"file\":\"src/lib.rs\",\"range\":[7,13]}},
     {{\"file\":\"src/lib.rs\",\"range\":[21,29]}},
     {{\"file\":\"src/foo.rs\",\"range\":[10,11]}},
diff --git a/src/macros.rs b/src/macros.rs
index e9a298a2..88d26358 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -379,6 +379,23 @@ fn handle_vec_semi(
     }
 }
 
+fn rewrite_empty_macro_def_body(
+    context: &RewriteContext<'_>,
+    span: Span,
+    shape: Shape,
+) -> Option<String> {
+    // Create an empty, dummy `ast::Block` representing an empty macro body
+    let block = ast::Block {
+        stmts: vec![].into(),
+        id: rustc_ast::node_id::DUMMY_NODE_ID,
+        rules: ast::BlockCheckMode::Default,
+        span: span,
+        tokens: None,
+        could_be_bare_literal: false,
+    };
+    block.rewrite(context, shape)
+}
+
 pub(crate) fn rewrite_macro_def(
     context: &RewriteContext<'_>,
     shape: Shape,
@@ -419,6 +436,13 @@ pub(crate) fn rewrite_macro_def(
         shape
     };
 
+    if parsed_def.branches.len() == 0 {
+        let lo = context.snippet_provider.span_before(span, "{");
+        result += " ";
+        result += &rewrite_empty_macro_def_body(context, span.with_lo(lo), shape)?;
+        return Some(result);
+    }
+
     let branch_items = itemize_list(
         context.snippet_provider,
         parsed_def.branches.iter(),
diff --git a/src/string.rs b/src/string.rs
index 78b72a50..cb666fff 100644
--- a/src/string.rs
+++ b/src/string.rs
@@ -1,7 +1,7 @@
 // Format string literals.
 
 use regex::Regex;
-use unicode_categories::UnicodeCategories;
+use unicode_properties::{GeneralCategory, UnicodeGeneralCategory};
 use unicode_segmentation::UnicodeSegmentation;
 
 use crate::config::Config;
@@ -366,7 +366,7 @@ fn is_whitespace(grapheme: &str) -> bool {
 fn is_punctuation(grapheme: &str) -> bool {
     grapheme
         .chars()
-        .all(UnicodeCategories::is_punctuation_other)
+        .all(|c| c.general_category() == GeneralCategory::OtherPunctuation)
 }
 
 fn graphemes_width(graphemes: &[&str]) -> usize {


Test patch:
diff --git a/tests/source/issue_5882.rs b/tests/source/issue_5882.rs
new file mode 100644
index 00000000..e36f9965
--- /dev/null
+++ b/tests/source/issue_5882.rs
@@ -0,0 +1,7 @@
+macro_rules!foo{}
+macro_rules!bar{/*comment*/}
+macro_rules!baz{//comment
+}
+macro_rules!foobar{
+//comment
+}
diff --git a/tests/target/issue_5882.rs b/tests/target/issue_5882.rs
new file mode 100644
index 00000000..565fb434
--- /dev/null
+++ b/tests/target/issue_5882.rs
@@ -0,0 +1,7 @@
+macro_rules! foo {}
+macro_rules! bar { /*comment*/ }
+macro_rules! baz { //comment
+}
+macro_rules! foobar {
+    //comment
+}