Skip to content

Commit

Permalink
Use the pragma setting instead of React
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 341bb4f commit 2010245
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 20 deletions.
2 changes: 1 addition & 1 deletion lib/rules/void-dom-elements-no-children.js
Expand Up @@ -99,7 +99,7 @@ module.exports = {
return;
}

if (!utils.isReactCreateElement(node)) {
if (!utils.isPragmaCreateElement(node)) {
return;
}

Expand Down
46 changes: 28 additions & 18 deletions lib/util/Components.js
Expand Up @@ -255,33 +255,33 @@ function componentRule(rule, context) {
},

/**
* Check if variable is destructured from React import
* Check if variable is destructured from pragma import
*
* @param {variable} String The variable name to check
* @returns {Boolean} True if createElement is destructured from React
* @returns {Boolean} True if createElement is destructured from the pragma
*/
isDestructuredFromReactImport: function(variable) {
isDestructuredFromPragmaImport: function(variable) {
const variables = variableUtil.variablesInScope(context);
const variableInScope = variableUtil.getVariable(variables, variable);
if (variableInScope) {
const map = variableInScope.scope.set;
return map.has('React');
return map.has(pragma);
}
return false;
},

/**
* Checks to see if node is called within React.createElement
* Checks to see if node is called within createElement from pragma
*
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if React.createElement called
* @returns {Boolean} True if createElement called from pragma
*/
isReactCreateElement: function(node) {
const calledOnReact = (
isPragmaCreateElement: function(node) {
const calledOnPragma = (
node &&
node.callee &&
node.callee.object &&
node.callee.object.name === 'React' &&
node.callee.object.name === pragma &&
node.callee.property &&
node.callee.property.name === 'createElement'
);
Expand All @@ -292,10 +292,10 @@ function componentRule(rule, context) {
node.callee.name === 'createElement'
);

if (this.isDestructuredFromReactImport('createElement')) {
return calledDirectly || calledOnReact;
if (this.isDestructuredFromPragmaImport('createElement')) {
return calledDirectly || calledOnPragma;
}
return calledOnReact;
return calledOnPragma;
},

getReturnPropertyAndNode(ASTnode) {
Expand Down Expand Up @@ -357,12 +357,12 @@ function componentRule(rule, context) {
node[property] &&
jsxUtil.isJSX(node[property])
;
const returnsReactCreateElement = this.isReactCreateElement(node[property]);
const returnsPragmaCreateElement = this.isPragmaCreateElement(node[property]);

return Boolean(
returnsConditionalJSX ||
returnsJSX ||
returnsReactCreateElement
returnsPragmaCreateElement
);
},

Expand Down Expand Up @@ -395,16 +395,16 @@ function componentRule(rule, context) {
return utils.isReturningJSX(ASTNode, strict) || utils.isReturningNull(ASTNode);
},

isReactComponentWrapper(node) {
isPragmaComponentWrapper(node) {
if (node.type !== 'CallExpression') {
return false;
}
const propertyNames = ['forwardRef', 'memo'];
const calleeObject = node.callee.object;
if (calleeObject) {
return arrayIncludes(propertyNames, node.callee.property.name) && node.callee.object.name === 'React';
return arrayIncludes(propertyNames, node.callee.property.name) && node.callee.object.name === pragma;
}
return arrayIncludes(propertyNames, node.callee.name) && this.isDestructuredFromReactImport(node.callee.name);
return arrayIncludes(propertyNames, node.callee.name) && this.isDestructuredFromPragmaImport(node.callee.name);
},

/**
Expand Down 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.isReactComponentWrapper(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
2 changes: 1 addition & 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
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 2010245

Please sign in to comment.