Skip to content

Commit

Permalink
Fix component detection when memo and forwardRef are used together
Browse files Browse the repository at this point in the history
Fixes #2349
  • Loading branch information
yannickcr committed Jul 22, 2019
1 parent fc70077 commit 98d4bf3
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 3 deletions.
22 changes: 19 additions & 3 deletions lib/util/Components.js
Expand Up @@ -422,8 +422,23 @@ function componentRule(rule, context) {
return utils.isReturningJSX(ASTNode, strict) || utils.isReturningNull(ASTNode);
},

getPragmaComponentWrapper(node) {
let isPragmaComponentWrapper;
let currentNode = node;
let prevNode;
do {
currentNode = currentNode.parent;
isPragmaComponentWrapper = this.isPragmaComponentWrapper(currentNode);
if (isPragmaComponentWrapper) {
prevNode = currentNode;
}
} while (isPragmaComponentWrapper);

return prevNode;
},

isPragmaComponentWrapper(node) {
if (node.type !== 'CallExpression') {
if (!node || node.type !== 'CallExpression') {
return false;
}
const propertyNames = ['forwardRef', 'memo'];
Expand Down Expand Up @@ -506,8 +521,9 @@ function componentRule(rule, context) {
const isArgument = node.parent && node.parent.type === 'CallExpression'; // Arguments (callback, etc.)
// Attribute Expressions inside JSX Elements (<button onClick={() => props.handleClick()}></button>)
const isJSXExpressionContainer = node.parent && node.parent.type === 'JSXExpressionContainer';
if (isFunction && node.parent && this.isPragmaComponentWrapper(node.parent)) {
return node.parent;
const pragmaComponentWrapper = this.getPragmaComponentWrapper(node);
if (isFunction && pragmaComponentWrapper) {
return pragmaComponentWrapper;
}
// Stop moving up if we reach a class or an argument (like a callback)
if (isClass || isArgument) {
Expand Down
74 changes: 74 additions & 0 deletions tests/lib/rules/prop-types.js
Expand Up @@ -2375,6 +2375,43 @@ ruleTester.run('prop-types', rule, {
}
`,
parser: parsers.BABEL_ESLINT
},
{
code: `
const Label = React.memo(React.forwardRef(({ text }, ref) => {
return <div ref={ref}>{text}</div>;
}));
Label.propTypes = {
text: PropTypes.string
};
`
},
{
code: `
import React, { memo, forwardRef } from 'react';
const Label = memo(forwardRef(({ text }, ref) => {
return <div ref={ref}>{text}</div>;
}));
Label.propTypes = {
text: PropTypes.string
};
`
},
{
code: `
import Foo, { memo, forwardRef } from 'foo';
const Label = memo(forwardRef(({ text }, ref) => {
return <div ref={ref}>{text}</div>;
}));
Label.propTypes = {
text: PropTypes.string
};
`,
settings: {
react: {
pragma: 'Foo'
}
}
}
],

Expand Down Expand Up @@ -4723,6 +4760,43 @@ ruleTester.run('prop-types', rule, {
errors: [{
message: '\'user.age\' is missing in props validation'
}]
},
{
code: `
const Label = React.memo(React.forwardRef(({ text }, ref) => {
return <div ref={ref}>{text}</div>;
}));
`,
errors: [{
message: '\'text\' is missing in props validation'
}]
},
{
code: `
import React, { memo, forwardRef } from 'react';
const Label = memo(forwardRef(({ text }, ref) => {
return <div ref={ref}>{text}</div>;
}));
`,
errors: [{
message: '\'text\' is missing in props validation'
}]
},
{
code: `
import Foo, { memo, forwardRef } from 'foo';
const Label = memo(forwardRef(({ text }, ref) => {
return <div ref={ref}>{text}</div>;
}));
`,
settings: {
react: {
pragma: 'Foo'
}
},
errors: [{
message: '\'text\' is missing in props validation'
}]
}
]
});

0 comments on commit 98d4bf3

Please sign in to comment.