Skip to content

Commit 3ce2078

Browse files
committed
Use the pragma setting instead of React
Also, the detection actually works now.
1 parent 341bb4f commit 3ce2078

File tree

4 files changed

+206
-20
lines changed

4 files changed

+206
-20
lines changed

lib/rules/void-dom-elements-no-children.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ module.exports = {
9999
return;
100100
}
101101

102-
if (!utils.isReactCreateElement(node)) {
102+
if (!utils.isCreateElement(node)) {
103103
return;
104104
}
105105

lib/util/Components.js

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -255,33 +255,33 @@ function componentRule(rule, context) {
255255
},
256256

257257
/**
258-
* Check if variable is destructured from React import
258+
* Check if variable is destructured from pragma import
259259
*
260260
* @param {variable} String The variable name to check
261-
* @returns {Boolean} True if createElement is destructured from React
261+
* @returns {Boolean} True if createElement is destructured from the pragma
262262
*/
263-
isDestructuredFromReactImport: function(variable) {
263+
isDestructuredFromPragmaImport: function(variable) {
264264
const variables = variableUtil.variablesInScope(context);
265265
const variableInScope = variableUtil.getVariable(variables, variable);
266266
if (variableInScope) {
267267
const map = variableInScope.scope.set;
268-
return map.has('React');
268+
return map.has(pragma);
269269
}
270270
return false;
271271
},
272272

273273
/**
274-
* Checks to see if node is called within React.createElement
274+
* Checks to see if node is called within createElement from pragma
275275
*
276276
* @param {ASTNode} node The AST node being checked.
277-
* @returns {Boolean} True if React.createElement called
277+
* @returns {Boolean} True if createElement called from pragma
278278
*/
279-
isReactCreateElement: function(node) {
280-
const calledOnReact = (
279+
isCreateElement: function(node) {
280+
const calledOnPragma = (
281281
node &&
282282
node.callee &&
283283
node.callee.object &&
284-
node.callee.object.name === 'React' &&
284+
node.callee.object.name === pragma &&
285285
node.callee.property &&
286286
node.callee.property.name === 'createElement'
287287
);
@@ -292,10 +292,10 @@ function componentRule(rule, context) {
292292
node.callee.name === 'createElement'
293293
);
294294

295-
if (this.isDestructuredFromReactImport('createElement')) {
296-
return calledDirectly || calledOnReact;
295+
if (this.isDestructuredFromPragmaImport('createElement')) {
296+
return calledDirectly || calledOnPragma;
297297
}
298-
return calledOnReact;
298+
return calledOnPragma;
299299
},
300300

301301
getReturnPropertyAndNode(ASTnode) {
@@ -357,12 +357,12 @@ function componentRule(rule, context) {
357357
node[property] &&
358358
jsxUtil.isJSX(node[property])
359359
;
360-
const returnsReactCreateElement = this.isReactCreateElement(node[property]);
360+
const returnsPragmaCreateElement = this.isCreateElement(node[property]);
361361

362362
return Boolean(
363363
returnsConditionalJSX ||
364364
returnsJSX ||
365-
returnsReactCreateElement
365+
returnsPragmaCreateElement
366366
);
367367
},
368368

@@ -395,16 +395,16 @@ function componentRule(rule, context) {
395395
return utils.isReturningJSX(ASTNode, strict) || utils.isReturningNull(ASTNode);
396396
},
397397

398-
isReactComponentWrapper(node) {
398+
isPragmaComponentWrapper(node) {
399399
if (node.type !== 'CallExpression') {
400400
return false;
401401
}
402402
const propertyNames = ['forwardRef', 'memo'];
403403
const calleeObject = node.callee.object;
404404
if (calleeObject) {
405-
return arrayIncludes(propertyNames, node.callee.property.name) && node.callee.object.name === 'React';
405+
return arrayIncludes(propertyNames, node.callee.property.name) && node.callee.object.name === pragma;
406406
}
407-
return arrayIncludes(propertyNames, node.callee.name) && this.isDestructuredFromReactImport(node.callee.name);
407+
return arrayIncludes(propertyNames, node.callee.name) && this.isDestructuredFromPragmaImport(node.callee.name);
408408
},
409409

410410
/**
@@ -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.isReactComponentWrapper(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: 1 addition & 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
}

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)