diff --git a/.changeset/social-ghosts-dress.md b/.changeset/social-ghosts-dress.md
new file mode 100644
index 000000000..1d9bd0c5f
--- /dev/null
+++ b/.changeset/social-ghosts-dress.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-vue': minor
+---
+
+Added `sortLineLength` option to [`vue/attributes-order`](https://eslint.vuejs.org/rules/attributes-order.html)
diff --git a/docs/rules/attributes-order.md b/docs/rules/attributes-order.md
index 74b3abaf6..7b44ccc5e 100644
--- a/docs/rules/attributes-order.md
+++ b/docs/rules/attributes-order.md
@@ -143,7 +143,8 @@ Note that `v-bind="object"` syntax is considered to be the same as the next or p
"EVENTS",
"CONTENT"
],
- "alphabetical": false
+ "alphabetical": false,
+ "sortLineLength": false
}]
}
```
@@ -201,6 +202,79 @@ Note that `v-bind="object"` syntax is considered to be the same as the next or p
+### `"sortLineLength": true`
+
+
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+### `"alphabetical": true` with `"sortLineLength": true`
+
+When `alphabetical` and `sortLineLength` are both set to `true`, attributes within the same group are sorted primarily by their line length, and then alphabetically as a tie-breaker for attributes with the same length. This provides a clean, predictable attribute order that enhances readability.
+
+
+
+```vue
+
+
+
+
+
+
+
+```
+
+
+
### Custom orders
#### `['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'TWO_WAY_BINDING', 'DEFINITION', 'OTHER_DIRECTIVES', 'OTHER_ATTR', 'EVENTS', 'CONTENT']`
diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js
index 50c9b452c..8fe461004 100644
--- a/lib/rules/attributes-order.js
+++ b/lib/rules/attributes-order.js
@@ -271,6 +271,9 @@ function create(context) {
const alphabetical = Boolean(
context.options[0] && context.options[0].alphabetical
)
+ const sortLineLength = Boolean(
+ context.options[0] && context.options[0].sortLineLength
+ )
/** @type { { [key: string]: number } } */
const attributePosition = {}
@@ -347,9 +350,23 @@ function create(context) {
const { attr, position } = attributeAndPositions[index]
let valid = previousPosition <= position - if (valid && alphabetical && previousPosition === position) { - valid = isAlphabetical(previousNode, attr, sourceCode) + if (valid && previousPosition === position) { + let sortedByLength = false + if (sortLineLength) { + const prevText = sourceCode.getText(previousNode) + const currText = sourceCode.getText(attr) + + if (prevText.length !== currText.length) { + valid = prevText.length < currText.length + sortedByLength = true + } + } + + if (alphabetical && !sortedByLength) { + valid = isAlphabetical(previousNode, attr, sourceCode) + } } + if (valid) { previousNode = attr previousPosition = position @@ -450,7 +467,8 @@ module.exports = { uniqueItems: true, additionalItems: false }, - alphabetical: { type: 'boolean' } + alphabetical: { type: 'boolean' }, + sortLineLength: { type: 'boolean' } }, additionalProperties: false } diff --git a/tests/lib/rules/attributes-order.js b/tests/lib/rules/attributes-order.js index eb4543d1d..edd58b23b 100644 --- a/tests/lib/rules/attributes-order.js +++ b/tests/lib/rules/attributes-order.js @@ -617,6 +617,71 @@ tester.run('attributes-order', rule, { alphabetical: false } ] + }, + { + filename: 'test.vue', + code: ` +
+
+
+ `,
+ options: [{ sortLineLength: true }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ options: [{ sortLineLength: false }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ options: [{ sortLineLength: true }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ options: [{ sortLineLength: true }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+ `,
+ options: [{ sortLineLength: true, alphabetical: true }]
}
],
@@ -2102,6 +2167,183 @@ tester.run('attributes-order', rule, {
endColumn: 26
}
]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ output: `
+
+
+
+ `,
+ options: [{ sortLineLength: true }],
+ errors: ['Attribute "short" should go before "medium-attr".']
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ output: `
+
+
+
+ `,
+ options: [{ sortLineLength: true }],
+ errors: ['Attribute "short" should go before "very-long-attribute-name".']
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ output: `
+
+
+
+ `,
+ options: [{ sortLineLength: true }],
+ errors: ['Attribute "short" should go before "very-long-attribute-name".']
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ output: `
+
+
+
+ `,
+ options: [{ sortLineLength: true }],
+ errors: [
+ 'Attribute "@click" should go before "@mouseover".',
+ 'Attribute "@input" should go before "@mouseover".'
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ output: `
+
+
+
+ `,
+ options: [{ sortLineLength: true }],
+ errors: [
+ 'Attribute ":a" should go before ":abc".',
+ 'Attribute ":ab" should go before ":abc".'
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ output: `
+
+
+
+ `,
+ options: [{ sortLineLength: true }],
+ errors: ['Attribute "short" should go before "very-long-binding".']
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+ `,
+ output: `
+
+
+ `,
+ options: [{ sortLineLength: true, alphabetical: true }],
+ errors: [{ message: 'Attribute "z" should go before "aa".' }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+ `,
+ output: `
+
+
+ `,
+ options: [{ sortLineLength: true, alphabetical: true }],
+ errors: [
+ { message: 'Attribute "bb" should go before "zz".' },
+ { message: 'Attribute "@click" should go before "@keyup".' }
+ ]
}
]
})