Skip to content

Commit

Permalink
[Fix] destructuring-assignment: fix a false positive for local prop…
Browse files Browse the repository at this point in the history
… named `context` in SFC

Fixes #2911.
  • Loading branch information
SyMind authored and ljharb committed Feb 19, 2021
1 parent 3885641 commit 734dc53
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -16,12 +16,14 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
* [`no-array-index-key`]: support optional chaining ([#2897][] @SyMind)
* [`no-typos`]: avoid a crash on bindingless `prop-types` import; add warning ([#2899][] @ljharb)
* [`jsx-curly-brace-presence`]: ignore containers with comments ([#2900][] @golopot)
* [`destructuring-assignment`]: fix a false positive for local prop named `context` in SFC ([#2929][] @SyMind)

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

[#2929]: https://github.com/yannickcr/eslint-plugin-react/pull/2929
[#2923]: https://github.com/yannickcr/eslint-plugin-react/pull/2923
[#2910]: https://github.com/yannickcr/eslint-plugin-react/pull/2910
[#2908]: https://github.com/yannickcr/eslint-plugin-react/pull/2908
Expand Down
69 changes: 64 additions & 5 deletions lib/rules/destructuring-assignment.js
Expand Up @@ -10,6 +10,40 @@ const isAssignmentLHS = require('../util/ast').isAssignmentLHS;

const DEFAULT_OPTION = 'always';

function createSFCParams() {
const queue = [];

return {
push(params) {
queue.unshift(params);
},
pop() {
queue.shift();
},
propsName() {
const found = queue.find((params) => {
const props = params[0];
return props && !props.destructuring && props.name;
});
return found && found[0] && found[0].name;
},
contextName() {
const found = queue.find((params) => {
const context = params[1];
return context && !context.destructuring && context.name;
});
return found && found[1] && found.name;
}
};
}

function evalParams(params) {
return params.map((param) => ({
destructuring: param.type === 'ObjectPattern',
name: param.type === 'Identifier' && param.name
}));
}

module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -46,31 +80,50 @@ module.exports = {
create: Components.detect((context, components, utils) => {
const configuration = context.options[0] || DEFAULT_OPTION;
const ignoreClassFields = (context.options[1] && (context.options[1].ignoreClassFields === true)) || false;
const sfcParams = createSFCParams();

/**
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
* FunctionDeclaration, or FunctionExpression
*/
function handleStatelessComponent(node) {
const destructuringProps = node.params && node.params[0] && node.params[0].type === 'ObjectPattern';
const destructuringContext = node.params && node.params[1] && node.params[1].type === 'ObjectPattern';
const params = evalParams(node.params);

const SFCComponent = components.get(context.getScope(node).block);
if (!SFCComponent) {
return;
}
sfcParams.push(params);

if (destructuringProps && components.get(node) && configuration === 'never') {
if (params[0] && params[0].destructuring && components.get(node) && configuration === 'never') {
context.report({
node,
messageId: 'noDestructPropsInSFCArg'
});
} else if (destructuringContext && components.get(node) && configuration === 'never') {
} else if (params[1] && params[1].destructuring && components.get(node) && configuration === 'never') {
context.report({
node,
messageId: 'noDestructContextInSFCArg'
});
}
}

function handleStatelessComponentExit(node) {
const SFCComponent = components.get(context.getScope(node).block);
if (SFCComponent) {
sfcParams.pop();
}
}

function handleSFCUsage(node) {
const propsName = sfcParams.propsName();
const contextName = sfcParams.contextName();
// props.aProp || context.aProp
const isPropUsed = (node.object.name === 'props' || node.object.name === 'context') && !isAssignmentLHS(node);
const isPropUsed = (
(propsName && node.object.name === propsName)
|| (contextName && node.object.name === contextName)
)
&& !isAssignmentLHS(node);
if (isPropUsed && configuration === 'always') {
context.report({
node,
Expand Down Expand Up @@ -123,6 +176,12 @@ module.exports = {

FunctionExpression: handleStatelessComponent,

'FunctionDeclaration:exit': handleStatelessComponentExit,

'ArrowFunctionExpression:exit': handleStatelessComponentExit,

'FunctionExpression:exit': handleStatelessComponentExit,

MemberExpression(node) {
const SFCComponent = components.get(context.getScope(node).block);
const classComponent = utils.getParentComponent(node);
Expand Down
11 changes: 11 additions & 0 deletions tests/lib/rules/destructuring-assignment.js
Expand Up @@ -176,6 +176,17 @@ ruleTester.run('destructuring-assignment', rule, {
].join('\n'),
options: ['always', {ignoreClassFields: true}],
parser: parsers.BABEL_ESLINT
},
// https://github.com/yannickcr/eslint-plugin-react/issues/2911
{
code: `
function Foo({context}) {
const d = context.describe()
return <div>{d}</div>
}
`,
options: ['always'],
parser: parsers.BABEL_ESLINT
}],

invalid: [{
Expand Down

0 comments on commit 734dc53

Please sign in to comment.