Skip to content

Commit 734dc53

Browse files
SyMindljharb
authored andcommitted
[Fix] destructuring-assignment: fix a false positive for local prop named context in SFC
Fixes #2911.
1 parent 3885641 commit 734dc53

File tree

3 files changed

+77
-5
lines changed

3 files changed

+77
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
1616
* [`no-array-index-key`]: support optional chaining ([#2897][] @SyMind)
1717
* [`no-typos`]: avoid a crash on bindingless `prop-types` import; add warning ([#2899][] @ljharb)
1818
* [`jsx-curly-brace-presence`]: ignore containers with comments ([#2900][] @golopot)
19+
* [`destructuring-assignment`]: fix a false positive for local prop named `context` in SFC ([#2929][] @SyMind)
1920

2021
### Changed
2122
* [Docs] [`jsx-no-constructed-context-values`][]: fix invalid example syntax ([#2910][] @kud)
2223
* [readme] Replace lists of rules with tables in readme ([#2908][] @motato1)
2324
* [Docs] added missing curly braces ([#2923][] @Muditxofficial)
2425

26+
[#2929]: https://github.com/yannickcr/eslint-plugin-react/pull/2929
2527
[#2923]: https://github.com/yannickcr/eslint-plugin-react/pull/2923
2628
[#2910]: https://github.com/yannickcr/eslint-plugin-react/pull/2910
2729
[#2908]: https://github.com/yannickcr/eslint-plugin-react/pull/2908

lib/rules/destructuring-assignment.js

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,40 @@ const isAssignmentLHS = require('../util/ast').isAssignmentLHS;
1010

1111
const DEFAULT_OPTION = 'always';
1212

13+
function createSFCParams() {
14+
const queue = [];
15+
16+
return {
17+
push(params) {
18+
queue.unshift(params);
19+
},
20+
pop() {
21+
queue.shift();
22+
},
23+
propsName() {
24+
const found = queue.find((params) => {
25+
const props = params[0];
26+
return props && !props.destructuring && props.name;
27+
});
28+
return found && found[0] && found[0].name;
29+
},
30+
contextName() {
31+
const found = queue.find((params) => {
32+
const context = params[1];
33+
return context && !context.destructuring && context.name;
34+
});
35+
return found && found[1] && found.name;
36+
}
37+
};
38+
}
39+
40+
function evalParams(params) {
41+
return params.map((param) => ({
42+
destructuring: param.type === 'ObjectPattern',
43+
name: param.type === 'Identifier' && param.name
44+
}));
45+
}
46+
1347
module.exports = {
1448
meta: {
1549
docs: {
@@ -46,31 +80,50 @@ module.exports = {
4680
create: Components.detect((context, components, utils) => {
4781
const configuration = context.options[0] || DEFAULT_OPTION;
4882
const ignoreClassFields = (context.options[1] && (context.options[1].ignoreClassFields === true)) || false;
83+
const sfcParams = createSFCParams();
4984

5085
/**
5186
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
5287
* FunctionDeclaration, or FunctionExpression
5388
*/
5489
function handleStatelessComponent(node) {
55-
const destructuringProps = node.params && node.params[0] && node.params[0].type === 'ObjectPattern';
56-
const destructuringContext = node.params && node.params[1] && node.params[1].type === 'ObjectPattern';
90+
const params = evalParams(node.params);
91+
92+
const SFCComponent = components.get(context.getScope(node).block);
93+
if (!SFCComponent) {
94+
return;
95+
}
96+
sfcParams.push(params);
5797

58-
if (destructuringProps && components.get(node) && configuration === 'never') {
98+
if (params[0] && params[0].destructuring && components.get(node) && configuration === 'never') {
5999
context.report({
60100
node,
61101
messageId: 'noDestructPropsInSFCArg'
62102
});
63-
} else if (destructuringContext && components.get(node) && configuration === 'never') {
103+
} else if (params[1] && params[1].destructuring && components.get(node) && configuration === 'never') {
64104
context.report({
65105
node,
66106
messageId: 'noDestructContextInSFCArg'
67107
});
68108
}
69109
}
70110

111+
function handleStatelessComponentExit(node) {
112+
const SFCComponent = components.get(context.getScope(node).block);
113+
if (SFCComponent) {
114+
sfcParams.pop();
115+
}
116+
}
117+
71118
function handleSFCUsage(node) {
119+
const propsName = sfcParams.propsName();
120+
const contextName = sfcParams.contextName();
72121
// props.aProp || context.aProp
73-
const isPropUsed = (node.object.name === 'props' || node.object.name === 'context') && !isAssignmentLHS(node);
122+
const isPropUsed = (
123+
(propsName && node.object.name === propsName)
124+
|| (contextName && node.object.name === contextName)
125+
)
126+
&& !isAssignmentLHS(node);
74127
if (isPropUsed && configuration === 'always') {
75128
context.report({
76129
node,
@@ -123,6 +176,12 @@ module.exports = {
123176

124177
FunctionExpression: handleStatelessComponent,
125178

179+
'FunctionDeclaration:exit': handleStatelessComponentExit,
180+
181+
'ArrowFunctionExpression:exit': handleStatelessComponentExit,
182+
183+
'FunctionExpression:exit': handleStatelessComponentExit,
184+
126185
MemberExpression(node) {
127186
const SFCComponent = components.get(context.getScope(node).block);
128187
const classComponent = utils.getParentComponent(node);

tests/lib/rules/destructuring-assignment.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,17 @@ ruleTester.run('destructuring-assignment', rule, {
176176
].join('\n'),
177177
options: ['always', {ignoreClassFields: true}],
178178
parser: parsers.BABEL_ESLINT
179+
},
180+
// https://github.com/yannickcr/eslint-plugin-react/issues/2911
181+
{
182+
code: `
183+
function Foo({context}) {
184+
const d = context.describe()
185+
return <div>{d}</div>
186+
}
187+
`,
188+
options: ['always'],
189+
parser: parsers.BABEL_ESLINT
179190
}],
180191

181192
invalid: [{

0 commit comments

Comments
 (0)