From 4704ab60fbd7af7b61c45c8be23d0eabc66a7b21 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Wed, 18 Sep 2024 10:44:00 +0900 Subject: [PATCH] Add support for props destructure to `vue/require-default-prop` rule (#2552) --- lib/rules/require-default-prop.js | 67 ++++++++++++++------- tests/lib/rules/require-default-prop.js | 79 ++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 22 deletions(-) diff --git a/lib/rules/require-default-prop.js b/lib/rules/require-default-prop.js index 313574a14..b5d2564e8 100644 --- a/lib/rules/require-default-prop.js +++ b/lib/rules/require-default-prop.js @@ -7,6 +7,7 @@ /** * @typedef {import('../utils').ComponentProp} ComponentProp * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp + * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp * @typedef {ComponentObjectProp & { value: ObjectExpression} } ComponentObjectPropObject */ @@ -137,18 +138,23 @@ module.exports = { /** * @param {ComponentProp[]} props - * @param {boolean} [withDefaults] - * @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions] + * @param {(prop: ComponentObjectProp|ComponentTypeProp)=>boolean} [ignore] */ - function processProps(props, withDefaults, withDefaultsExpressions) { + function processProps(props, ignore) { for (const prop of props) { - if (prop.type === 'object' && !prop.node.shorthand) { + if (prop.type === 'object') { + if (prop.node.shorthand) { + continue + } if (!isWithoutDefaultValue(prop)) { continue } if (isBooleanProp(prop)) { continue } + if (ignore?.(prop)) { + continue + } const propName = prop.propName == null ? `[${context.getSourceCode().getText(prop.node.key)}]` @@ -161,26 +167,23 @@ module.exports = { propName } }) - } else if ( - prop.type === 'type' && - withDefaults && - withDefaultsExpressions - ) { + } else if (prop.type === 'type') { if (prop.required) { continue } if (prop.types.length === 1 && prop.types[0] === 'Boolean') { continue } - if (!withDefaultsExpressions[prop.propName]) { - context.report({ - node: prop.node, - messageId: `missingDefault`, - data: { - propName: prop.propName - } - }) + if (ignore?.(prop)) { + continue } + context.report({ + node: prop.node, + messageId: `missingDefault`, + data: { + propName: prop.propName + } + }) } } } @@ -188,11 +191,33 @@ module.exports = { return utils.compositingVisitors( utils.defineScriptSetupVisitor(context, { onDefinePropsEnter(node, props) { - processProps( - props, - utils.hasWithDefaults(node), + const hasWithDefaults = utils.hasWithDefaults(node) + const defaultsByWithDefaults = utils.getWithDefaultsPropExpressions(node) - ) + const isUsingPropsDestructure = utils.isUsingPropsDestructure(node) + const defaultsByAssignmentPatterns = + utils.getDefaultPropExpressionsForPropsDestructure(node) + + processProps(props, (prop) => { + if (prop.type === 'type') { + if (!hasWithDefaults) { + // If don't use withDefaults(), exclude it from the report. + return true + } + if (defaultsByWithDefaults[prop.propName]) { + return true + } + } + if (!isUsingPropsDestructure) { + return false + } + if (prop.propName == null) { + // If using Props Destructure but the property name cannot be determined, + // it will be ignored. + return true + } + return Boolean(defaultsByAssignmentPatterns[prop.propName]) + }) } }), utils.executeOnVue(context, (obj) => { diff --git a/tests/lib/rules/require-default-prop.js b/tests/lib/rules/require-default-prop.js index 3ac13bcb8..e352eddf3 100644 --- a/tests/lib/rules/require-default-prop.js +++ b/tests/lib/rules/require-default-prop.js @@ -351,6 +351,43 @@ ruleTester.run('require-default-prop', rule, { ...languageOptions, parserOptions: { parser: require.resolve('@typescript-eslint/parser') } } + }, + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parser: require('vue-eslint-parser'), + ...languageOptions + } + }, + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parser: require('vue-eslint-parser'), + ...languageOptions + } + }, + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parser: require('vue-eslint-parser'), + ...languageOptions + } } ], @@ -623,6 +660,46 @@ ruleTester.run('require-default-prop', rule, { } ] } - ]) + ]), + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parser: require('vue-eslint-parser'), + ...languageOptions + }, + errors: [ + { + message: "Prop 'bar' requires default value to be set.", + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parser: require('vue-eslint-parser'), + ...languageOptions + }, + errors: [ + { + message: "Prop 'foo' requires default value to be set.", + line: 3 + }, + { + message: "Prop 'bar' requires default value to be set.", + line: 3 + } + ] + } ] })