From 3d32c1b7f290a981fd821e70f562a26b363acb43 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 18 Sep 2024 10:44:09 +0900 Subject: [PATCH] Add support for props destructure to `vue/no-boolean-default` rule (#2553) --- lib/rules/no-boolean-default.js | 68 +++++++++++++++++---------- tests/lib/rules/no-boolean-default.js | 50 +++++++++++++++++++- 2 files changed, 92 insertions(+), 26 deletions(-) diff --git a/lib/rules/no-boolean-default.js b/lib/rules/no-boolean-default.js index 6796c0329..2ff6a2d06 100644 --- a/lib/rules/no-boolean-default.js +++ b/lib/rules/no-boolean-default.js @@ -8,18 +8,27 @@ const utils = require('../utils') /** * @typedef {import('../utils').ComponentProp} ComponentProp + * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp */ /** - * @param {Property | SpreadElement} prop + * @param {Expression|undefined} node + */ +function isBooleanIdentifier(node) { + return Boolean(node && node.type === 'Identifier' && node.name === 'Boolean') +} + +/** + * Detects whether given prop node is a Boolean + * @param {ComponentObjectProp} prop + * @return {Boolean} */ function isBooleanProp(prop) { + const value = utils.skipTSAsExpression(prop.value) return ( - prop.type === 'Property' && - prop.key.type === 'Identifier' && - prop.key.name === 'type' && - prop.value.type === 'Identifier' && - prop.value.name === 'Boolean' + isBooleanIdentifier(value) || + (value.type === 'ObjectExpression' && + isBooleanIdentifier(utils.findProperty(value, 'type')?.value)) ) } @@ -55,40 +64,40 @@ module.exports = { const booleanType = context.options[0] || 'no-default' /** * @param {ComponentProp} prop - * @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions] + * @param {(propName: string) => Expression[]} otherDefaultProvider */ - function processProp(prop, withDefaultsExpressions) { + function processProp(prop, otherDefaultProvider) { if (prop.type === 'object') { - if (prop.value.type !== 'ObjectExpression') { + if (!isBooleanProp(prop)) { return } - if (!prop.value.properties.some(isBooleanProp)) { - return + if (prop.value.type === 'ObjectExpression') { + const defaultNode = getDefaultNode(prop.value) + if (defaultNode) { + verifyDefaultExpression(defaultNode.value) + } } - const defaultNode = getDefaultNode(prop.value) - if (!defaultNode) { - return + if (prop.propName != null) { + for (const defaultNode of otherDefaultProvider(prop.propName)) { + verifyDefaultExpression(defaultNode) + } } - verifyDefaultExpression(defaultNode.value) } else if (prop.type === 'type') { if (prop.types.length !== 1 || prop.types[0] !== 'Boolean') { return } - const defaultNode = - withDefaultsExpressions && withDefaultsExpressions[prop.propName] - if (!defaultNode) { - return + for (const defaultNode of otherDefaultProvider(prop.propName)) { + verifyDefaultExpression(defaultNode) } - verifyDefaultExpression(defaultNode) } } /** * @param {ComponentProp[]} props - * @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions] + * @param {(propName: string) => Expression[]} otherDefaultProvider */ - function processProps(props, withDefaultsExpressions) { + function processProps(props, otherDefaultProvider) { for (const prop of props) { - processProp(prop, withDefaultsExpressions) + processProp(prop, otherDefaultProvider) } } @@ -118,11 +127,20 @@ module.exports = { } return utils.compositingVisitors( utils.executeOnVueComponent(context, (obj) => { - processProps(utils.getComponentPropsFromOptions(obj)) + processProps(utils.getComponentPropsFromOptions(obj), () => []) }), utils.defineScriptSetupVisitor(context, { onDefinePropsEnter(node, props) { - processProps(props, utils.getWithDefaultsPropExpressions(node)) + const defaultsByWithDefaults = + utils.getWithDefaultsPropExpressions(node) + const defaultsByAssignmentPatterns = + utils.getDefaultPropExpressionsForPropsDestructure(node) + processProps(props, (propName) => + [ + defaultsByWithDefaults[propName], + defaultsByAssignmentPatterns[propName]?.expression + ].filter(utils.isDef) + ) } }) ) diff --git a/tests/lib/rules/no-boolean-default.js b/tests/lib/rules/no-boolean-default.js index 6f275c671..98524760c 100644 --- a/tests/lib/rules/no-boolean-default.js +++ b/tests/lib/rules/no-boolean-default.js @@ -326,6 +326,18 @@ ruleTester.run('no-boolean-default', rule, { parser: require.resolve('@typescript-eslint/parser') } } + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['default-false'], + languageOptions: { + parser: require('vue-eslint-parser') + } } ], @@ -512,6 +524,42 @@ ruleTester.run('no-boolean-default', rule, { } ] } - ]) + ]), + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parser: require('vue-eslint-parser') + }, + errors: [ + { + message: + 'Boolean prop should not set a default (Vue defaults it to false).', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['default-false'], + languageOptions: { + parser: require('vue-eslint-parser') + }, + errors: [ + { + message: 'Boolean prop should only be defaulted to false.', + line: 3 + } + ] + } ] })