diff --git a/.changeset/social-ghosts-dress.md b/.changeset/social-ghosts-dress.md
new file mode 100644
index 000000000..18f5aabd5
--- /dev/null
+++ b/.changeset/social-ghosts-dress.md
@@ -0,0 +1,5 @@
+---
+'eslint-plugin-vue': minor
+---
+
+[vue/attributes-order](https://eslint.vuejs.org/rules/attributes-order.html) can now sort attributes by line length within groups.
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 79ab47627..ba0c4be1e 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 }]
}
],
@@ -1766,6 +1831,183 @@ tester.run('attributes-order', rule, {
}
],
errors: ['Attribute "v-model" should go before ":prop-one".']
+ },
+ {
+ 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".' }
+ ]
}
]
})