From ca0ec69de5c9a3bf19e4c2d0ab5ff8242a5e3dcf Mon Sep 17 00:00:00 2001 From: waynzh Date: Mon, 14 Apr 2025 12:13:15 +0800 Subject: [PATCH 1/5] feat: add define-props-destructuring rule --- docs/rules/define-props-destructuring.md | 95 ++++++++ docs/rules/index.md | 2 + lib/index.js | 1 + lib/rules/define-props-destructuring.js | 73 +++++++ tests/lib/rules/define-props-destructuring.js | 204 ++++++++++++++++++ 5 files changed, 375 insertions(+) create mode 100644 docs/rules/define-props-destructuring.md create mode 100644 lib/rules/define-props-destructuring.js create mode 100644 tests/lib/rules/define-props-destructuring.js diff --git a/docs/rules/define-props-destructuring.md b/docs/rules/define-props-destructuring.md new file mode 100644 index 000000000..39224d704 --- /dev/null +++ b/docs/rules/define-props-destructuring.md @@ -0,0 +1,95 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/define-props-destructuring +description: enforce consistent style for prop destructuring +--- + +# vue/define-props-destructuring + +> enforce consistent style for prop destructuring + +- :exclamation: _**This rule has not been released yet.**_ + +## :book: Rule Details + +This rule enforces a consistent style for handling Vue 3 Composition API props, allowing you to choose between requiring destructuring or prohibiting it. + +By default, the rule requires you to use destructuring syntax when using `defineProps` instead of storing props in a variable and warns against combining `withDefaults` with destructuring. + + + +```vue + +``` + + + +The rule applies to both JavaScript and TypeScript props: + + + +```vue + +``` + + + +## :wrench: Options + +```js +{ + "vue/define-props-destructuring": ["error", { + "destructure": "always" | "never" + }] +} +``` + +- `destructure` - Sets the destructuring preference for props + - `"always"` (default) - Requires destructuring when using `defineProps` and warns against using `withDefaults` with destructuring + - `"never"` - Requires using a variable to store props and prohibits destructuring + +### `"destructure": "never"` + + + +```vue + +``` + + + +## :books: Further Reading + +- [Reactive Props Destructure](https://vuejs.org/guide/components/props.html#reactive-props-destructure) + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/define-props-destructuring.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/define-props-destructuring.js) diff --git a/docs/rules/index.md b/docs/rules/index.md index 55c5b96c9..fc626d72a 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -218,6 +218,7 @@ For example: | [vue/define-emits-declaration] | enforce declaration style of `defineEmits` | | :hammer: | | [vue/define-macros-order] | enforce order of compiler macros (`defineProps`, `defineEmits`, etc.) | :wrench::bulb: | :lipstick: | | [vue/define-props-declaration] | enforce declaration style of `defineProps` | | :hammer: | +| [vue/define-props-destructuring] | enforce consistent style for prop destructuring | | :hammer: | | [vue/enforce-style-attribute] | enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags | | :hammer: | | [vue/html-button-has-type] | disallow usage of button without an explicit type attribute | | :hammer: | | [vue/html-comment-content-newline] | enforce unified line break in HTML comments | :wrench: | :lipstick: | @@ -398,6 +399,7 @@ The following rules extend the rules provided by ESLint itself and apply them to [vue/define-emits-declaration]: ./define-emits-declaration.md [vue/define-macros-order]: ./define-macros-order.md [vue/define-props-declaration]: ./define-props-declaration.md +[vue/define-props-destructuring]: ./define-props-destructuring.md [vue/dot-location]: ./dot-location.md [vue/dot-notation]: ./dot-notation.md [vue/enforce-style-attribute]: ./enforce-style-attribute.md diff --git a/lib/index.js b/lib/index.js index 834e5f28b..e511536fa 100644 --- a/lib/index.js +++ b/lib/index.js @@ -58,6 +58,7 @@ const plugin = { 'define-emits-declaration': require('./rules/define-emits-declaration'), 'define-macros-order': require('./rules/define-macros-order'), 'define-props-declaration': require('./rules/define-props-declaration'), + 'define-props-destructuring': require('./rules/define-props-destructuring'), 'dot-location': require('./rules/dot-location'), 'dot-notation': require('./rules/dot-notation'), 'enforce-style-attribute': require('./rules/enforce-style-attribute'), diff --git a/lib/rules/define-props-destructuring.js b/lib/rules/define-props-destructuring.js new file mode 100644 index 000000000..13ed4f6ad --- /dev/null +++ b/lib/rules/define-props-destructuring.js @@ -0,0 +1,73 @@ +/** + * @author Wayne Zhang + * See LICENSE file in root directory for full license. + */ +'use strict' + +const utils = require('../utils') + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'enforce consistent style for prop destructuring', + categories: undefined, + url: 'https://eslint.vuejs.org/rules/define-props-destructuring.html' + }, + fixable: undefined, + schema: [ + { + type: 'object', + properties: { + destructure: { + enum: ['always', 'never'] + } + }, + additionalProperties: false + } + ], + messages: { + preferDestructuring: 'Prefer destructuring from `defineProps` directly.', + avoidDestructuring: 'Avoid destructuring from `defineProps`.', + avoidWithDefaults: 'Avoid using `withDefaults` with destructuring.' + } + }, + /** @param {RuleContext} context */ + create(context) { + const options = context.options[0] || {} + const destructurePreference = options.destructure || 'always' + + return utils.compositingVisitors( + utils.defineScriptSetupVisitor(context, { + onDefinePropsEnter(node) { + const hasDestructure = utils.isUsingPropsDestructure(node) + const hasWithDefaults = utils.hasWithDefaults(node) + + if (destructurePreference === 'always') { + if (!hasDestructure) { + context.report({ + node, + messageId: 'preferDestructuring' + }) + return + } + + if (hasWithDefaults) { + context.report({ + node: node.parent.callee, + messageId: 'avoidWithDefaults' + }) + } + } else { + if (hasDestructure) { + context.report({ + node, + messageId: 'avoidDestructuring' + }) + } + } + } + }) + ) + } +} diff --git a/tests/lib/rules/define-props-destructuring.js b/tests/lib/rules/define-props-destructuring.js new file mode 100644 index 000000000..801f2fb63 --- /dev/null +++ b/tests/lib/rules/define-props-destructuring.js @@ -0,0 +1,204 @@ +/** + * @author Wayne Zhang + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/define-props-destructuring') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2015, + sourceType: 'module' + } +}) + +tester.run('define-props-destructuring', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + } + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ destructure: 'never' }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ destructure: 'never' }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ destructure: 'never' }], + languageOptions: { + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + } + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'preferDestructuring', + line: 3, + column: 21 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'preferDestructuring', + line: 3, + column: 34 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'avoidWithDefaults', + line: 3, + column: 23 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + }, + errors: [ + { + messageId: 'preferDestructuring', + line: 3, + column: 34 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + }, + errors: [ + { + messageId: 'avoidWithDefaults', + line: 3, + column: 23 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ destructure: 'never' }], + errors: [ + { + messageId: 'avoidDestructuring', + line: 3, + column: 23 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ destructure: 'never' }], + errors: [ + { + messageId: 'avoidDestructuring', + line: 3, + column: 36 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ destructure: 'never' }], + languageOptions: { + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + }, + errors: [ + { + messageId: 'avoidDestructuring', + line: 3, + column: 23 + } + ] + } + ] +}) From 8cf5d3bc4a084f90bcb9ac852a3647cc6d1a7952 Mon Sep 17 00:00:00 2001 From: waynzh Date: Mon, 14 Apr 2025 13:30:03 +0800 Subject: [PATCH 2/5] update --- lib/rules/define-props-destructuring.js | 9 +++++++-- tests/lib/rules/define-props-destructuring.js | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/rules/define-props-destructuring.js b/lib/rules/define-props-destructuring.js index 13ed4f6ad..8c95a9553 100644 --- a/lib/rules/define-props-destructuring.js +++ b/lib/rules/define-props-destructuring.js @@ -14,7 +14,7 @@ module.exports = { categories: undefined, url: 'https://eslint.vuejs.org/rules/define-props-destructuring.html' }, - fixable: undefined, + fixable: null, schema: [ { type: 'object', @@ -39,7 +39,12 @@ module.exports = { return utils.compositingVisitors( utils.defineScriptSetupVisitor(context, { - onDefinePropsEnter(node) { + onDefinePropsEnter(node, props) { + const hasNoArgs = props.filter((prop) => prop.propName).length === 0 + if (hasNoArgs) { + return + } + const hasDestructure = utils.isUsingPropsDestructure(node) const hasWithDefaults = utils.hasWithDefaults(node) diff --git a/tests/lib/rules/define-props-destructuring.js b/tests/lib/rules/define-props-destructuring.js index 801f2fb63..ec24b4328 100644 --- a/tests/lib/rules/define-props-destructuring.js +++ b/tests/lib/rules/define-props-destructuring.js @@ -17,6 +17,14 @@ const tester = new RuleTester({ tester.run('define-props-destructuring', rule, { valid: [ + { + filename: 'test.vue', + code: ` + + ` + }, { filename: 'test.vue', code: ` From 72f5898f71a59ea3b80d4c158ed53644268e20f5 Mon Sep 17 00:00:00 2001 From: Wayne Zhang Date: Thu, 17 Apr 2025 10:07:46 +0800 Subject: [PATCH 3/5] Update lib/rules/define-props-destructuring.js Co-authored-by: Flo Edelmann --- lib/rules/define-props-destructuring.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/define-props-destructuring.js b/lib/rules/define-props-destructuring.js index 8c95a9553..7ebdbcde2 100644 --- a/lib/rules/define-props-destructuring.js +++ b/lib/rules/define-props-destructuring.js @@ -10,7 +10,7 @@ module.exports = { meta: { type: 'suggestion', docs: { - description: 'enforce consistent style for prop destructuring', + description: 'enforce consistent style for props destructuring', categories: undefined, url: 'https://eslint.vuejs.org/rules/define-props-destructuring.html' }, From 1f2918ae8e8a5d1fa502f4c9c000114b294b9c65 Mon Sep 17 00:00:00 2001 From: Wayne Zhang Date: Thu, 17 Apr 2025 10:08:57 +0800 Subject: [PATCH 4/5] Update lib/rules/define-props-destructuring.js Co-authored-by: Flo Edelmann --- lib/rules/define-props-destructuring.js | 33 +++++++++++++------------ 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/rules/define-props-destructuring.js b/lib/rules/define-props-destructuring.js index 7ebdbcde2..65ec1dcd7 100644 --- a/lib/rules/define-props-destructuring.js +++ b/lib/rules/define-props-destructuring.js @@ -48,28 +48,29 @@ module.exports = { const hasDestructure = utils.isUsingPropsDestructure(node) const hasWithDefaults = utils.hasWithDefaults(node) - if (destructurePreference === 'always') { - if (!hasDestructure) { - context.report({ - node, - messageId: 'preferDestructuring' - }) - return - } - - if (hasWithDefaults) { - context.report({ - node: node.parent.callee, - messageId: 'avoidWithDefaults' - }) - } - } else { + if (destructurePreference === 'never') { if (hasDestructure) { context.report({ node, messageId: 'avoidDestructuring' }) } + return + } + + if (!hasDestructure) { + context.report({ + node, + messageId: 'preferDestructuring' + }) + return + } + + if (hasWithDefaults) { + context.report({ + node: node.parent.callee, + messageId: 'avoidWithDefaults' + }) } } }) From c8fbe35de023d88b52645021156b8c45b2dcc34e Mon Sep 17 00:00:00 2001 From: waynzh Date: Thu, 17 Apr 2025 10:10:12 +0800 Subject: [PATCH 5/5] docs: update --- docs/rules/define-props-destructuring.md | 4 ++-- docs/rules/index.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/rules/define-props-destructuring.md b/docs/rules/define-props-destructuring.md index 39224d704..7f3bc7849 100644 --- a/docs/rules/define-props-destructuring.md +++ b/docs/rules/define-props-destructuring.md @@ -2,12 +2,12 @@ pageClass: rule-details sidebarDepth: 0 title: vue/define-props-destructuring -description: enforce consistent style for prop destructuring +description: enforce consistent style for props destructuring --- # vue/define-props-destructuring -> enforce consistent style for prop destructuring +> enforce consistent style for props destructuring - :exclamation: _**This rule has not been released yet.**_ diff --git a/docs/rules/index.md b/docs/rules/index.md index fc626d72a..7828b58bb 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -218,7 +218,7 @@ For example: | [vue/define-emits-declaration] | enforce declaration style of `defineEmits` | | :hammer: | | [vue/define-macros-order] | enforce order of compiler macros (`defineProps`, `defineEmits`, etc.) | :wrench::bulb: | :lipstick: | | [vue/define-props-declaration] | enforce declaration style of `defineProps` | | :hammer: | -| [vue/define-props-destructuring] | enforce consistent style for prop destructuring | | :hammer: | +| [vue/define-props-destructuring] | enforce consistent style for props destructuring | | :hammer: | | [vue/enforce-style-attribute] | enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags | | :hammer: | | [vue/html-button-has-type] | disallow usage of button without an explicit type attribute | | :hammer: | | [vue/html-comment-content-newline] | enforce unified line break in HTML comments | :wrench: | :lipstick: |