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
{foo}
+ }; + `, + options: ['always'], + settings: { react: { version: '16.9.0' } }, + }, + { + code: ` + import { useContext } from 'react'; + + const MyComponent = (props) => { + const foo = useContext(aContext); + return
{foo.test}
+ }; + `, + options: ['never'], + settings: { react: { version: '16.9.0' } }, + }, + { + code: ` + const MyComponent = (props) => { + const foo = useContext(aContext); + return
{foo.test}
+ }; + `, + options: ['always'], + settings: { react: { version: '16.8.999' } }, + }, + { + code: ` + const MyComponent = (props) => { + const {foo} = useContext(aContext); + return
{foo}
+ }; + `, + options: ['never'], + settings: { react: { version: '16.8.999' } }, + }, + { + code: ` + const MyComponent = (props) => { + const {foo} = useContext(aContext); + return
{foo}
+ }; + `, + options: ['always'], + settings: { react: { version: '16.8.999' } }, + }, + { + code: ` + const MyComponent = (props) => { + const foo = useContext(aContext); + return
{foo.test}
+ }; + `, + options: ['never'], + settings: { react: { version: '16.8.999' } }, + }, ]), invalid: parsers.all([].concat( @@ -785,6 +849,36 @@ ruleTester.run('destructuring-assignment', rule, { `, features: ['ts', 'no-babel'], }, - ] : [] + ] : [], + { + code: ` + import { useContext } from 'react'; + + const MyComponent = (props) => { + const foo = useContext(aContext); + return
{foo.test}
+ }; + `, + options: ['always'], + settings: { react: { version: '16.9.0' } }, + errors: [ + { message: 'Must use destructuring foo assignment' }, + ], + }, + { + code: ` + import { useContext } from 'react'; + + const MyComponent = (props) => { + const {foo} = useContext(aContext); + return
{foo}
+ }; + `, + options: ['never'], + settings: { react: { version: '16.9.0' } }, + errors: [ + { message: 'Must never use destructuring useContext assignment' }, + ], + } )), });