Repository: rust-lang/rustfmt (rust)

Issue:
`#[rustfmt::skip::attributes(derive)]` not work.
```rust
    #[rustfmt::skip::attributes(derive)]
    #[allow(dead_code)]
    #[derive(StructField)]
    #[derive(Clone)]
    struct Struct {
        field: String,
    }
```

It will merge `#derive(StructField)` and `#[derive(Clone)]` to `#derive(StructField, Clone)]`, when i run `cargo fmt`.

I don't want to merge `#derive(StructField)` and `#[derive(Clone)]`.

Pull request description:
Honor `#[rustfmt::skip::attributes(derive)]` attribute
Fixes #5270

Previously, rustfmt only checked the `merge_derives` configuration value
to determine if it should merge_derives. This lead to derives being
merged even when annotated with the `rustfmt::skip` attribute.

Now, rustfmt also checks if derives are explicitly being skipped in the
current context via the `rustfmt::skip` attribute.

Patch:
diff --git a/Configurations.md b/Configurations.md
index ecec97dc..a47439b9 100644
--- a/Configurations.md
+++ b/Configurations.md
@@ -2200,6 +2200,40 @@ specific version of rustfmt is used in your CI, use this option.
 - **Possible values**: any published version (e.g. `"0.3.8"`)
 - **Stable**: No (tracking issue: [#3386](https://github.com/rust-lang/rustfmt/issues/3386))
 
+## `short_array_element_width_threshold`
+
+The width threshold for an array element to be considered "short".
+
+The layout of an array is dependent on the length of each of its elements. 
+If the length of every element in an array is below this threshold (all elements are "short") then the array can be formatted in the mixed/compressed style, but if any one element has a length that exceeds this threshold then the array elements will have to be formatted vertically.
+
+- **Default value**: `10`
+- **Possible values**: any positive integer that is less than or equal to the value specified for [`max_width`](#max_width)
+- **Stable**: Yes
+
+#### `10` (default):
+```rust
+fn main() {
+    pub const FORMAT_TEST: [u64; 5] = [
+        0x0000000000000000,
+        0xaaaaaaaaaaaaaaaa,
+        0xbbbbbbbbbbbbbbbb,
+        0xcccccccccccccccc,
+        0xdddddddddddddddd,
+    ];
+}
+```
+#### `20`:
+```rust
+fn main() {
+    pub const FORMAT_TEST: [u64; 5] = [
+        0x0000000000000000, 0xaaaaaaaaaaaaaaaa, 0xbbbbbbbbbbbbbbbb, 0xcccccccccccccccc,
+        0xdddddddddddddddd,
+    ];
+}
+```
+See also [`max_width`](#max_width).
+
 ## `skip_children`
 
 Don't reformat out of line modules
diff --git a/src/attr.rs b/src/attr.rs
index 3887a805..befe12ae 100644
--- a/src/attr.rs
+++ b/src/attr.rs
@@ -389,6 +389,10 @@ impl Rewrite for [ast::Attribute] {
         let mut attrs = self;
         let mut result = String::new();
 
+        // Determine if the source text is annotated with `#[rustfmt::skip::attributes(derive)]`
+        // or `#![rustfmt::skip::attributes(derive)]`
+        let skip_derives = context.skip_context.skip_attribute("derive");
+
         // This is not just a simple map because we need to handle doc comments
         // (where we take as many doc comment attributes as possible) and possibly
         // merging derives into a single attribute.
@@ -431,7 +435,7 @@ impl Rewrite for [ast::Attribute] {
             }
 
             // Handle derives if we will merge them.
-            if context.config.merge_derives() && is_derive(&attrs[0]) {
+            if !skip_derives && context.config.merge_derives() && is_derive(&attrs[0]) {
                 let derives = take_while_with_pred(context, attrs, is_derive);
                 let derive_str = format_derive(derives, shape, context)?;
                 result.push_str(&derive_str);
diff --git a/src/config/mod.rs b/src/config/mod.rs
index 5041e1e3..18e18546 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -106,6 +106,8 @@ create_config! {
     // Misc.
     remove_nested_parens: bool, true, true, "Remove nested parens";
     combine_control_expr: bool, true, false, "Combine control expressions with function calls";
+    short_array_element_width_threshold: usize, 10, true,
+        "Width threshold for an array element to be considered short";
     overflow_delimited_expr: bool, false, false,
         "Allow trailing bracket/brace delimited expressions to overflow";
     struct_field_align_threshold: usize, 0, false,
@@ -591,6 +593,7 @@ spaces_around_ranges = false
 binop_separator = "Front"
 remove_nested_parens = true
 combine_control_expr = true
+short_array_element_width_threshold = 10
 overflow_delimited_expr = false
 struct_field_align_threshold = 0
 enum_discrim_align_threshold = 0
diff --git a/src/overflow.rs b/src/overflow.rs
index 3475f5c3..80aed998 100644
--- a/src/overflow.rs
+++ b/src/overflow.rs
@@ -26,8 +26,6 @@ use crate::spanned::Spanned;
 use crate::types::{can_be_overflowed_type, SegmentParam};
 use crate::utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp};
 
-const SHORT_ITEM_THRESHOLD: usize = 10;
-
 /// A list of `format!`-like macros, that take a long format string and a list of arguments to
 /// format.
 ///
@@ -572,7 +570,12 @@ impl<'a> Context<'a> {
                             if one_line {
                                 tactic = DefinitiveListTactic::SpecialMacro(num_args_before);
                             };
-                        } else if is_every_expr_simple(&self.items) && no_long_items(list_items) {
+                        } else if is_every_expr_simple(&self.items)
+                            && no_long_items(
+                                list_items,
+                                self.context.config.short_array_element_width_threshold(),
+                            )
+                        {
                             tactic = DefinitiveListTactic::Mixed;
                         }
                     }
@@ -755,9 +758,9 @@ fn shape_from_indent_style(
     }
 }
 
-fn no_long_items(list: &[ListItem]) -> bool {
+fn no_long_items(list: &[ListItem], short_array_element_width_threshold: usize) -> bool {
     list.iter()
-        .all(|item| item.inner_as_ref().len() <= SHORT_ITEM_THRESHOLD)
+        .all(|item| item.inner_as_ref().len() <= short_array_element_width_threshold)
 }
 
 /// In case special-case style is required, returns an offset from which we start horizontal layout.


Test patch:
diff --git a/tests/source/configs/short_array_element_width_threshold/10.rs b/tests/source/configs/short_array_element_width_threshold/10.rs
new file mode 100644
index 00000000..7d0d7091
--- /dev/null
+++ b/tests/source/configs/short_array_element_width_threshold/10.rs
@@ -0,0 +1,11 @@
+// rustfmt-short_array_element_width_threshold: 10
+
+fn main() {
+    pub const FORMAT_TEST: [u64; 5] = [
+        0x0000000000000000,
+        0xaaaaaaaaaaaaaaaa,
+        0xbbbbbbbbbbbbbbbb,
+        0xcccccccccccccccc,
+        0xdddddddddddddddd,
+    ];
+}
\ No newline at end of file
diff --git a/tests/source/configs/short_array_element_width_threshold/20.rs b/tests/source/configs/short_array_element_width_threshold/20.rs
new file mode 100644
index 00000000..8a93a51d
--- /dev/null
+++ b/tests/source/configs/short_array_element_width_threshold/20.rs
@@ -0,0 +1,11 @@
+// rustfmt-short_array_element_width_threshold: 20
+
+fn main() {
+    pub const FORMAT_TEST: [u64; 5] = [
+        0x0000000000000000,
+        0xaaaaaaaaaaaaaaaa,
+        0xbbbbbbbbbbbbbbbb,
+        0xcccccccccccccccc,
+        0xdddddddddddddddd,
+    ];
+}
\ No newline at end of file
diff --git a/tests/source/configs/short_array_element_width_threshold/greater_than_max_width.rs b/tests/source/configs/short_array_element_width_threshold/greater_than_max_width.rs
new file mode 100644
index 00000000..710b6fe7
--- /dev/null
+++ b/tests/source/configs/short_array_element_width_threshold/greater_than_max_width.rs
@@ -0,0 +1,12 @@
+// rustfmt-max_width: 20
+// rustfmt-short_array_element_width_threshold: 30
+
+fn main() {
+    pub const FORMAT_TEST: [u64; 5] = [
+        0x0000000000000000,
+        0xaaaaaaaaaaaaaaaa,
+        0xbbbbbbbbbbbbbbbb,
+        0xcccccccccccccccc,
+        0xdddddddddddddddd,
+    ];
+}
diff --git a/tests/source/issue-5270/merge_derives_true.rs b/tests/source/issue-5270/merge_derives_true.rs
new file mode 100644
index 00000000..b31bbf09
--- /dev/null
+++ b/tests/source/issue-5270/merge_derives_true.rs
@@ -0,0 +1,62 @@
+// rustfmt-merge_derives:true
+
+#[rustfmt::skip::attributes(derive)]
+#[allow(dead_code)]
+#[derive(StructField)]
+#[derive(Clone)]
+struct DoNotMergeDerives {
+    field: String,
+}
+
+#[allow(dead_code)]
+#[derive(StructField)]
+#[rustfmt::skip::attributes(derive)]
+#[derive(Clone)]
+struct DoNotMergeDerivesSkipInMiddle {
+    field: String,
+}
+
+#[allow(dead_code)]
+#[derive(StructField)]
+#[derive(Clone)]
+#[rustfmt::skip::attributes(derive)]
+struct DoNotMergeDerivesSkipAtEnd {
+    field: String,
+}
+
+#[allow(dead_code)]
+#[derive(StructField)]
+#[derive(Clone)]
+struct MergeDerives {
+    field: String,
+}
+
+mod inner_attribute_derive_skip {
+    #![rustfmt::skip::attributes(derive)]
+
+    #[allow(dead_code)]
+    #[derive(StructField)]
+    #[derive(Clone)]
+    struct DoNotMergeDerives {
+        field: String,
+    }
+}
+
+#[rustfmt::skip::attributes(derive)]
+mod outer_attribute_derive_skip {
+    #[allow(dead_code)]
+    #[derive(StructField)]
+    #[derive(Clone)]
+    struct DoNotMergeDerives {
+        field: String,
+    }
+}
+
+mod no_derive_skip {
+    #[allow(dead_code)]
+    #[derive(StructField)]
+    #[derive(Clone)]
+    struct MergeDerives {
+        field: String,
+    }
+}
diff --git a/tests/target/configs/short_array_element_width_threshold/10.rs b/tests/target/configs/short_array_element_width_threshold/10.rs
new file mode 100644
index 00000000..78c4adba
--- /dev/null
+++ b/tests/target/configs/short_array_element_width_threshold/10.rs
@@ -0,0 +1,11 @@
+// rustfmt-short_array_element_width_threshold: 10
+
+fn main() {
+    pub const FORMAT_TEST: [u64; 5] = [
+        0x0000000000000000,
+        0xaaaaaaaaaaaaaaaa,
+        0xbbbbbbbbbbbbbbbb,
+        0xcccccccccccccccc,
+        0xdddddddddddddddd,
+    ];
+}
diff --git a/tests/target/configs/short_array_element_width_threshold/20.rs b/tests/target/configs/short_array_element_width_threshold/20.rs
new file mode 100644
index 00000000..60846906
--- /dev/null
+++ b/tests/target/configs/short_array_element_width_threshold/20.rs
@@ -0,0 +1,8 @@
+// rustfmt-short_array_element_width_threshold: 20
+
+fn main() {
+    pub const FORMAT_TEST: [u64; 5] = [
+        0x0000000000000000, 0xaaaaaaaaaaaaaaaa, 0xbbbbbbbbbbbbbbbb, 0xcccccccccccccccc,
+        0xdddddddddddddddd,
+    ];
+}
diff --git a/tests/target/configs/short_array_element_width_threshold/greater_than_max_width.rs b/tests/target/configs/short_array_element_width_threshold/greater_than_max_width.rs
new file mode 100644
index 00000000..710b6fe7
--- /dev/null
+++ b/tests/target/configs/short_array_element_width_threshold/greater_than_max_width.rs
@@ -0,0 +1,12 @@
+// rustfmt-max_width: 20
+// rustfmt-short_array_element_width_threshold: 30
+
+fn main() {
+    pub const FORMAT_TEST: [u64; 5] = [
+        0x0000000000000000,
+        0xaaaaaaaaaaaaaaaa,
+        0xbbbbbbbbbbbbbbbb,
+        0xcccccccccccccccc,
+        0xdddddddddddddddd,
+    ];
+}
diff --git a/tests/target/issue-5270/merge_derives_false.rs b/tests/target/issue-5270/merge_derives_false.rs
new file mode 100644
index 00000000..3b6f7e66
--- /dev/null
+++ b/tests/target/issue-5270/merge_derives_false.rs
@@ -0,0 +1,62 @@
+// rustfmt-merge_derives:false
+
+#[rustfmt::skip::attributes(derive)]
+#[allow(dead_code)]
+#[derive(StructField)]
+#[derive(Clone)]
+struct DoNotMergeDerives {
+    field: String,
+}
+
+#[allow(dead_code)]
+#[derive(StructField)]
+#[rustfmt::skip::attributes(derive)]
+#[derive(Clone)]
+struct DoNotMergeDerivesSkipInMiddle {
+    field: String,
+}
+
+#[allow(dead_code)]
+#[derive(StructField)]
+#[derive(Clone)]
+#[rustfmt::skip::attributes(derive)]
+struct DoNotMergeDerivesSkipAtEnd {
+    field: String,
+}
+
+#[allow(dead_code)]
+#[derive(StructField)]
+#[derive(Clone)]
+struct MergeDerives {
+    field: String,
+}
+
+mod inner_attribute_derive_skip {
+    #![rustfmt::skip::attributes(derive)]
+
+    #[allow(dead_code)]
+    #[derive(StructField)]
+    #[derive(Clone)]
+    struct DoNotMergeDerives {
+        field: String,
+    }
+}
+
+#[rustfmt::skip::attributes(derive)]
+mod outer_attribute_derive_skip {
+    #[allow(dead_code)]
+    #[derive(StructField)]
+    #[derive(Clone)]
+    struct DoNotMergeDerives {
+        field: String,
+    }
+}
+
+mod no_derive_skip {
+    #[allow(dead_code)]
+    #[derive(StructField)]
+    #[derive(Clone)]
+    struct MergeDerives {
+        field: String,
+    }
+}
diff --git a/tests/target/issue-5270/merge_derives_true.rs b/tests/target/issue-5270/merge_derives_true.rs
new file mode 100644
index 00000000..5f488b45
--- /dev/null
+++ b/tests/target/issue-5270/merge_derives_true.rs
@@ -0,0 +1,60 @@
+// rustfmt-merge_derives:true
+
+#[rustfmt::skip::attributes(derive)]
+#[allow(dead_code)]
+#[derive(StructField)]
+#[derive(Clone)]
+struct DoNotMergeDerives {
+    field: String,
+}
+
+#[allow(dead_code)]
+#[derive(StructField)]
+#[rustfmt::skip::attributes(derive)]
+#[derive(Clone)]
+struct DoNotMergeDerivesSkipInMiddle {
+    field: String,
+}
+
+#[allow(dead_code)]
+#[derive(StructField)]
+#[derive(Clone)]
+#[rustfmt::skip::attributes(derive)]
+struct DoNotMergeDerivesSkipAtEnd {
+    field: String,
+}
+
+#[allow(dead_code)]
+#[derive(StructField, Clone)]
+struct MergeDerives {
+    field: String,
+}
+
+mod inner_attribute_derive_skip {
+    #![rustfmt::skip::attributes(derive)]
+
+    #[allow(dead_code)]
+    #[derive(StructField)]
+    #[derive(Clone)]
+    struct DoNotMergeDerives {
+        field: String,
+    }
+}
+
+#[rustfmt::skip::attributes(derive)]
+mod outer_attribute_derive_skip {
+    #[allow(dead_code)]
+    #[derive(StructField)]
+    #[derive(Clone)]
+    struct DoNotMergeDerives {
+        field: String,
+    }
+}
+
+mod no_derive_skip {
+    #[allow(dead_code)]
+    #[derive(StructField, Clone)]
+    struct MergeDerives {
+        field: String,
+    }
+}