Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 0b816dc

Browse files
Add new attributes order for dynamic and static props (#2068)
1 parent ad07deb commit 0b816dc

File tree

3 files changed

+293
-4
lines changed

3 files changed

+293
-4
lines changed

‎docs/rules/attributes-order.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ This rule aims to enforce ordering of component attributes. The default order is
3535
- `OTHER_DIRECTIVES`
3636
e.g. 'v-custom-directive'
3737
- `OTHER_ATTR`
38-
e.g. 'custom-prop="foo"', 'v-bind:prop="foo"', ':prop="foo"'
38+
alias for `[ATTR_DYNAMIC, ATTR_STATIC, ATTR_SHORTHAND_BOOL]`:
39+
- `ATTR_DYNAMIC`
40+
e.g. 'v-bind:prop="foo"', ':prop="foo"'
41+
- `ATTR_STATIC`
42+
e.g. 'prop="foo"', 'custom-prop="foo"'
43+
- `ATTR_SHORTHAND_BOOL`
44+
e.g. 'boolean-prop'
3945
- `EVENTS`
4046
e.g. '@click="functionCall"', 'v-on="event"'
4147
- `CONTENT`

‎lib/rules/attributes-order.js

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ const ATTRS = {
2020
TWO_WAY_BINDING: 'TWO_WAY_BINDING',
2121
OTHER_DIRECTIVES: 'OTHER_DIRECTIVES',
2222
OTHER_ATTR: 'OTHER_ATTR',
23+
ATTR_STATIC: 'ATTR_STATIC',
24+
ATTR_DYNAMIC: 'ATTR_DYNAMIC',
25+
ATTR_SHORTHAND_BOOL: 'ATTR_SHORTHAND_BOOL',
2326
EVENTS: 'EVENTS',
2427
CONTENT: 'CONTENT'
2528
}
@@ -66,6 +69,15 @@ function isVBindObject(node) {
6669
return isVBind(node) && node.key.argument == null
6770
}
6871

72+
/**
73+
* Check whether the given attribute is a shorthand boolean like `selected`.
74+
* @param {VAttribute | VDirective | undefined | null} node
75+
* @returns { node is VAttribute }
76+
*/
77+
function isVShorthandBoolean(node) {
78+
return isVAttribute(node) && !node.value
79+
}
80+
6981
/**
7082
* @param {VAttribute | VDirective} attribute
7183
* @param {SourceCode} sourceCode
@@ -153,7 +165,13 @@ function getAttributeType(attribute) {
153165
case 'slot-scope':
154166
return ATTRS.SLOT
155167
default:
156-
return ATTRS.OTHER_ATTR
168+
if (isVBind(attribute)) {
169+
return ATTRS.ATTR_DYNAMIC
170+
}
171+
if (isVShorthandBoolean(attribute)) {
172+
return ATTRS.ATTR_SHORTHAND_BOOL
173+
}
174+
return ATTRS.ATTR_STATIC
157175
}
158176
}
159177

@@ -191,6 +209,11 @@ function isAlphabetical(prevNode, currNode, sourceCode) {
191209
*/
192210
function create(context) {
193211
const sourceCode = context.getSourceCode()
212+
const otherAttrs = [
213+
ATTRS.ATTR_DYNAMIC,
214+
ATTRS.ATTR_STATIC,
215+
ATTRS.ATTR_SHORTHAND_BOOL
216+
]
194217
let attributeOrder = [
195218
ATTRS.DEFINITION,
196219
ATTRS.LIST_RENDERING,
@@ -200,12 +223,36 @@ function create(context) {
200223
[ATTRS.UNIQUE, ATTRS.SLOT],
201224
ATTRS.TWO_WAY_BINDING,
202225
ATTRS.OTHER_DIRECTIVES,
203-
ATTRS.OTHER_ATTR,
226+
otherAttrs,
204227
ATTRS.EVENTS,
205228
ATTRS.CONTENT
206229
]
207230
if (context.options[0] && context.options[0].order) {
208-
attributeOrder = context.options[0].order
231+
attributeOrder = [...context.options[0].order]
232+
233+
// check if `OTHER_ATTR` is valid
234+
for (const item of attributeOrder.flat()) {
235+
if (item === ATTRS.OTHER_ATTR) {
236+
for (const attribute of attributeOrder.flat()) {
237+
if (otherAttrs.includes(attribute)) {
238+
throw new Error(
239+
`Value "${ATTRS.OTHER_ATTR}" is not allowed with "${attribute}".`
240+
)
241+
}
242+
}
243+
}
244+
}
245+
246+
// expand `OTHER_ATTR` alias
247+
for (const [index, item] of attributeOrder.entries()) {
248+
if (item === ATTRS.OTHER_ATTR) {
249+
attributeOrder[index] = otherAttrs
250+
} else if (Array.isArray(item) && item.includes(ATTRS.OTHER_ATTR)) {
251+
const attributes = item.filter((i) => i !== ATTRS.OTHER_ATTR)
252+
attributes.push(...otherAttrs)
253+
attributeOrder[index] = attributes
254+
}
255+
}
209256
}
210257
const alphabetical = Boolean(
211258
context.options[0] && context.options[0].alphabetical

‎tests/lib/rules/attributes-order.js

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,138 @@ tester.run('attributes-order', rule, {
483483
</div>
484484
</template>`,
485485
options: [{ order: ['LIST_RENDERING', 'CONDITIONALS'] }]
486+
},
487+
488+
// https://github.com/vuejs/eslint-plugin-vue/issues/1728
489+
{
490+
filename: 'test.vue',
491+
code: `
492+
<template>
493+
<div
494+
:prop-bind="prop"
495+
prop-one="prop"
496+
prop-two="prop">
497+
</div>
498+
</template>`,
499+
options: [
500+
{
501+
order: ['ATTR_DYNAMIC', 'ATTR_STATIC'],
502+
alphabetical: false
503+
}
504+
]
505+
},
506+
{
507+
filename: 'test.vue',
508+
code: `
509+
<template>
510+
<div
511+
v-model="a
512+
:class="b"
513+
:is="c"
514+
prop-one="d"
515+
class="e"
516+
prop-two="f">
517+
</div>
518+
</template>`,
519+
options: [
520+
{
521+
order: ['TWO_WAY_BINDING', 'ATTR_DYNAMIC', 'ATTR_STATIC'],
522+
alphabetical: false
523+
}
524+
]
525+
},
526+
{
527+
filename: 'test.vue',
528+
code: `
529+
<template>
530+
<div
531+
prop-one="a"
532+
prop-three="b"
533+
:prop-two="c">
534+
</div>
535+
</template>`,
536+
options: [
537+
{
538+
order: [
539+
'DEFINITION',
540+
'LIST_RENDERING',
541+
'CONDITIONALS',
542+
'RENDER_MODIFIERS',
543+
'GLOBAL',
544+
['UNIQUE', 'SLOT'],
545+
'TWO_WAY_BINDING',
546+
'OTHER_DIRECTIVES',
547+
'ATTR_STATIC',
548+
'ATTR_DYNAMIC',
549+
'EVENTS',
550+
'CONTENT'
551+
],
552+
alphabetical: false
553+
}
554+
]
555+
},
556+
{
557+
filename: 'test.vue',
558+
code: `
559+
<template>
560+
<div
561+
prop-one="a"
562+
:prop-two="b"
563+
prop-three="c">
564+
</div>
565+
</template>`,
566+
options: [
567+
{
568+
order: [
569+
'DEFINITION',
570+
'LIST_RENDERING',
571+
'CONDITIONALS',
572+
'RENDER_MODIFIERS',
573+
'GLOBAL',
574+
['UNIQUE', 'SLOT'],
575+
'TWO_WAY_BINDING',
576+
'OTHER_DIRECTIVES',
577+
['ATTR_STATIC', 'ATTR_DYNAMIC'],
578+
'EVENTS',
579+
'CONTENT'
580+
],
581+
alphabetical: false
582+
}
583+
]
584+
},
585+
586+
// https://github.com/vuejs/eslint-plugin-vue/issues/1870
587+
{
588+
filename: 'test.vue',
589+
code: `
590+
<template>
591+
<div
592+
boolean-prop
593+
prop-one="a"
594+
prop-two="b"
595+
:prop-three="c">
596+
</div>
597+
</template>`,
598+
options: [
599+
{
600+
order: [
601+
'DEFINITION',
602+
'LIST_RENDERING',
603+
'CONDITIONALS',
604+
'RENDER_MODIFIERS',
605+
'GLOBAL',
606+
['UNIQUE', 'SLOT'],
607+
'TWO_WAY_BINDING',
608+
'OTHER_DIRECTIVES',
609+
'ATTR_SHORTHAND_BOOL',
610+
'ATTR_STATIC',
611+
'ATTR_DYNAMIC',
612+
'EVENTS',
613+
'CONTENT'
614+
],
615+
alphabetical: false
616+
}
617+
]
486618
}
487619
],
488620

@@ -1528,6 +1660,110 @@ tester.run('attributes-order', rule, {
15281660
attr="foo"/>
15291661
</template>`,
15301662
errors: ['Attribute "@click" should go before "v-bind".']
1663+
},
1664+
1665+
{
1666+
filename: 'test.vue',
1667+
code: `
1668+
<template>
1669+
<div
1670+
v-bind:prop-one="a"
1671+
prop-two="b"
1672+
:prop-three="c"/>
1673+
</template>`,
1674+
options: [{ order: ['ATTR_STATIC', 'ATTR_DYNAMIC'] }],
1675+
output: `
1676+
<template>
1677+
<div
1678+
prop-two="b"
1679+
v-bind:prop-one="a"
1680+
:prop-three="c"/>
1681+
</template>`,
1682+
errors: ['Attribute "prop-two" should go before "v-bind:prop-one".']
1683+
},
1684+
1685+
{
1686+
filename: 'test.vue',
1687+
code: `
1688+
<template>
1689+
<div
1690+
:prop-one="a"
1691+
v-model="value"
1692+
prop-two="b"
1693+
:prop-three="c"
1694+
class="class"
1695+
boolean-prop/>
1696+
</template>`,
1697+
options: [
1698+
{
1699+
order: [
1700+
'LIST_RENDERING',
1701+
'CONDITIONALS',
1702+
'RENDER_MODIFIERS',
1703+
'TWO_WAY_BINDING',
1704+
'OTHER_DIRECTIVES',
1705+
'ATTR_DYNAMIC',
1706+
'ATTR_STATIC',
1707+
'ATTR_SHORTHAND_BOOL',
1708+
'EVENTS'
1709+
]
1710+
}
1711+
],
1712+
output: `
1713+
<template>
1714+
<div
1715+
v-model="value"
1716+
:prop-one="a"
1717+
:prop-three="c"
1718+
prop-two="b"
1719+
class="class"
1720+
boolean-prop/>
1721+
</template>`,
1722+
errors: [
1723+
'Attribute "v-model" should go before ":prop-one".',
1724+
'Attribute ":prop-three" should go before "prop-two".'
1725+
]
1726+
},
1727+
1728+
{
1729+
filename: 'test.vue',
1730+
code: `
1731+
<template>
1732+
<div
1733+
:prop-one="a"
1734+
v-model="value"
1735+
boolean-prop
1736+
prop-two="b"
1737+
:prop-three="c"/>
1738+
</template>`,
1739+
options: [
1740+
{
1741+
order: [
1742+
'UNIQUE',
1743+
'LIST_RENDERING',
1744+
'CONDITIONALS',
1745+
'RENDER_MODIFIERS',
1746+
'GLOBAL',
1747+
'TWO_WAY_BINDING',
1748+
'OTHER_DIRECTIVES',
1749+
['ATTR_STATIC', 'ATTR_DYNAMIC', 'ATTR_SHORTHAND_BOOL'],
1750+
'EVENTS',
1751+
'CONTENT',
1752+
'DEFINITION',
1753+
'SLOT'
1754+
]
1755+
}
1756+
],
1757+
output: `
1758+
<template>
1759+
<div
1760+
v-model="value"
1761+
:prop-one="a"
1762+
boolean-prop
1763+
prop-two="b"
1764+
:prop-three="c"/>
1765+
</template>`,
1766+
errors: ['Attribute "v-model" should go before ":prop-one".']
15311767
}
15321768
]
15331769
})

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /