Skip to content

Commit 7d08da6

Browse files
committed
feat(attributes-order): add sortByLineLength option
1 parent ca97301 commit 7d08da6

File tree

3 files changed

+338
-4
lines changed

3 files changed

+338
-4
lines changed

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

@@ -1766,6 +1831,183 @@ tester.run('attributes-order', rule, {
17661831
}
17671832
],
17681833
errors: ['Attribute "v-model" should go before ":prop-one".']
1834+
},
1835+
{
1836+
filename: 'test.vue',
1837+
code: `
1838+
<template>
1839+
<div
1840+
medium-attr="value"
1841+
short="val"
1842+
very-long-attribute-name="value">
1843+
</div>
1844+
</template>`,
1845+
output: `
1846+
<template>
1847+
<div
1848+
short="val"
1849+
medium-attr="value"
1850+
very-long-attribute-name="value">
1851+
</div>
1852+
</template>`,
1853+
options: [{ sortLineLength: true }],
1854+
errors: ['Attribute "short" should go before "medium-attr".']
1855+
},
1856+
{
1857+
filename: 'test.vue',
1858+
code: `
1859+
<template>
1860+
<div
1861+
very-long-attribute-name="value"
1862+
short="val">
1863+
</div>
1864+
</template>`,
1865+
output: `
1866+
<template>
1867+
<div
1868+
short="val"
1869+
very-long-attribute-name="value">
1870+
</div>
1871+
</template>`,
1872+
options: [{ sortLineLength: true }],
1873+
errors: ['Attribute "short" should go before "very-long-attribute-name".']
1874+
},
1875+
{
1876+
filename: 'test.vue',
1877+
code: `
1878+
<template>
1879+
<div
1880+
id="id"
1881+
v-model="foo"
1882+
very-long-attribute-name="value"
1883+
short="val">
1884+
</div>
1885+
</template>`,
1886+
output: `
1887+
<template>
1888+
<div
1889+
id="id"
1890+
v-model="foo"
1891+
short="val"
1892+
very-long-attribute-name="value">
1893+
</div>
1894+
</template>`,
1895+
options: [{ sortLineLength: true }],
1896+
errors: ['Attribute "short" should go before "very-long-attribute-name".']
1897+
},
1898+
{
1899+
filename: 'test.vue',
1900+
code: `
1901+
<template>
1902+
<div
1903+
@mouseover="handleMouseover"
1904+
@click="fn"
1905+
@input="handleInput">
1906+
</div>
1907+
</template>`,
1908+
output: `
1909+
<template>
1910+
<div
1911+
@click="fn"
1912+
@mouseover="handleMouseover"
1913+
@input="handleInput">
1914+
</div>
1915+
</template>`,
1916+
options: [{ sortLineLength: true }],
1917+
errors: [
1918+
'Attribute "@click" should go before "@mouseover".',
1919+
'Attribute "@input" should go before "@mouseover".'
1920+
]
1921+
},
1922+
{
1923+
filename: 'test.vue',
1924+
code: `
1925+
<template>
1926+
<div
1927+
:abc="value"
1928+
:a="value"
1929+
:ab="value">
1930+
</div>
1931+
</template>`,
1932+
output: `
1933+
<template>
1934+
<div
1935+
:a="value"
1936+
:abc="value"
1937+
:ab="value">
1938+
</div>
1939+
</template>`,
1940+
options: [{ sortLineLength: true }],
1941+
errors: [
1942+
'Attribute ":a" should go before ":abc".',
1943+
'Attribute ":ab" should go before ":abc".'
1944+
]
1945+
},
1946+
{
1947+
filename: 'test.vue',
1948+
code: `
1949+
<template>
1950+
<div
1951+
very-long-binding="longerValue"
1952+
short="obj"
1953+
@click="fn">
1954+
</div>
1955+
</template>`,
1956+
output: `
1957+
<template>
1958+
<div
1959+
short="obj"
1960+
very-long-binding="longerValue"
1961+
@click="fn">
1962+
</div>
1963+
</template>`,
1964+
options: [{ sortLineLength: true }],
1965+
errors: ['Attribute "short" should go before "very-long-binding".']
1966+
},
1967+
{
1968+
filename: 'test.vue',
1969+
code: `
1970+
<template>
1971+
<div
1972+
aa="v"
1973+
z="v"
1974+
></div>
1975+
</template>`,
1976+
output: `
1977+
<template>
1978+
<div
1979+
z="v"
1980+
aa="v"
1981+
></div>
1982+
</template>`,
1983+
options: [{ sortLineLength: true, alphabetical: true }],
1984+
errors: [{ message: 'Attribute "z" should go before "aa".' }]
1985+
},
1986+
{
1987+
filename: 'test.vue',
1988+
code: `
1989+
<template>
1990+
<div
1991+
zz="v"
1992+
bb="v"
1993+
@keyup="fn"
1994+
@click="fn"
1995+
></div>
1996+
</template>`,
1997+
output: `
1998+
<template>
1999+
<div
2000+
bb="v"
2001+
zz="v"
2002+
@click="fn"
2003+
@keyup="fn"
2004+
></div>
2005+
</template>`,
2006+
options: [{ sortLineLength: true, alphabetical: true }],
2007+
errors: [
2008+
{ message: 'Attribute "bb" should go before "zz".' },
2009+
{ message: 'Attribute "@click" should go before "@keyup".' }
2010+
]
17692011
}
17702012
]
17712013
})

0 commit comments

Comments
 (0)