Skip to content

Commit adc460d

Browse files
0xA1337waynzh
andauthored
feat(attributes-order): add sortLineLength option (#2759)
Co-authored-by: waynzh <waynzh19@gmail.com>
1 parent 10d645e commit adc460d

File tree

4 files changed

+343
-4
lines changed

4 files changed

+343
-4
lines changed

.changeset/social-ghosts-dress.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-vue': minor
3+
---
4+
5+
Added `sortLineLength` option to [`vue/attributes-order`](https://eslint.vuejs.org/rules/attributes-order.html)

docs/rules/attributes-order.md

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ Note that `v-bind="object"` syntax is considered to be the same as the next or p
143143
"EVENTS",
144144
"CONTENT"
145145
],
146-
"alphabetical": false
146+
"alphabetical": false,
147+
"sortLineLength": false
147148
}]
148149
}
149150
```
@@ -201,6 +202,79 @@ Note that `v-bind="object"` syntax is considered to be the same as the next or p
201202

202203
</eslint-code-block>
203204

205+
### `"sortLineLength": true`
206+
207+
<eslint-code-block fix :rules="{'vue/attributes-order': ['error', {sortLineLength: true}]}">
208+
209+
```vue
210+
<template>
211+
<!-- ✓ GOOD -->
212+
<div
213+
a="short"
214+
abc="value"
215+
a-prop="longer"
216+
boolean-prop
217+
:my-prop="value"
218+
very-long-prop="value"
219+
@blur="functionCall"
220+
@change="functionCall"
221+
@input="handleInput">
222+
</div>
223+
224+
<!-- ✗ BAD -->
225+
<div
226+
very-long-prop="value"
227+
a="short"
228+
a-prop="longer">
229+
</div>
230+
231+
<div
232+
@input="handleInput"
233+
@blur="short">
234+
</div>
235+
236+
<div
237+
:my-prop="value"
238+
:a="short">
239+
</div>
240+
241+
</template>
242+
```
243+
244+
</eslint-code-block>
245+
246+
### `"alphabetical": true` with `"sortLineLength": true`
247+
248+
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.
249+
250+
<eslint-code-block fix :rules="{'vue/attributes-order': ['error', {alphabetical: true, sortLineLength: true}]}">
251+
252+
```vue
253+
<template>
254+
<!-- ✓ GOOD -->
255+
<div
256+
a="1"
257+
b="2"
258+
cc="3"
259+
dd="4"
260+
@keyup="fn"
261+
@submit="fn"
262+
></div>
263+
264+
<!-- ✗ BAD -->
265+
<div
266+
b="2"
267+
a="1"
268+
@submit="fn"
269+
@keyup="fn"
270+
dd="4"
271+
cc="3"
272+
></div>
273+
</template>
274+
```
275+
276+
</eslint-code-block>
277+
204278
### Custom orders
205279

206280
#### `['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'TWO_WAY_BINDING', 'DEFINITION', 'OTHER_DIRECTIVES', 'OTHER_ATTR', 'EVENTS', 'CONTENT']`

lib/rules/attributes-order.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,9 @@ function create(context) {
271271
const alphabetical = Boolean(
272272
context.options[0] && context.options[0].alphabetical
273273
)
274+
const sortLineLength = Boolean(
275+
context.options[0] && context.options[0].sortLineLength
276+
)
274277

275278
/** @type { { [key: string]: number } } */
276279
const attributePosition = {}
@@ -347,9 +350,23 @@ function create(context) {
347350
const { attr, position } = attributeAndPositions[index]
348351

349352
let valid = previousPosition <= position
350-
if (valid && alphabetical && previousPosition === position) {
351-
valid = isAlphabetical(previousNode, attr, sourceCode)
353+
if (valid && previousPosition === position) {
354+
let sortedByLength = false
355+
if (sortLineLength) {
356+
const prevText = sourceCode.getText(previousNode)
357+
const currText = sourceCode.getText(attr)
358+
359+
if (prevText.length !== currText.length) {
360+
valid = prevText.length < currText.length
361+
sortedByLength = true
362+
}
363+
}
364+
365+
if (alphabetical && !sortedByLength) {
366+
valid = isAlphabetical(previousNode, attr, sourceCode)
367+
}
352368
}
369+
353370
if (valid) {
354371
previousNode = attr
355372
previousPosition = position
@@ -450,7 +467,8 @@ module.exports = {
450467
uniqueItems: true,
451468
additionalItems: false
452469
},
453-
alphabetical: { type: 'boolean' }
470+
alphabetical: { type: 'boolean' },
471+
sortLineLength: { type: 'boolean' }
454472
},
455473
additionalProperties: false
456474
}

tests/lib/rules/attributes-order.js

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,71 @@ tester.run('attributes-order', rule, {
617617
alphabetical: false
618618
}
619619
]
620+
},
621+
{
622+
filename: 'test.vue',
623+
code: `
624+
<template>
625+
<div
626+
short="val"
627+
medium-attr="value"
628+
very-long-attribute-name="value">
629+
</div>
630+
</template>`,
631+
options: [{ sortLineLength: true }]
632+
},
633+
{
634+
filename: 'test.vue',
635+
code: `
636+
<template>
637+
<div
638+
v-if="condition"
639+
v-show="show"
640+
short="val"
641+
medium-attr="value"
642+
very-long-attribute-name="value">
643+
</div>
644+
</template>`,
645+
options: [{ sortLineLength: false }]
646+
},
647+
{
648+
filename: 'test.vue',
649+
code: `
650+
<template>
651+
<div
652+
@click="fn"
653+
@input="handleInput"
654+
@mouseover="handleMouseover">
655+
</div>
656+
</template>`,
657+
options: [{ sortLineLength: true }]
658+
},
659+
{
660+
filename: 'test.vue',
661+
code: `
662+
<template>
663+
<div
664+
:a="value"
665+
:ab="value"
666+
:abc="value">
667+
</div>
668+
</template>`,
669+
options: [{ sortLineLength: true }]
670+
},
671+
{
672+
filename: 'test.vue',
673+
code: `
674+
<template>
675+
<div
676+
bb="v"
677+
zz="v"
678+
aaa="v"
679+
@click="fn"
680+
@keyup="fn"
681+
@submit="fn"
682+
></div>
683+
</template>`,
684+
options: [{ sortLineLength: true, alphabetical: true }]
620685
}
621686
],
622687

@@ -2102,6 +2167,183 @@ tester.run('attributes-order', rule, {
21022167
endColumn: 26
21032168
}
21042169
]
2170+
},
2171+
{
2172+
filename: 'test.vue',
2173+
code: `
2174+
<template>
2175+
<div
2176+
medium-attr="value"
2177+
short="val"
2178+
very-long-attribute-name="value">
2179+
</div>
2180+
</template>`,
2181+
output: `
2182+
<template>
2183+
<div
2184+
short="val"
2185+
medium-attr="value"
2186+
very-long-attribute-name="value">
2187+
</div>
2188+
</template>`,
2189+
options: [{ sortLineLength: true }],
2190+
errors: ['Attribute "short" should go before "medium-attr".']
2191+
},
2192+
{
2193+
filename: 'test.vue',
2194+
code: `
2195+
<template>
2196+
<div
2197+
very-long-attribute-name="value"
2198+
short="val">
2199+
</div>
2200+
</template>`,
2201+
output: `
2202+
<template>
2203+
<div
2204+
short="val"
2205+
very-long-attribute-name="value">
2206+
</div>
2207+
</template>`,
2208+
options: [{ sortLineLength: true }],
2209+
errors: ['Attribute "short" should go before "very-long-attribute-name".']
2210+
},
2211+
{
2212+
filename: 'test.vue',
2213+
code: `
2214+
<template>
2215+
<div
2216+
id="id"
2217+
v-model="foo"
2218+
very-long-attribute-name="value"
2219+
short="val">
2220+
</div>
2221+
</template>`,
2222+
output: `
2223+
<template>
2224+
<div
2225+
id="id"
2226+
v-model="foo"
2227+
short="val"
2228+
very-long-attribute-name="value">
2229+
</div>
2230+
</template>`,
2231+
options: [{ sortLineLength: true }],
2232+
errors: ['Attribute "short" should go before "very-long-attribute-name".']
2233+
},
2234+
{
2235+
filename: 'test.vue',
2236+
code: `
2237+
<template>
2238+
<div
2239+
@mouseover="handleMouseover"
2240+
@click="fn"
2241+
@input="handleInput">
2242+
</div>
2243+
</template>`,
2244+
output: `
2245+
<template>
2246+
<div
2247+
@click="fn"
2248+
@mouseover="handleMouseover"
2249+
@input="handleInput">
2250+
</div>
2251+
</template>`,
2252+
options: [{ sortLineLength: true }],
2253+
errors: [
2254+
'Attribute "@click" should go before "@mouseover".',
2255+
'Attribute "@input" should go before "@mouseover".'
2256+
]
2257+
},
2258+
{
2259+
filename: 'test.vue',
2260+
code: `
2261+
<template>
2262+
<div
2263+
:abc="value"
2264+
:a="value"
2265+
:ab="value">
2266+
</div>
2267+
</template>`,
2268+
output: `
2269+
<template>
2270+
<div
2271+
:a="value"
2272+
:abc="value"
2273+
:ab="value">
2274+
</div>
2275+
</template>`,
2276+
options: [{ sortLineLength: true }],
2277+
errors: [
2278+
'Attribute ":a" should go before ":abc".',
2279+
'Attribute ":ab" should go before ":abc".'
2280+
]
2281+
},
2282+
{
2283+
filename: 'test.vue',
2284+
code: `
2285+
<template>
2286+
<div
2287+
very-long-binding="longerValue"
2288+
short="obj"
2289+
@click="fn">
2290+
</div>
2291+
</template>`,
2292+
output: `
2293+
<template>
2294+
<div
2295+
short="obj"
2296+
very-long-binding="longerValue"
2297+
@click="fn">
2298+
</div>
2299+
</template>`,
2300+
options: [{ sortLineLength: true }],
2301+
errors: ['Attribute "short" should go before "very-long-binding".']
2302+
},
2303+
{
2304+
filename: 'test.vue',
2305+
code: `
2306+
<template>
2307+
<div
2308+
aa="v"
2309+
z="v"
2310+
></div>
2311+
</template>`,
2312+
output: `
2313+
<template>
2314+
<div
2315+
z="v"
2316+
aa="v"
2317+
></div>
2318+
</template>`,
2319+
options: [{ sortLineLength: true, alphabetical: true }],
2320+
errors: [{ message: 'Attribute "z" should go before "aa".' }]
2321+
},
2322+
{
2323+
filename: 'test.vue',
2324+
code: `
2325+
<template>
2326+
<div
2327+
zz="v"
2328+
bb="v"
2329+
@keyup="fn"
2330+
@click="fn"
2331+
></div>
2332+
</template>`,
2333+
output: `
2334+
<template>
2335+
<div
2336+
bb="v"
2337+
zz="v"
2338+
@click="fn"
2339+
@keyup="fn"
2340+
></div>
2341+
</template>`,
2342+
options: [{ sortLineLength: true, alphabetical: true }],
2343+
errors: [
2344+
{ message: 'Attribute "bb" should go before "zz".' },
2345+
{ message: 'Attribute "@click" should go before "@keyup".' }
2346+
]
21052347
}
21062348
]
21072349
})

0 commit comments

Comments
 (0)