From dc06c645f52f9b5c9027f8eeb8545aebfb8180a1 Mon Sep 17 00:00:00 2001 From: Stas Lashmanov Date: Thu, 1 Oct 2020 12:45:28 +0300 Subject: [PATCH 1/3] feat(attribute-order): add slot attribute --- docs/rules/attributes-order.md | 6 +++ lib/rules/attributes-order.js | 20 +++++----- tests/lib/rules/attributes-order.js | 61 +++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/docs/rules/attributes-order.md b/docs/rules/attributes-order.md index 8bcb8b8ea..e2bd0244a 100644 --- a/docs/rules/attributes-order.md +++ b/docs/rules/attributes-order.md @@ -38,6 +38,12 @@ This rule aims to enforce ordering of component attributes. The default order is e.g. '@click="functionCall"', 'v-on="event"' - `CONTENT` e.g. 'v-text', 'v-html' + +Since 7.0.1: + +- `SLOT` + e.g. 'v-slot', 'slot' (separately from `UNIQUE`). + It will inherit `UNIQUE` position by default. ### the default order diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js index 96fb1ac1d..9ef3567f3 100644 --- a/lib/rules/attributes-order.js +++ b/lib/rules/attributes-order.js @@ -24,7 +24,8 @@ const ATTRS = { OTHER_DIRECTIVES: 'OTHER_DIRECTIVES', OTHER_ATTR: 'OTHER_ATTR', EVENTS: 'EVENTS', - CONTENT: 'CONTENT' + CONTENT: 'CONTENT', + SLOT: 'SLOT' } /** @@ -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 } @@ -214,6 +212,10 @@ function create(context) { } else attributePosition[item] = i }) + if (!(ATTRS.SLOT in attributePosition)) { + attributePosition[ATTRS.SLOT] = attributePosition[ATTRS.GLOBAL] + } + /** * @param {VAttribute | VDirective} node * @param {VAttribute | VDirective} previousNode @@ -353,7 +355,7 @@ module.exports = { items: { type: 'string' }, - maxItems: 10, + maxItems: 11, minItems: 10 } } diff --git a/tests/lib/rules/attributes-order.js b/tests/lib/rules/attributes-order.js index 97c3b6e73..d6faaf340 100644 --- a/tests/lib/rules/attributes-order.js +++ b/tests/lib/rules/attributes-order.js @@ -1213,6 +1213,67 @@ tester.run('attributes-order', rule, { 'Attribute "v-bind" should go before "v-on:click".', 'Attribute "v-if" should go before "v-on:click".' ] + }, + { + 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".' + } + ] } ] }) From 856a99e6b15dc56ce4da66c87f8181ead65eb913 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Tue, 2 Feb 2021 11:11:36 +0900 Subject: [PATCH 2/3] update --- docs/rules/attributes-order.md | 12 +-- lib/rules/attributes-order.js | 127 ++++++++++++++++------------ tests/lib/rules/attributes-order.js | 60 +++++++++++++ 3 files changed, 137 insertions(+), 62 deletions(-) diff --git a/docs/rules/attributes-order.md b/docs/rules/attributes-order.md index e2bd0244a..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` @@ -38,12 +40,6 @@ This rule aims to enforce ordering of component attributes. The default order is e.g. '@click="functionCall"', 'v-on="event"' - `CONTENT` e.g. 'v-text', 'v-html' - -Since 7.0.1: - -- `SLOT` - e.g. 'v-slot', 'slot' (separately from `UNIQUE`). - It will inherit `UNIQUE` position by default. ### the default order @@ -133,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 9ef3567f3..df61ed218 100644 --- a/lib/rules/attributes-order.js +++ b/lib/rules/attributes-order.js @@ -20,12 +20,12 @@ 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', EVENTS: 'EVENTS', - CONTENT: 'CONTENT', - SLOT: 'SLOT' + CONTENT: 'CONTENT' } /** @@ -152,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 } /** @@ -188,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, @@ -212,10 +213,6 @@ function create(context) { } else attributePosition[item] = i }) - if (!(ATTRS.SLOT in attributePosition)) { - attributePosition[ATTRS.SLOT] = attributePosition[ATTRS.GLOBAL] - } - /** * @param {VAttribute | VDirective} node * @param {VAttribute | VDirective} previousNode @@ -269,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 + }) + + 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 - // 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. + /** + * @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 d6faaf340..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'] }] } ], @@ -1214,6 +1228,52 @@ tester.run('attributes-order', rule, { '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: [ From 102f81bf922d813dc0f24bd63d59cba2146f0a5d Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Tue, 2 Feb 2021 11:50:19 +0900 Subject: [PATCH 3/3] revert option schema --- lib/rules/attributes-order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js index df61ed218..7c985e5e4 100644 --- a/lib/rules/attributes-order.js +++ b/lib/rules/attributes-order.js @@ -374,7 +374,7 @@ module.exports = { items: { type: 'string' }, - maxItems: 11, + maxItems: 10, minItems: 10 } }