diff --git a/lib/rules/no-setup-props-destructure.js b/lib/rules/no-setup-props-destructure.js index e77f63c4d..ee3699c55 100644 --- a/lib/rules/no-setup-props-destructure.js +++ b/lib/rules/no-setup-props-destructure.js @@ -20,31 +20,40 @@ module.exports = { destructuring: 'Destructuring the `props` will cause the value to lose reactivity.', getProperty: - 'Getting a value from the `props` in root scope of `setup()` will cause the value to lose reactivity.' + 'Getting a value from the `props` in root scope of `{{scopeName}}` will cause the value to lose reactivity.' } }, /** @param {RuleContext} context */ create(context) { - /** @type {Map>} */ + /** + * @typedef {object} ScopePropsReferences + * @property {Set} refs + * @property {string} scopeName + */ + /** @type {Map} */ const setupScopePropsReferenceIds = new Map() /** * @param {ESNode} node * @param {string} messageId + * @param {string} scopeName */ - function report(node, messageId) { + function report(node, messageId, scopeName) { context.report({ node, - messageId + messageId, + data: { + scopeName + } }) } /** * @param {Pattern} left * @param {Expression | null} right - * @param {Set} propsReferenceIds + * @param {ScopePropsReferences} propsReferences */ - function verify(left, right, propsReferenceIds) { + function verify(left, right, propsReferences) { if (!right) { return } @@ -62,88 +71,142 @@ module.exports = { while (rightId.type === 'MemberExpression') { rightId = utils.skipChainExpression(rightId.object) } - if (rightId.type === 'Identifier' && propsReferenceIds.has(rightId)) { - report(left, 'getProperty') + if (rightId.type === 'Identifier' && propsReferences.refs.has(rightId)) { + report(left, 'getProperty', propsReferences.scopeName) } } /** * @typedef {object} ScopeStack * @property {ScopeStack | null} upper - * @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} functionNode + * @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program} scopeNode */ /** * @type {ScopeStack | null} */ let scopeStack = null - return utils.defineVueVisitor(context, { - ':function'(node) { - scopeStack = { - upper: scopeStack, - functionNode: node - } - }, - onSetupFunctionEnter(node) { - const propsParam = utils.skipDefaultParamValue(node.params[0]) - if (!propsParam) { - // no arguments - return - } - if (propsParam.type === 'RestElement') { - // cannot check - return - } - if ( - propsParam.type === 'ArrayPattern' || - propsParam.type === 'ObjectPattern' - ) { - report(propsParam, 'destructuring') - return - } + /** + * @param {Pattern | null} node + * @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program} scopeNode + * @param {string} scopeName + */ + function processPattern(node, scopeNode, scopeName) { + if (!node) { + // no arguments + return + } + if ( + node.type === 'RestElement' || + node.type === 'AssignmentPattern' || + node.type === 'MemberExpression' + ) { + // cannot check + return + } + if (node.type === 'ArrayPattern' || node.type === 'ObjectPattern') { + report(node, 'destructuring', scopeName) + return + } - const variable = findVariable(context.getScope(), propsParam) - if (!variable) { - return + const variable = findVariable(context.getScope(), node) + if (!variable) { + return + } + const propsReferenceIds = new Set() + for (const reference of variable.references) { + if (!reference.isRead()) { + continue } - const propsReferenceIds = new Set() - for (const reference of variable.references) { - if (!reference.isRead()) { - continue + + propsReferenceIds.add(reference.identifier) + } + setupScopePropsReferenceIds.set(scopeNode, { + refs: propsReferenceIds, + scopeName + }) + } + return utils.compositingVisitors( + { + /** + * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression | Program} node + */ + 'Program, :function'(node) { + scopeStack = { + upper: scopeStack, + scopeNode: node } + }, + /** + * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression | Program} node + */ + 'Program, :function:exit'(node) { + scopeStack = scopeStack && scopeStack.upper - propsReferenceIds.add(reference.identifier) - } - setupScopePropsReferenceIds.set(node, propsReferenceIds) - }, - VariableDeclarator(node) { - if (!scopeStack) { - return - } - const propsReferenceIds = setupScopePropsReferenceIds.get( - scopeStack.functionNode - ) - if (!propsReferenceIds) { - return + setupScopePropsReferenceIds.delete(node) + }, + /** + * @param {VariableDeclarator} node + */ + VariableDeclarator(node) { + if (!scopeStack) { + return + } + const propsReferenceIds = setupScopePropsReferenceIds.get( + scopeStack.scopeNode + ) + if (!propsReferenceIds) { + return + } + verify(node.id, node.init, propsReferenceIds) + }, + /** + * @param {AssignmentExpression} node + */ + AssignmentExpression(node) { + if (!scopeStack) { + return + } + const propsReferenceIds = setupScopePropsReferenceIds.get( + scopeStack.scopeNode + ) + if (!propsReferenceIds) { + return + } + verify(node.left, node.right, propsReferenceIds) } - verify(node.id, node.init, propsReferenceIds) }, - AssignmentExpression(node) { - if (!scopeStack) { - return + utils.defineScriptSetupVisitor(context, { + onDefinePropsEnter(node) { + let target = node + if ( + target.parent && + target.parent.type === 'CallExpression' && + target.parent.arguments[0] === target && + target.parent.callee.type === 'Identifier' && + target.parent.callee.name === 'withDefaults' + ) { + target = target.parent + } + if (!target.parent) { + return + } + + /** @type {Pattern|null} */ + let id = null + if (target.parent.type === 'VariableDeclarator') { + id = target.parent.init === target ? target.parent.id : null + } else if (target.parent.type === 'AssignmentExpression') { + id = target.parent.right === target ? target.parent.left : null + } + processPattern(id, context.getSourceCode().ast, ' + `, + errors: [ + { + messageId: 'getProperty', + line: 4 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'getProperty', + line: 4 + } + ] } ], invalid: [ @@ -428,6 +468,63 @@ tester.run('no-setup-props-destructure', rule, { line: 5 } ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + 'Destructuring the `props` will cause the value to lose reactivity.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + 'Getting a value from the `props` in root scope of ` + `, + errors: [ + { + messageId: 'getProperty', + line: 4 + }, + { + messageId: 'getProperty', + line: 5 + } + ] } ] })