From 3ce2078fe16b0a5b96ae6d46aa0b0b8a9e44dbc9 Mon Sep 17 00:00:00 2001 From: Joshua Stiefer Date: Sun, 16 Dec 2018 10:46:54 -0700 Subject: [PATCH] Use the pragma setting instead of `React` Also, the detection actually works now. --- lib/rules/void-dom-elements-no-children.js | 2 +- lib/util/Components.js | 46 +++--- lib/util/usedPropTypes.js | 2 +- tests/lib/rules/prop-types.js | 176 +++++++++++++++++++++ 4 files changed, 206 insertions(+), 20 deletions(-) diff --git a/lib/rules/void-dom-elements-no-children.js b/lib/rules/void-dom-elements-no-children.js index af3a7ca659..758ba4b04f 100644 --- a/lib/rules/void-dom-elements-no-children.js +++ b/lib/rules/void-dom-elements-no-children.js @@ -99,7 +99,7 @@ module.exports = { return; } - if (!utils.isReactCreateElement(node)) { + if (!utils.isCreateElement(node)) { return; } diff --git a/lib/util/Components.js b/lib/util/Components.js index 7ddf088a4b..4cbb2ea760 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -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 = ( + isCreateElement: 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' ); @@ -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) { @@ -357,12 +357,12 @@ function componentRule(rule, context) { node[property] && jsxUtil.isJSX(node[property]) ; - const returnsReactCreateElement = this.isReactCreateElement(node[property]); + const returnsPragmaCreateElement = this.isCreateElement(node[property]); return Boolean( returnsConditionalJSX || returnsJSX || - returnsReactCreateElement + returnsPragmaCreateElement ); }, @@ -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); }, /** @@ -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 () 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; @@ -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; diff --git a/lib/util/usedPropTypes.js b/lib/util/usedPropTypes.js index caec9ad450..ffd67c441a 100644 --- a/lib/util/usedPropTypes.js +++ b/lib/util/usedPropTypes.js @@ -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); } } diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index 8a41063f89..acc540d109 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -2066,6 +2066,110 @@ ruleTester.run('prop-types', rule, { }; `, settings: {react: {version: '16.3.0'}} + }, + { + code: ` + const HeaderBalance = React.memo(({ cryptoCurrency }) => ( +
+
+ BTC + {cryptoCurrency} +
+
+ )); + HeaderBalance.propTypes = { + cryptoCurrency: PropTypes.string + }; + ` + }, + { + code: ` + import React, { memo } from 'react'; + const HeaderBalance = memo(({ cryptoCurrency }) => ( +
+
+ BTC + {cryptoCurrency} +
+
+ )); + HeaderBalance.propTypes = { + cryptoCurrency: PropTypes.string + }; + ` + }, + { + code: ` + import Foo, { memo } from 'foo'; + const HeaderBalance = memo(({ cryptoCurrency }) => ( +
+
+ BTC + {cryptoCurrency} +
+
+ )); + HeaderBalance.propTypes = { + cryptoCurrency: PropTypes.string + }; + `, + settings: { + react: { + pragma: 'Foo' + } + } + }, + { + code: ` + const Label = React.forwardRef(({ text }, ref) => { + return
{text}
; + }); + Label.propTypes = { + text: PropTypes.string, + }; + ` + }, + { + code: ` + const Label = Foo.forwardRef(({ text }, ref) => { + return
{text}
; + }); + Label.propTypes = { + text: PropTypes.string, + }; + `, + settings: { + react: { + pragma: 'Foo' + } + } + }, + { + code: ` + import React, { forwardRef } from 'react'; + const Label = forwardRef(({ text }, ref) => { + return
{text}
; + }); + Label.propTypes = { + text: PropTypes.string, + }; + ` + }, + { + code: ` + import Foo, { forwardRef } from 'foo'; + const Label = forwardRef(({ text }, ref) => { + return
{text}
; + }); + Label.propTypes = { + text: PropTypes.string, + }; + `, + settings: { + react: { + pragma: 'Foo' + } + } } ], @@ -3979,6 +4083,47 @@ ruleTester.run('prop-types', rule, { message: '\'cryptoCurrency\' is missing in props validation' }] }, + { + code: ` + const HeaderBalance = Foo.memo(({ cryptoCurrency }) => ( +
+
+ BTC + {cryptoCurrency} +
+
+ )); + `, + settings: { + react: { + pragma: 'Foo' + } + }, + errors: [{ + message: '\'cryptoCurrency\' is missing in props validation' + }] + }, + { + code: ` + import Foo, { memo } from 'foo'; + const HeaderBalance = memo(({ cryptoCurrency }) => ( +
+
+ BTC + {cryptoCurrency} +
+
+ )); + `, + settings: { + react: { + pragma: 'Foo' + } + }, + errors: [{ + message: '\'cryptoCurrency\' is missing in props validation' + }] + }, { code: ` const Label = React.forwardRef(({ text }, ref) => { @@ -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
{text}
; + }); + `, + settings: { + react: { + pragma: 'Foo' + } + }, + errors: [{ + message: '\'text\' is missing in props validation' + }] + }, + { + code: ` + import Foo, { forwardRef } from 'foo'; + const Label = forwardRef(({ text }, ref) => { + return
{text}
; + }); + `, + settings: { + react: { + pragma: 'Foo' + } + }, + errors: [{ + message: '\'text\' is missing in props validation' + }] } ] });