diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js index 5af387db0..0d9ea9ce8 100644 --- a/lib/rules/attributes-order.js +++ b/lib/rules/attributes-order.js @@ -36,6 +36,14 @@ const ATTRS = { function isVBind(node) { return Boolean(node && node.directive && node.key.name.name === 'bind') } +/** + * Check whether the given attribute is `v-model` directive. + * @param {VAttribute | VDirective | undefined | null} node + * @returns { node is VDirective } + */ +function isVModel(node) { + return Boolean(node && node.directive && node.key.name.name === 'model') +} /** * Check whether the given attribute is plain attribute. * @param {VAttribute | VDirective | undefined | null} node @@ -45,12 +53,12 @@ function isVAttribute(node) { return Boolean(node && !node.directive) } /** - * Check whether the given attribute is plain attribute or `v-bind` directive. + * Check whether the given attribute is plain attribute, `v-bind` directive or `v-model` directive. * @param {VAttribute | VDirective | undefined | null} node * @returns { node is VAttribute } */ -function isVAttributeOrVBind(node) { - return isVAttribute(node) || isVBind(node) +function isVAttributeOrVBindOrVModel(node) { + return isVAttribute(node) || isVBind(node) || isVModel(node) } /** @@ -235,8 +243,8 @@ function create(context) { if (isVBindObject(node)) { // prev, v-bind:foo, v-bind -> v-bind:foo, v-bind, prev - isMoveUp = isVAttributeOrVBind - } else if (isVAttributeOrVBind(node)) { + isMoveUp = isVAttributeOrVBindOrVModel + } else if (isVAttributeOrVBindOrVModel(node)) { // prev, v-bind, v-bind:foo -> v-bind, v-bind:foo, prev isMoveUp = isVBindObject } else { @@ -298,11 +306,13 @@ function create(context) { const attributes = node.attributes.filter((node, index, attributes) => { if ( isVBindObject(node) && - (isVAttributeOrVBind(attributes[index - 1]) || - isVAttributeOrVBind(attributes[index + 1])) + (isVAttributeOrVBindOrVModel(attributes[index - 1]) || + isVAttributeOrVBindOrVModel(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. + // In Vue 3, ignore `v-bind="object"`, which is + // a pair of `v-bind:foo="..."` and `v-bind="object"` and + // a pair of `v-model="..."` and `v-bind="object"`, + // because changing the order behaves differently. return false } return true @@ -330,14 +340,14 @@ function create(context) { if (isVBindObject(node)) { // node is `v-bind ="object"` syntax - // In Vue 3, if change the order of `v-bind:foo=" ... "` and `v-bind ="object"`, + // In Vue 3, if change the order of `v-bind:foo="..."`, `v-model="..."` and `v-bind="object"`, // the behavior will be different, so adjust so that there is no change in behavior. const len = attributes.length for (let nextIndex = index + 1; nextIndex < len; nextIndex++) { const next = attributes[nextIndex] - if (isVAttributeOrVBind(next) && !isVBindObject(next)) { + if (isVAttributeOrVBindOrVModel(next) && !isVBindObject(next)) { // It is considered to be in the same order as the next bind prop node. return getPositionFromAttrIndex(nextIndex) } diff --git a/tests/lib/rules/attributes-order.js b/tests/lib/rules/attributes-order.js index b08ab1612..3797b9181 100644 --- a/tests/lib/rules/attributes-order.js +++ b/tests/lib/rules/attributes-order.js @@ -442,6 +442,42 @@ tester.run('attributes-order', rule, { `, options: [{ alphabetical: true }] }, + { + filename: 'test.vue', + code: ` + ` + }, + { + filename: 'test.vue', + code: ` + ` + }, + { + filename: 'test.vue', + code: ` + ` + }, // omit order { @@ -1246,6 +1282,50 @@ tester.run('attributes-order', rule, { 'Attribute "v-if" should go before "v-on:click".' ] }, + { + filename: 'test.vue', + code: ` + `, + output: ` + `, + errors: ['Attribute "v-model" should go before "v-custom-directive".'] + }, + { + filename: 'test.vue', + code: ` + `, + output: ` + `, + errors: ['Attribute "v-bind:id" should go before "v-model".'] + }, // omit order {