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".' } + ] } ] })