diff --git a/lib/rules/destructuring-assignment.js b/lib/rules/destructuring-assignment.js index 2536bd671d..9652e37fcf 100644 --- a/lib/rules/destructuring-assignment.js +++ b/lib/rules/destructuring-assignment.js @@ -8,6 +8,7 @@ const Components = require('../util/Components'); const docsUrl = require('../util/docsUrl'); const isAssignmentLHS = require('../util/ast').isAssignmentLHS; const report = require('../util/report'); +const testReactVersion = require('../util/version').testReactVersion; const DEFAULT_OPTION = 'always'; @@ -94,6 +95,8 @@ module.exports = { const destructureInSignature = (context.options[1] && context.options[1].destructureInSignature) || 'ignore'; const sfcParams = createSFCParams(); + // set to save renamed var of useContext + const contextSet = new Set(); /** * @param {ASTNode} node We expect either an ArrowFunctionExpression, * FunctionDeclaration, or FunctionExpression @@ -128,7 +131,7 @@ module.exports = { function handleSFCUsage(node) { const propsName = sfcParams.propsName(); const contextName = sfcParams.contextName(); - // props.aProp || context.aProp + // props.aProp const isPropUsed = ( (propsName && node.object.name === propsName) || (contextName && node.object.name === contextName) @@ -142,6 +145,16 @@ module.exports = { }, }); } + + // const foo = useContext(aContext); + // foo.aProp + const isContextUsed = contextSet.has(node.object.name) && !isAssignmentLHS(node); + if (isContextUsed && configuration === 'always') { + context.report({ + node, + message: `Must use destructuring ${node.object.name} assignment`, + }); + } } function isInClassProperty(node) { @@ -176,8 +189,9 @@ module.exports = { } } - return { + const hasHooks = testReactVersion(context, '>= 16.9'); + return { FunctionDeclaration: handleStatelessComponent, ArrowFunctionExpression: handleStatelessComponent, @@ -212,13 +226,29 @@ module.exports = { const SFCComponent = components.get(context.getScope(node).block); const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern'); + const identifier = (node.init && node.id && node.id.type === 'Identifier'); // let {foo} = props; - const destructuringSFC = destructuring && (node.init.name === 'props' || node.init.name === 'context'); + const destructuringSFC = destructuring && node.init.name === 'props'; + // let {foo} = useContext(aContext); + const destructuringUseContext = hasHooks && destructuring && node.init.callee && node.init.callee.name === 'useContext'; + // let foo = useContext(aContext); + const assignUseContext = hasHooks && identifier && node.init.callee && node.init.callee.name === 'useContext'; // let {foo} = this.props; const destructuringClass = destructuring && node.init.object && node.init.object.type === 'ThisExpression' && ( node.init.property.name === 'props' || node.init.property.name === 'context' || node.init.property.name === 'state' ); + if (SFCComponent && assignUseContext) { + contextSet.add(node.id.name); + } + + if (SFCComponent && destructuringUseContext && configuration === 'never') { + context.report({ + node, + message: `Must never use destructuring ${node.init.callee.name} assignment`, + }); + } + if (SFCComponent && destructuringSFC && configuration === 'never') { report(context, messages.noDestructAssignment, 'noDestructAssignment', { node, diff --git a/tests/lib/rules/destructuring-assignment.js b/tests/lib/rules/destructuring-assignment.js index 738a9800ec..6c89907f29 100644 --- a/tests/lib/rules/destructuring-assignment.js +++ b/tests/lib/rules/destructuring-assignment.js @@ -358,6 +358,70 @@ ruleTester.run('destructuring-assignment', rule, { `, options: ['always', { destructureInSignature: 'always' }], }, + { + code: ` + import { useContext } from 'react'; + + const MyComponent = (props) => { + const {foo} = useContext(aContext); + return