diff --git a/docs/rules/attributes-order.md b/docs/rules/attributes-order.md
index 5cf2a4be1..1b9bcc626 100644
--- a/docs/rules/attributes-order.md
+++ b/docs/rules/attributes-order.md
@@ -35,7 +35,13 @@ This rule aims to enforce ordering of component attributes. The default order is
- `OTHER_DIRECTIVES`
e.g. 'v-custom-directive'
- `OTHER_ATTR`
- e.g. 'custom-prop="foo"', 'v-bind:prop="foo"', ':prop="foo"'
+ alias for `[ATTR_DYNAMIC, ATTR_STATIC, ATTR_SHORTHAND_BOOL]`:
+ - `ATTR_DYNAMIC`
+ e.g. 'v-bind:prop="foo"', ':prop="foo"'
+ - `ATTR_STATIC`
+ e.g. 'prop="foo"', 'custom-prop="foo"'
+ - `ATTR_SHORTHAND_BOOL`
+ e.g. 'boolean-prop'
- `EVENTS`
e.g. '@click="functionCall"', 'v-on="event"'
- `CONTENT`
diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js
index d47001adf..e6915aebf 100644
--- a/lib/rules/attributes-order.js
+++ b/lib/rules/attributes-order.js
@@ -20,6 +20,9 @@ const ATTRS = {
TWO_WAY_BINDING: 'TWO_WAY_BINDING',
OTHER_DIRECTIVES: 'OTHER_DIRECTIVES',
OTHER_ATTR: 'OTHER_ATTR',
+ ATTR_STATIC: 'ATTR_STATIC',
+ ATTR_DYNAMIC: 'ATTR_DYNAMIC',
+ ATTR_SHORTHAND_BOOL: 'ATTR_SHORTHAND_BOOL',
EVENTS: 'EVENTS',
CONTENT: 'CONTENT'
}
@@ -66,6 +69,15 @@ function isVBindObject(node) {
return isVBind(node) && node.key.argument == null
}
+/**
+ * Check whether the given attribute is a shorthand boolean like `selected`.
+ * @param {VAttribute | VDirective | undefined | null} node
+ * @returns { node is VAttribute }
+ */
+function isVShorthandBoolean(node) {
+ return isVAttribute(node) && !node.value
+}
+
/**
* @param {VAttribute | VDirective} attribute
* @param {SourceCode} sourceCode
@@ -153,7 +165,13 @@ function getAttributeType(attribute) {
case 'slot-scope':
return ATTRS.SLOT
default:
- return ATTRS.OTHER_ATTR
+ if (isVBind(attribute)) {
+ return ATTRS.ATTR_DYNAMIC
+ }
+ if (isVShorthandBoolean(attribute)) {
+ return ATTRS.ATTR_SHORTHAND_BOOL
+ }
+ return ATTRS.ATTR_STATIC
}
}
@@ -191,6 +209,11 @@ function isAlphabetical(prevNode, currNode, sourceCode) {
*/
function create(context) {
const sourceCode = context.getSourceCode()
+ const otherAttrs = [
+ ATTRS.ATTR_DYNAMIC,
+ ATTRS.ATTR_STATIC,
+ ATTRS.ATTR_SHORTHAND_BOOL
+ ]
let attributeOrder = [
ATTRS.DEFINITION,
ATTRS.LIST_RENDERING,
@@ -200,12 +223,36 @@ function create(context) {
[ATTRS.UNIQUE, ATTRS.SLOT],
ATTRS.TWO_WAY_BINDING,
ATTRS.OTHER_DIRECTIVES,
- ATTRS.OTHER_ATTR,
+ otherAttrs,
ATTRS.EVENTS,
ATTRS.CONTENT
]
if (context.options[0] && context.options[0].order) {
- attributeOrder = context.options[0].order
+ attributeOrder = [...context.options[0].order]
+
+ // check if `OTHER_ATTR` is valid
+ for (const item of attributeOrder.flat()) {
+ if (item === ATTRS.OTHER_ATTR) {
+ for (const attribute of attributeOrder.flat()) {
+ if (otherAttrs.includes(attribute)) {
+ throw new Error(
+ `Value "${ATTRS.OTHER_ATTR}" is not allowed with "${attribute}".`
+ )
+ }
+ }
+ }
+ }
+
+ // expand `OTHER_ATTR` alias
+ for (const [index, item] of attributeOrder.entries()) {
+ if (item === ATTRS.OTHER_ATTR) {
+ attributeOrder[index] = otherAttrs
+ } else if (Array.isArray(item) && item.includes(ATTRS.OTHER_ATTR)) {
+ const attributes = item.filter((i) => i !== ATTRS.OTHER_ATTR)
+ attributes.push(...otherAttrs)
+ attributeOrder[index] = attributes
+ }
+ }
}
const alphabetical = Boolean(
context.options[0] && context.options[0].alphabetical
diff --git a/tests/lib/rules/attributes-order.js b/tests/lib/rules/attributes-order.js
index 47099273d..9abb3a266 100644
--- a/tests/lib/rules/attributes-order.js
+++ b/tests/lib/rules/attributes-order.js
@@ -483,6 +483,138 @@ tester.run('attributes-order', rule, {
`,
options: [{ order: ['LIST_RENDERING', 'CONDITIONALS'] }]
+ },
+
+ // https://github.com/vuejs/eslint-plugin-vue/issues/1728
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ options: [
+ {
+ order: ['ATTR_DYNAMIC', 'ATTR_STATIC'],
+ alphabetical: false
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ options: [
+ {
+ order: ['TWO_WAY_BINDING', 'ATTR_DYNAMIC', 'ATTR_STATIC'],
+ alphabetical: false
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ options: [
+ {
+ order: [
+ 'DEFINITION',
+ 'LIST_RENDERING',
+ 'CONDITIONALS',
+ 'RENDER_MODIFIERS',
+ 'GLOBAL',
+ ['UNIQUE', 'SLOT'],
+ 'TWO_WAY_BINDING',
+ 'OTHER_DIRECTIVES',
+ 'ATTR_STATIC',
+ 'ATTR_DYNAMIC',
+ 'EVENTS',
+ 'CONTENT'
+ ],
+ alphabetical: false
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ options: [
+ {
+ order: [
+ 'DEFINITION',
+ 'LIST_RENDERING',
+ 'CONDITIONALS',
+ 'RENDER_MODIFIERS',
+ 'GLOBAL',
+ ['UNIQUE', 'SLOT'],
+ 'TWO_WAY_BINDING',
+ 'OTHER_DIRECTIVES',
+ ['ATTR_STATIC', 'ATTR_DYNAMIC'],
+ 'EVENTS',
+ 'CONTENT'
+ ],
+ alphabetical: false
+ }
+ ]
+ },
+
+ // https://github.com/vuejs/eslint-plugin-vue/issues/1870
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ options: [
+ {
+ order: [
+ 'DEFINITION',
+ 'LIST_RENDERING',
+ 'CONDITIONALS',
+ 'RENDER_MODIFIERS',
+ 'GLOBAL',
+ ['UNIQUE', 'SLOT'],
+ 'TWO_WAY_BINDING',
+ 'OTHER_DIRECTIVES',
+ 'ATTR_SHORTHAND_BOOL',
+ 'ATTR_STATIC',
+ 'ATTR_DYNAMIC',
+ 'EVENTS',
+ 'CONTENT'
+ ],
+ alphabetical: false
+ }
+ ]
}
],
@@ -1528,6 +1660,110 @@ tester.run('attributes-order', rule, {
attr="foo"/>
`,
errors: ['Attribute "@click" should go before "v-bind".']
+ },
+
+ {
+ filename: 'test.vue',
+ code: `
+
+
+ `,
+ options: [{ order: ['ATTR_STATIC', 'ATTR_DYNAMIC'] }],
+ output: `
+
+
+ `,
+ errors: ['Attribute "prop-two" should go before "v-bind:prop-one".']
+ },
+
+ {
+ filename: 'test.vue',
+ code: `
+
+
+ `,
+ options: [
+ {
+ order: [
+ 'LIST_RENDERING',
+ 'CONDITIONALS',
+ 'RENDER_MODIFIERS',
+ 'TWO_WAY_BINDING',
+ 'OTHER_DIRECTIVES',
+ 'ATTR_DYNAMIC',
+ 'ATTR_STATIC',
+ 'ATTR_SHORTHAND_BOOL',
+ 'EVENTS'
+ ]
+ }
+ ],
+ output: `
+
+
+ `,
+ errors: [
+ 'Attribute "v-model" should go before ":prop-one".',
+ 'Attribute ":prop-three" should go before "prop-two".'
+ ]
+ },
+
+ {
+ filename: 'test.vue',
+ code: `
+
+
+ `,
+ options: [
+ {
+ order: [
+ 'UNIQUE',
+ 'LIST_RENDERING',
+ 'CONDITIONALS',
+ 'RENDER_MODIFIERS',
+ 'GLOBAL',
+ 'TWO_WAY_BINDING',
+ 'OTHER_DIRECTIVES',
+ ['ATTR_STATIC', 'ATTR_DYNAMIC', 'ATTR_SHORTHAND_BOOL'],
+ 'EVENTS',
+ 'CONTENT',
+ 'DEFINITION',
+ 'SLOT'
+ ]
+ }
+ ],
+ output: `
+
+
+ `,
+ errors: ['Attribute "v-model" should go before ":prop-one".']
}
]
})