Skip to content

Commit

Permalink
[Fix] destructuring-assignment: Handle destructuring of useContext …
Browse files Browse the repository at this point in the history
…in SFC

Co-authored-by: Jin Young Park <jy2045@sogang.ac.kr>
Co-authored-by: Jordan Harband <ljharb@gmail.com>
  • Loading branch information
ed-jinyoung-park and ljharb committed Sep 16, 2020
1 parent a60f020 commit 523db20
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 4 deletions.
36 changes: 33 additions & 3 deletions lib/rules/destructuring-assignment.js
Expand Up @@ -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';

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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) {
Expand Down Expand Up @@ -176,8 +189,9 @@ module.exports = {
}
}

return {
const hasHooks = testReactVersion(context, '>= 16.9');

return {
FunctionDeclaration: handleStatelessComponent,

ArrowFunctionExpression: handleStatelessComponent,
Expand Down Expand Up @@ -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,
Expand Down
96 changes: 95 additions & 1 deletion tests/lib/rules/destructuring-assignment.js
Expand Up @@ -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 <div>{foo}</div>
};
`,
options: ['always'],
settings: { react: { version: '16.9.0' } },
},
{
code: `
import { useContext } from 'react';
const MyComponent = (props) => {
const foo = useContext(aContext);
return <div>{foo.test}</div>
};
`,
options: ['never'],
settings: { react: { version: '16.9.0' } },
},
{
code: `
const MyComponent = (props) => {
const foo = useContext(aContext);
return <div>{foo.test}</div>
};
`,
options: ['always'],
settings: { react: { version: '16.8.999' } },
},
{
code: `
const MyComponent = (props) => {
const {foo} = useContext(aContext);
return <div>{foo}</div>
};
`,
options: ['never'],
settings: { react: { version: '16.8.999' } },
},
{
code: `
const MyComponent = (props) => {
const {foo} = useContext(aContext);
return <div>{foo}</div>
};
`,
options: ['always'],
settings: { react: { version: '16.8.999' } },
},
{
code: `
const MyComponent = (props) => {
const foo = useContext(aContext);
return <div>{foo.test}</div>
};
`,
options: ['never'],
settings: { react: { version: '16.8.999' } },
},
]),

invalid: parsers.all([].concat(
Expand Down Expand Up @@ -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 <div>{foo.test}</div>
};
`,
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 <div>{foo}</div>
};
`,
options: ['never'],
settings: { react: { version: '16.9.0' } },
errors: [
{ message: 'Must never use destructuring useContext assignment' },
],
}
)),
});

0 comments on commit 523db20

Please sign in to comment.