Skip to content

Commit 4704762

Browse files
committed
Support checking pragma for forwardRef/memo
Also, the detection actually works now.
1 parent 36d6089 commit 4704762

File tree

3 files changed

+195
-2
lines changed

3 files changed

+195
-2
lines changed

lib/util/Components.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,9 +476,12 @@ function componentRule(rule, context) {
476476
const enclosingScopeParent = enclosingScope && enclosingScope.block.parent;
477477
const isClass = enclosingScope && astUtil.isClass(enclosingScope.block);
478478
const isMethod = enclosingScopeParent && enclosingScopeParent.type === 'MethodDefinition'; // Classes methods
479-
const isArgument = node.parent && node.parent.type === 'CallExpression' && !this.isPragmaComponentWrapper(node.parent); // Arguments (callback, etc.)
479+
const isArgument = node.parent && node.parent.type === 'CallExpression'; // Arguments (callback, etc.)
480480
// Attribute Expressions inside JSX Elements (<button onClick={() => props.handleClick()}></button>)
481481
const isJSXExpressionContainer = node.parent && node.parent.type === 'JSXExpressionContainer';
482+
if (node.parent && this.isPragmaComponentWrapper(node.parent)) {
483+
return node.parent;
484+
}
482485
// Stop moving up if we reach a class or an argument (like a callback)
483486
if (isClass || isArgument) {
484487
return null;
@@ -613,6 +616,13 @@ function componentRule(rule, context) {
613616

614617
// Component detection instructions
615618
const detectionInstructions = {
619+
CallExpression: function(node) {
620+
if (!utils.isPragmaComponentWrapper(node)) {
621+
return;
622+
}
623+
components.add(node, 2);
624+
},
625+
616626
ClassExpression: function(node) {
617627
if (!utils.isES6Component(node)) {
618628
return;

lib/util/usedPropTypes.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
429429
*/
430430
function markDestructuredFunctionArgumentsAsUsed(node) {
431431
const destructuring = node.params && node.params[0] && node.params[0].type === 'ObjectPattern';
432-
if (destructuring && components.get(node)) {
432+
if (destructuring && (components.get(node) || components.get(node.parent))) {
433433
markPropTypesAsUsed(node);
434434
}
435435
}
@@ -483,6 +483,13 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
483483
markPropTypesAsUsed(node);
484484
},
485485

486+
CallExpression: function(node) {
487+
if (!utils.isPragmaComponentWrapper(node)) {
488+
return;
489+
}
490+
handleFunctionLikeExpressions(node.arguments[0]);
491+
},
492+
486493
FunctionDeclaration: handleFunctionLikeExpressions,
487494

488495
ArrowFunctionExpression: handleFunctionLikeExpressions,

tests/lib/rules/prop-types.js

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2066,6 +2066,110 @@ ruleTester.run('prop-types', rule, {
20662066
};
20672067
`,
20682068
settings: {react: {version: '16.3.0'}}
2069+
},
2070+
{
2071+
code: `
2072+
const HeaderBalance = React.memo(({ cryptoCurrency }) => (
2073+
<div className="header-balance">
2074+
<div className="header-balance__balance">
2075+
BTC
2076+
{cryptoCurrency}
2077+
</div>
2078+
</div>
2079+
));
2080+
HeaderBalance.propTypes = {
2081+
cryptoCurrency: PropTypes.string
2082+
};
2083+
`
2084+
},
2085+
{
2086+
code: `
2087+
import React, { memo } from 'react';
2088+
const HeaderBalance = memo(({ cryptoCurrency }) => (
2089+
<div className="header-balance">
2090+
<div className="header-balance__balance">
2091+
BTC
2092+
{cryptoCurrency}
2093+
</div>
2094+
</div>
2095+
));
2096+
HeaderBalance.propTypes = {
2097+
cryptoCurrency: PropTypes.string
2098+
};
2099+
`
2100+
},
2101+
{
2102+
code: `
2103+
import Foo, { memo } from 'foo';
2104+
const HeaderBalance = memo(({ cryptoCurrency }) => (
2105+
<div className="header-balance">
2106+
<div className="header-balance__balance">
2107+
BTC
2108+
{cryptoCurrency}
2109+
</div>
2110+
</div>
2111+
));
2112+
HeaderBalance.propTypes = {
2113+
cryptoCurrency: PropTypes.string
2114+
};
2115+
`,
2116+
settings: {
2117+
react: {
2118+
pragma: 'Foo'
2119+
}
2120+
}
2121+
},
2122+
{
2123+
code: `
2124+
const Label = React.forwardRef(({ text }, ref) => {
2125+
return <div ref={ref}>{text}</div>;
2126+
});
2127+
Label.propTypes = {
2128+
text: PropTypes.string,
2129+
};
2130+
`
2131+
},
2132+
{
2133+
code: `
2134+
const Label = Foo.forwardRef(({ text }, ref) => {
2135+
return <div ref={ref}>{text}</div>;
2136+
});
2137+
Label.propTypes = {
2138+
text: PropTypes.string,
2139+
};
2140+
`,
2141+
settings: {
2142+
react: {
2143+
pragma: 'Foo'
2144+
}
2145+
}
2146+
},
2147+
{
2148+
code: `
2149+
import React, { forwardRef } from 'react';
2150+
const Label = forwardRef(({ text }, ref) => {
2151+
return <div ref={ref}>{text}</div>;
2152+
});
2153+
Label.propTypes = {
2154+
text: PropTypes.string,
2155+
};
2156+
`
2157+
},
2158+
{
2159+
code: `
2160+
import Foo, { forwardRef } from 'foo';
2161+
const Label = forwardRef(({ text }, ref) => {
2162+
return <div ref={ref}>{text}</div>;
2163+
});
2164+
Label.propTypes = {
2165+
text: PropTypes.string,
2166+
};
2167+
`,
2168+
settings: {
2169+
react: {
2170+
pragma: 'Foo'
2171+
}
2172+
}
20692173
}
20702174
],
20712175

@@ -3979,6 +4083,47 @@ ruleTester.run('prop-types', rule, {
39794083
message: '\'cryptoCurrency\' is missing in props validation'
39804084
}]
39814085
},
4086+
{
4087+
code: `
4088+
const HeaderBalance = Foo.memo(({ cryptoCurrency }) => (
4089+
<div className="header-balance">
4090+
<div className="header-balance__balance">
4091+
BTC
4092+
{cryptoCurrency}
4093+
</div>
4094+
</div>
4095+
));
4096+
`,
4097+
settings: {
4098+
react: {
4099+
pragma: 'Foo'
4100+
}
4101+
},
4102+
errors: [{
4103+
message: '\'cryptoCurrency\' is missing in props validation'
4104+
}]
4105+
},
4106+
{
4107+
code: `
4108+
import Foo, { memo } from 'foo';
4109+
const HeaderBalance = memo(({ cryptoCurrency }) => (
4110+
<div className="header-balance">
4111+
<div className="header-balance__balance">
4112+
BTC
4113+
{cryptoCurrency}
4114+
</div>
4115+
</div>
4116+
));
4117+
`,
4118+
settings: {
4119+
react: {
4120+
pragma: 'Foo'
4121+
}
4122+
},
4123+
errors: [{
4124+
message: '\'cryptoCurrency\' is missing in props validation'
4125+
}]
4126+
},
39824127
{
39834128
code: `
39844129
const Label = React.forwardRef(({ text }, ref) => {
@@ -3999,6 +4144,37 @@ ruleTester.run('prop-types', rule, {
39994144
errors: [{
40004145
message: '\'text\' is missing in props validation'
40014146
}]
4147+
},
4148+
{
4149+
code: `
4150+
const Label = Foo.forwardRef(({ text }, ref) => {
4151+
return <div ref={ref}>{text}</div>;
4152+
});
4153+
`,
4154+
settings: {
4155+
react: {
4156+
pragma: 'Foo'
4157+
}
4158+
},
4159+
errors: [{
4160+
message: '\'text\' is missing in props validation'
4161+
}]
4162+
},
4163+
{
4164+
code: `
4165+
import Foo, { forwardRef } from 'foo';
4166+
const Label = forwardRef(({ text }, ref) => {
4167+
return <div ref={ref}>{text}</div>;
4168+
});
4169+
`,
4170+
settings: {
4171+
react: {
4172+
pragma: 'Foo'
4173+
}
4174+
},
4175+
errors: [{
4176+
message: '\'text\' is missing in props validation'
4177+
}]
40024178
}
40034179
]
40044180
});

0 commit comments

Comments
 (0)