diff --git a/lib/rules/no-boolean-default.js b/lib/rules/no-boolean-default.js index e278e3011..6b1f44e74 100644 --- a/lib/rules/no-boolean-default.js +++ b/lib/rules/no-boolean-default.js @@ -9,6 +9,7 @@ const utils = require('../utils') /** * @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp + * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp */ // ------------------------------------------------------------------------------ @@ -29,31 +30,10 @@ function isBooleanProp(prop) { } /** - * @typedef {ComponentObjectProp & { value : ObjectExpression }} ObjectExpressionProp + * @param {ObjectExpression} propDefValue */ - -/** - * @param {(ComponentArrayProp | ComponentObjectProp)[]} props - * @returns {ObjectExpressionProp[]} - */ -function getBooleanProps(props) { - return props.filter( - /** - * @param {ComponentArrayProp | ComponentObjectProp} prop - * @returns {prop is ObjectExpressionProp} - */ - (prop) => - prop.value != null && - prop.value.type === 'ObjectExpression' && - prop.value.properties.some(isBooleanProp) - ) -} - -/** - * @param {ObjectExpressionProp} propDef - */ -function getDefaultNode(propDef) { - return utils.findProperty(propDef.value, 'default') +function getDefaultNode(propDefValue) { + return utils.findProperty(propDefValue, 'default') } module.exports = { @@ -73,42 +53,78 @@ module.exports = { }, /** @param {RuleContext} context */ create(context) { - return utils.executeOnVueComponent(context, (obj) => { - const props = utils.getComponentProps(obj) - const booleanProps = getBooleanProps(props) - - if (!booleanProps.length) return - - const booleanType = context.options[0] || 'no-default' - - booleanProps.forEach((propDef) => { - const defaultNode = getDefaultNode(propDef) + const booleanType = context.options[0] || 'no-default' + /** + * @param {ComponentArrayProp | ComponentObjectProp | ComponentTypeProp} prop + * @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions] + */ + function processProp(prop, withDefaultsExpressions) { + if (prop.type === 'object') { + if (prop.value.type !== 'ObjectExpression') { + return + } + if (!prop.value.properties.some(isBooleanProp)) { + return + } + const defaultNode = getDefaultNode(prop.value) + if (!defaultNode) { + return + } + 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 } + verifyDefaultExpression(defaultNode) + } + } + /** + * @param {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp)[]} props + * @param { { [key: string]: Expression | undefined } } [withDefaultsExpressions] + */ + function processProps(props, withDefaultsExpressions) { + for (const prop of props) { + processProp(prop, withDefaultsExpressions) + } + } + + /** + * @param {Expression} defaultNode + */ + function verifyDefaultExpression(defaultNode) { + switch (booleanType) { + case 'no-default': + context.report({ + node: defaultNode, + message: + 'Boolean prop should not set a default (Vue defaults it to false).' + }) + break - switch (booleanType) { - case 'no-default': + case 'default-false': + if (defaultNode.type !== 'Literal' || defaultNode.value !== false) { context.report({ node: defaultNode, - message: - 'Boolean prop should not set a default (Vue defaults it to false).' + message: 'Boolean prop should only be defaulted to false.' }) - break - - case 'default-false': - if ( - defaultNode.value.type !== 'Literal' || - defaultNode.value.value !== false - ) { - context.report({ - node: defaultNode, - message: 'Boolean prop should only be defaulted to false.' - }) - } - break + } + break + } + } + return utils.compositingVisitors( + utils.executeOnVueComponent(context, (obj) => { + processProps(utils.getComponentProps(obj)) + }), + utils.defineScriptSetupVisitor(context, { + onDefinePropsEnter(node, props) { + processProps(props, utils.getWithDefaultsPropExpressions(node)) } }) - }) + ) } } diff --git a/tests/lib/rules/no-boolean-default.js b/tests/lib/rules/no-boolean-default.js index 7421a049d..ef1af7925 100644 --- a/tests/lib/rules/no-boolean-default.js +++ b/tests/lib/rules/no-boolean-default.js @@ -8,6 +8,7 @@ // Requirements // ------------------------------------------------------------------------------ +const semver = require('semver') const rule = require('../../../lib/rules/no-boolean-default') const RuleTester = require('eslint').RuleTester @@ -229,6 +230,102 @@ ruleTester.run('no-boolean-default', rule, { } `, options: ['default-false'] + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser') + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + options: ['default-false'] + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + options: ['default-false'] + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } } ], @@ -314,6 +411,103 @@ ruleTester.run('no-boolean-default', rule, { line: 6 } ] - } + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + errors: [ + { + message: + 'Boolean prop should not set a default (Vue defaults it to false).', + line: 6 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + options: ['default-false'], + errors: [ + { + message: 'Boolean prop should only be defaulted to false.', + line: 6 + } + ] + }, + ...(semver.lt( + require('@typescript-eslint/parser/package.json').version, + '4.0.0' + ) + ? [] + : [ + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + errors: [ + { + message: + 'Boolean prop should not set a default (Vue defaults it to false).', + line: 7 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + options: ['default-false'], + errors: [ + { + message: 'Boolean prop should only be defaulted to false.', + line: 7 + } + ] + } + ]) ] })