Skip to content

Commit 523db20

Browse files
[Fix] destructuring-assignment: Handle destructuring of useContext in SFC
Co-authored-by: Jin Young Park <[email protected]> Co-authored-by: Jordan Harband <[email protected]>
1 parent a60f020 commit 523db20

File tree

2 files changed

+128
-4
lines changed

2 files changed

+128
-4
lines changed

lib/rules/destructuring-assignment.js

+33-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const Components = require('../util/Components');
88
const docsUrl = require('../util/docsUrl');
99
const isAssignmentLHS = require('../util/ast').isAssignmentLHS;
1010
const report = require('../util/report');
11+
const testReactVersion = require('../util/version').testReactVersion;
1112

1213
const DEFAULT_OPTION = 'always';
1314

@@ -94,6 +95,8 @@ module.exports = {
9495
const destructureInSignature = (context.options[1] && context.options[1].destructureInSignature) || 'ignore';
9596
const sfcParams = createSFCParams();
9697

98+
// set to save renamed var of useContext
99+
const contextSet = new Set();
97100
/**
98101
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
99102
* FunctionDeclaration, or FunctionExpression
@@ -128,7 +131,7 @@ module.exports = {
128131
function handleSFCUsage(node) {
129132
const propsName = sfcParams.propsName();
130133
const contextName = sfcParams.contextName();
131-
// props.aProp || context.aProp
134+
// props.aProp
132135
const isPropUsed = (
133136
(propsName && node.object.name === propsName)
134137
|| (contextName && node.object.name === contextName)
@@ -142,6 +145,16 @@ module.exports = {
142145
},
143146
});
144147
}
148+
149+
// const foo = useContext(aContext);
150+
// foo.aProp
151+
const isContextUsed = contextSet.has(node.object.name) && !isAssignmentLHS(node);
152+
if (isContextUsed && configuration === 'always') {
153+
context.report({
154+
node,
155+
message: `Must use destructuring ${node.object.name} assignment`,
156+
});
157+
}
145158
}
146159

147160
function isInClassProperty(node) {
@@ -176,8 +189,9 @@ module.exports = {
176189
}
177190
}
178191

179-
return {
192+
const hasHooks = testReactVersion(context, '>= 16.9');
180193

194+
return {
181195
FunctionDeclaration: handleStatelessComponent,
182196

183197
ArrowFunctionExpression: handleStatelessComponent,
@@ -212,13 +226,29 @@ module.exports = {
212226
const SFCComponent = components.get(context.getScope(node).block);
213227

214228
const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern');
229+
const identifier = (node.init && node.id && node.id.type === 'Identifier');
215230
// let {foo} = props;
216-
const destructuringSFC = destructuring && (node.init.name === 'props' || node.init.name === 'context');
231+
const destructuringSFC = destructuring && node.init.name === 'props';
232+
// let {foo} = useContext(aContext);
233+
const destructuringUseContext = hasHooks && destructuring && node.init.callee && node.init.callee.name === 'useContext';
234+
// let foo = useContext(aContext);
235+
const assignUseContext = hasHooks && identifier && node.init.callee && node.init.callee.name === 'useContext';
217236
// let {foo} = this.props;
218237
const destructuringClass = destructuring && node.init.object && node.init.object.type === 'ThisExpression' && (
219238
node.init.property.name === 'props' || node.init.property.name === 'context' || node.init.property.name === 'state'
220239
);
221240

241+
if (SFCComponent && assignUseContext) {
242+
contextSet.add(node.id.name);
243+
}
244+
245+
if (SFCComponent && destructuringUseContext && configuration === 'never') {
246+
context.report({
247+
node,
248+
message: `Must never use destructuring ${node.init.callee.name} assignment`,
249+
});
250+
}
251+
222252
if (SFCComponent && destructuringSFC && configuration === 'never') {
223253
report(context, messages.noDestructAssignment, 'noDestructAssignment', {
224254
node,

tests/lib/rules/destructuring-assignment.js

+95-1
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,70 @@ ruleTester.run('destructuring-assignment', rule, {
358358
`,
359359
options: ['always', { destructureInSignature: 'always' }],
360360
},
361+
{
362+
code: `
363+
import { useContext } from 'react';
364+
365+
const MyComponent = (props) => {
366+
const {foo} = useContext(aContext);
367+
return <div>{foo}</div>
368+
};
369+
`,
370+
options: ['always'],
371+
settings: { react: { version: '16.9.0' } },
372+
},
373+
{
374+
code: `
375+
import { useContext } from 'react';
376+
377+
const MyComponent = (props) => {
378+
const foo = useContext(aContext);
379+
return <div>{foo.test}</div>
380+
};
381+
`,
382+
options: ['never'],
383+
settings: { react: { version: '16.9.0' } },
384+
},
385+
{
386+
code: `
387+
const MyComponent = (props) => {
388+
const foo = useContext(aContext);
389+
return <div>{foo.test}</div>
390+
};
391+
`,
392+
options: ['always'],
393+
settings: { react: { version: '16.8.999' } },
394+
},
395+
{
396+
code: `
397+
const MyComponent = (props) => {
398+
const {foo} = useContext(aContext);
399+
return <div>{foo}</div>
400+
};
401+
`,
402+
options: ['never'],
403+
settings: { react: { version: '16.8.999' } },
404+
},
405+
{
406+
code: `
407+
const MyComponent = (props) => {
408+
const {foo} = useContext(aContext);
409+
return <div>{foo}</div>
410+
};
411+
`,
412+
options: ['always'],
413+
settings: { react: { version: '16.8.999' } },
414+
},
415+
{
416+
code: `
417+
const MyComponent = (props) => {
418+
const foo = useContext(aContext);
419+
return <div>{foo.test}</div>
420+
};
421+
`,
422+
options: ['never'],
423+
settings: { react: { version: '16.8.999' } },
424+
},
361425
]),
362426

363427
invalid: parsers.all([].concat(
@@ -785,6 +849,36 @@ ruleTester.run('destructuring-assignment', rule, {
785849
`,
786850
features: ['ts', 'no-babel'],
787851
},
788-
] : []
852+
] : [],
853+
{
854+
code: `
855+
import { useContext } from 'react';
856+
857+
const MyComponent = (props) => {
858+
const foo = useContext(aContext);
859+
return <div>{foo.test}</div>
860+
};
861+
`,
862+
options: ['always'],
863+
settings: { react: { version: '16.9.0' } },
864+
errors: [
865+
{ message: 'Must use destructuring foo assignment' },
866+
],
867+
},
868+
{
869+
code: `
870+
import { useContext } from 'react';
871+
872+
const MyComponent = (props) => {
873+
const {foo} = useContext(aContext);
874+
return <div>{foo}</div>
875+
};
876+
`,
877+
options: ['never'],
878+
settings: { react: { version: '16.9.0' } },
879+
errors: [
880+
{ message: 'Must never use destructuring useContext assignment' },
881+
],
882+
}
789883
)),
790884
});

0 commit comments

Comments
 (0)