Skip to content

Commit

Permalink
Support checking pragma for forwardRef/memo
Browse files Browse the repository at this point in the history
Also, the detection actually works now.
  • Loading branch information
jomasti committed Dec 16, 2018
1 parent 36d6089 commit 4704762
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 2 deletions.
12 changes: 11 additions & 1 deletion lib/util/Components.js
Expand Up @@ -476,9 +476,12 @@ function componentRule(rule, context) {
const enclosingScopeParent = enclosingScope && enclosingScope.block.parent;
const isClass = enclosingScope && astUtil.isClass(enclosingScope.block);
const isMethod = enclosingScopeParent && enclosingScopeParent.type === 'MethodDefinition'; // Classes methods
const isArgument = node.parent && node.parent.type === 'CallExpression' && !this.isPragmaComponentWrapper(node.parent); // Arguments (callback, etc.)
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 (node.parent && this.isPragmaComponentWrapper(node.parent)) {
return node.parent;
}
// Stop moving up if we reach a class or an argument (like a callback)
if (isClass || isArgument) {
return null;
Expand Down Expand Up @@ -613,6 +616,13 @@ function componentRule(rule, context) {

// Component detection instructions
const detectionInstructions = {
CallExpression: function(node) {
if (!utils.isPragmaComponentWrapper(node)) {
return;
}
components.add(node, 2);
},

ClassExpression: function(node) {
if (!utils.isES6Component(node)) {
return;
Expand Down
9 changes: 8 additions & 1 deletion lib/util/usedPropTypes.js
Expand Up @@ -429,7 +429,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
*/
function markDestructuredFunctionArgumentsAsUsed(node) {
const destructuring = node.params && node.params[0] && node.params[0].type === 'ObjectPattern';
if (destructuring && components.get(node)) {
if (destructuring && (components.get(node) || components.get(node.parent))) {
markPropTypesAsUsed(node);
}
}
Expand Down Expand Up @@ -483,6 +483,13 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
markPropTypesAsUsed(node);
},

CallExpression: function(node) {
if (!utils.isPragmaComponentWrapper(node)) {
return;
}
handleFunctionLikeExpressions(node.arguments[0]);
},

FunctionDeclaration: handleFunctionLikeExpressions,

ArrowFunctionExpression: handleFunctionLikeExpressions,
Expand Down
176 changes: 176 additions & 0 deletions tests/lib/rules/prop-types.js
Expand Up @@ -2066,6 +2066,110 @@ ruleTester.run('prop-types', rule, {
};
`,
settings: {react: {version: '16.3.0'}}
},
{
code: `
const HeaderBalance = React.memo(({ cryptoCurrency }) => (
<div className="header-balance">
<div className="header-balance__balance">
BTC
{cryptoCurrency}
</div>
</div>
));
HeaderBalance.propTypes = {
cryptoCurrency: PropTypes.string
};
`
},
{
code: `
import React, { memo } from 'react';
const HeaderBalance = memo(({ cryptoCurrency }) => (
<div className="header-balance">
<div className="header-balance__balance">
BTC
{cryptoCurrency}
</div>
</div>
));
HeaderBalance.propTypes = {
cryptoCurrency: PropTypes.string
};
`
},
{
code: `
import Foo, { memo } from 'foo';
const HeaderBalance = memo(({ cryptoCurrency }) => (
<div className="header-balance">
<div className="header-balance__balance">
BTC
{cryptoCurrency}
</div>
</div>
));
HeaderBalance.propTypes = {
cryptoCurrency: PropTypes.string
};
`,
settings: {
react: {
pragma: 'Foo'
}
}
},
{
code: `
const Label = React.forwardRef(({ text }, ref) => {
return <div ref={ref}>{text}</div>;
});
Label.propTypes = {
text: PropTypes.string,
};
`
},
{
code: `
const Label = Foo.forwardRef(({ text }, ref) => {
return <div ref={ref}>{text}</div>;
});
Label.propTypes = {
text: PropTypes.string,
};
`,
settings: {
react: {
pragma: 'Foo'
}
}
},
{
code: `
import React, { forwardRef } from 'react';
const Label = forwardRef(({ text }, ref) => {
return <div ref={ref}>{text}</div>;
});
Label.propTypes = {
text: PropTypes.string,
};
`
},
{
code: `
import Foo, { forwardRef } from 'foo';
const Label = forwardRef(({ text }, ref) => {
return <div ref={ref}>{text}</div>;
});
Label.propTypes = {
text: PropTypes.string,
};
`,
settings: {
react: {
pragma: 'Foo'
}
}
}
],

Expand Down Expand Up @@ -3979,6 +4083,47 @@ ruleTester.run('prop-types', rule, {
message: '\'cryptoCurrency\' is missing in props validation'
}]
},
{
code: `
const HeaderBalance = Foo.memo(({ cryptoCurrency }) => (
<div className="header-balance">
<div className="header-balance__balance">
BTC
{cryptoCurrency}
</div>
</div>
));
`,
settings: {
react: {
pragma: 'Foo'
}
},
errors: [{
message: '\'cryptoCurrency\' is missing in props validation'
}]
},
{
code: `
import Foo, { memo } from 'foo';
const HeaderBalance = memo(({ cryptoCurrency }) => (
<div className="header-balance">
<div className="header-balance__balance">
BTC
{cryptoCurrency}
</div>
</div>
));
`,
settings: {
react: {
pragma: 'Foo'
}
},
errors: [{
message: '\'cryptoCurrency\' is missing in props validation'
}]
},
{
code: `
const Label = React.forwardRef(({ text }, ref) => {
Expand All @@ -3999,6 +4144,37 @@ ruleTester.run('prop-types', rule, {
errors: [{
message: '\'text\' is missing in props validation'
}]
},
{
code: `
const Label = Foo.forwardRef(({ text }, ref) => {
return <div ref={ref}>{text}</div>;
});
`,
settings: {
react: {
pragma: 'Foo'
}
},
errors: [{
message: '\'text\' is missing in props validation'
}]
},
{
code: `
import Foo, { forwardRef } from 'foo';
const Label = 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 4704762

Please sign in to comment.