diff --git a/docs/rules/attributes-order.md b/docs/rules/attributes-order.md
index 8bcb8b8ea..9caad0b67 100644
--- a/docs/rules/attributes-order.md
+++ b/docs/rules/attributes-order.md
@@ -27,7 +27,9 @@ This rule aims to enforce ordering of component attributes. The default order is
- `GLOBAL`
e.g. 'id'
- `UNIQUE`
- e.g. 'ref', 'key', 'v-slot', 'slot'
+ e.g. 'ref', 'key'
+- `SLOT`
+ e.g. 'v-slot', 'slot'.
- `TWO_WAY_BINDING`
e.g. 'v-model'
- `OTHER_DIRECTIVES`
@@ -127,7 +129,7 @@ Note that `v-bind="object"` syntax is considered to be the same as the next or p
"CONDITIONALS",
"RENDER_MODIFIERS",
"GLOBAL",
- "UNIQUE",
+ ["UNIQUE", "SLOT"],
"TWO_WAY_BINDING",
"OTHER_DIRECTIVES",
"OTHER_ATTR",
diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js
index 96fb1ac1d..7c985e5e4 100644
--- a/lib/rules/attributes-order.js
+++ b/lib/rules/attributes-order.js
@@ -20,6 +20,7 @@ const ATTRS = {
RENDER_MODIFIERS: 'RENDER_MODIFIERS',
GLOBAL: 'GLOBAL',
UNIQUE: 'UNIQUE',
+ SLOT: 'SLOT',
TWO_WAY_BINDING: 'TWO_WAY_BINDING',
OTHER_DIRECTIVES: 'OTHER_DIRECTIVES',
OTHER_ATTR: 'OTHER_ATTR',
@@ -121,7 +122,7 @@ function getAttributeType(attribute) {
} else if (name === 'html' || name === 'text') {
return ATTRS.CONTENT
} else if (name === 'slot') {
- return ATTRS.UNIQUE
+ return ATTRS.SLOT
} else if (name === 'is') {
return ATTRS.DEFINITION
} else {
@@ -139,13 +140,10 @@ function getAttributeType(attribute) {
return ATTRS.DEFINITION
} else if (propName === 'id') {
return ATTRS.GLOBAL
- } else if (
- propName === 'ref' ||
- propName === 'key' ||
- propName === 'slot' ||
- propName === 'slot-scope'
- ) {
+ } else if (propName === 'ref' || propName === 'key') {
return ATTRS.UNIQUE
+ } else if (propName === 'slot' || propName === 'slot-scope') {
+ return ATTRS.SLOT
} else {
return ATTRS.OTHER_ATTR
}
@@ -154,12 +152,13 @@ function getAttributeType(attribute) {
/**
* @param {VAttribute | VDirective} attribute
* @param { { [key: string]: number } } attributePosition
+ * @returns {number | null} If the value is null, the order is omitted. Do not force the order.
*/
function getPosition(attribute, attributePosition) {
const attributeType = getAttributeType(attribute)
return attributePosition[attributeType] != null
? attributePosition[attributeType]
- : -1
+ : null
}
/**
@@ -190,7 +189,7 @@ function create(context) {
ATTRS.CONDITIONALS,
ATTRS.RENDER_MODIFIERS,
ATTRS.GLOBAL,
- ATTRS.UNIQUE,
+ [ATTRS.UNIQUE, ATTRS.SLOT],
ATTRS.TWO_WAY_BINDING,
ATTRS.OTHER_DIRECTIVES,
ATTRS.OTHER_ATTR,
@@ -267,74 +266,96 @@ function create(context) {
return utils.defineTemplateBodyVisitor(context, {
VStartTag(node) {
- const attributes = node.attributes.filter((node, index, attributes) => {
- if (
- isVBindObject(node) &&
- (isVAttributeOrVBind(attributes[index - 1]) ||
- isVAttributeOrVBind(attributes[index + 1]))
- ) {
- // In Vue 3, ignore the `v-bind:foo=" ... "` and `v-bind ="object"` syntax
- // as they behave differently if you change the order.
- return false
- }
- return true
- })
- if (attributes.length <= 1) {
+ const attributeAndPositions = getAttributeAndPositionList(node)
+ if (attributeAndPositions.length <= 1) {
return
}
- let previousNode = attributes[0]
- let previousPosition = getPositionFromAttrIndex(0)
- for (let index = 1; index < attributes.length; index++) {
- const node = attributes[index]
- const position = getPositionFromAttrIndex(index)
+ let {
+ attr: previousNode,
+ position: previousPosition
+ } = attributeAndPositions[0]
+ for (let index = 1; index < attributeAndPositions.length; index++) {
+ const { attr, position } = attributeAndPositions[index]
let valid = previousPosition <= position
if (valid && alphabetical && previousPosition === position) {
- valid = isAlphabetical(previousNode, node, sourceCode)
+ valid = isAlphabetical(previousNode, attr, sourceCode)
}
if (valid) {
- previousNode = node
+ previousNode = attr
previousPosition = position
} else {
- reportIssue(node, previousNode)
+ reportIssue(attr, previousNode)
}
}
+ }
+ })
- /**
- * @param {number} index
- * @returns {number}
- */
- function getPositionFromAttrIndex(index) {
- const node = attributes[index]
- if (isVBindObject(node)) {
- // node is `v-bind ="object"` syntax
+ /**
+ * @param {VStartTag} node
+ * @returns { { attr: ( VAttribute | VDirective ), position: number }[] }
+ */
+ function getAttributeAndPositionList(node) {
+ const attributes = node.attributes.filter((node, index, attributes) => {
+ if (
+ isVBindObject(node) &&
+ (isVAttributeOrVBind(attributes[index - 1]) ||
+ isVAttributeOrVBind(attributes[index + 1]))
+ ) {
+ // In Vue 3, ignore the `v-bind:foo=" ... "` and `v-bind ="object"` syntax
+ // as they behave differently if you change the order.
+ return false
+ }
+ return true
+ })
- // In Vue 3, if change the order of `v-bind:foo=" ... "` and `v-bind ="object"`,
- // the behavior will be different, so adjust so that there is no change in behavior.
+ const results = []
+ for (let index = 0; index < attributes.length; index++) {
+ const attr = attributes[index]
+ const position = getPositionFromAttrIndex(index)
+ if (position == null) {
+ // The omitted order is skipped.
+ continue
+ }
+ results.push({ attr, position })
+ }
+
+ return results
+
+ /**
+ * @param {number} index
+ * @returns {number | null}
+ */
+ function getPositionFromAttrIndex(index) {
+ const node = attributes[index]
+ if (isVBindObject(node)) {
+ // node is `v-bind ="object"` syntax
- const len = attributes.length
- for (let nextIndex = index + 1; nextIndex < len; nextIndex++) {
- const next = attributes[nextIndex]
+ // In Vue 3, if change the order of `v-bind:foo=" ... "` and `v-bind ="object"`,
+ // the behavior will be different, so adjust so that there is no change in behavior.
- if (isVAttributeOrVBind(next) && !isVBindObject(next)) {
- // It is considered to be in the same order as the next bind prop node.
- return getPositionFromAttrIndex(nextIndex)
- }
+ const len = attributes.length
+ for (let nextIndex = index + 1; nextIndex < len; nextIndex++) {
+ const next = attributes[nextIndex]
+
+ if (isVAttributeOrVBind(next) && !isVBindObject(next)) {
+ // It is considered to be in the same order as the next bind prop node.
+ return getPositionFromAttrIndex(nextIndex)
}
- for (let prevIndex = index - 1; prevIndex >= 0; prevIndex--) {
- const prev = attributes[prevIndex]
+ }
+ for (let prevIndex = index - 1; prevIndex >= 0; prevIndex--) {
+ const prev = attributes[prevIndex]
- if (isVAttributeOrVBind(prev) && !isVBindObject(prev)) {
- // It is considered to be in the same order as the prev bind prop node.
- return getPositionFromAttrIndex(prevIndex)
- }
+ if (isVAttributeOrVBind(prev) && !isVBindObject(prev)) {
+ // It is considered to be in the same order as the prev bind prop node.
+ return getPositionFromAttrIndex(prevIndex)
}
}
- return getPosition(node, attributePosition)
}
+ return getPosition(node, attributePosition)
}
- })
+ }
}
module.exports = {
diff --git a/tests/lib/rules/attributes-order.js b/tests/lib/rules/attributes-order.js
index 97c3b6e73..6933a01d1 100644
--- a/tests/lib/rules/attributes-order.js
+++ b/tests/lib/rules/attributes-order.js
@@ -421,6 +421,20 @@ tester.run('attributes-order', rule, {
`,
options: [{ alphabetical: true }]
+ },
+
+ // omit order
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ options: [{ order: ['LIST_RENDERING', 'CONDITIONALS'] }]
}
],
@@ -1213,6 +1227,113 @@ tester.run('attributes-order', rule, {
'Attribute "v-bind" should go before "v-on:click".',
'Attribute "v-if" should go before "v-on:click".'
]
+ },
+
+ // omit order
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ options: [{ order: ['LIST_RENDERING', 'CONDITIONALS'] }],
+ output: `
+
+
+
+ `,
+ errors: ['Attribute "v-for" should go before "v-if".']
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+
+ `,
+ options: [{ order: ['LIST_RENDERING', 'CONDITIONALS'] }],
+ output: `
+
+
+
+ `,
+ errors: ['Attribute "v-for" should go before "v-if".']
+ },
+
+ // slot
+ {
+ filename: 'test.vue',
+ options: [
+ {
+ order: [
+ 'UNIQUE',
+ 'LIST_RENDERING',
+ 'CONDITIONALS',
+ 'RENDER_MODIFIERS',
+ 'GLOBAL',
+ 'TWO_WAY_BINDING',
+ 'OTHER_DIRECTIVES',
+ 'OTHER_ATTR',
+ 'EVENTS',
+ 'CONTENT',
+ 'DEFINITION',
+ 'SLOT'
+ ]
+ }
+ ],
+ code:
+ '',
+ output:
+ '',
+ errors: [
+ {
+ message: 'Attribute "bar" should go before "v-slot".'
+ }
+ ]
+ },
+
+ {
+ filename: 'test.vue',
+ options: [
+ {
+ order: [
+ 'UNIQUE',
+ 'LIST_RENDERING',
+ 'CONDITIONALS',
+ 'RENDER_MODIFIERS',
+ 'GLOBAL',
+ 'TWO_WAY_BINDING',
+ 'OTHER_DIRECTIVES',
+ 'OTHER_ATTR',
+ 'EVENTS',
+ 'CONTENT',
+ 'DEFINITION',
+ 'SLOT'
+ ]
+ }
+ ],
+ code:
+ '',
+ output:
+ '',
+ errors: [
+ {
+ message: 'Attribute "ref" should go before "bar".'
+ }
+ ]
}
]
})