From ee4bad351e620ad0b86321a1437fbf9055bc0aeb Mon Sep 17 00:00:00 2001 From: Johnny Zabala Date: Mon, 6 Jul 2020 18:52:06 -0400 Subject: [PATCH] [Fix] `prop-types`/`function-component-definition`: Add check for first letter capitalization in functional component detection Fixes #2554. Fixes #2495. Fixes #2607. Fixes #2352. --- lib/util/Components.js | 24 ++++++++++++++--- .../rules/function-component-definition.js | 26 +++++++++++++++++++ tests/lib/rules/prop-types.js | 22 +++++++++++++++- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/lib/util/Components.js b/lib/util/Components.js index 3ded3e39f4..b5abdd36f2 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -70,6 +70,14 @@ function isReturnsLogicalJSX(node, property, strict) { : (returnsLogicalJSXLeft || returnsLogicalJSXRight); } +function isFirstLetterCapitalized(word) { + if (!word) { + return false; + } + const firstLetter = word.charAt(0); + return firstLetter.toUpperCase() === firstLetter; +} + const Lists = new WeakMap(); /** @@ -624,13 +632,21 @@ function componentRule(rule, context) { * @returns {ASTNode | undefined} */ getStatelessComponent(node) { - if (node.type === 'FunctionDeclaration') { - if (utils.isReturningJSXOrNull(node)) { - return node; - } + if ( + node.type === 'FunctionDeclaration' + && isFirstLetterCapitalized(node.id.name) + && utils.isReturningJSXOrNull(node) + ) { + return node; } if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') { + if (node.parent.type === 'VariableDeclarator' && utils.isReturningJSXOrNull(node)) { + if (isFirstLetterCapitalized(node.parent.id.name)) { + return node; + } + return undefined; + } if (utils.isInAllowedPositionForComponent(node) && utils.isReturningJSXOrNull(node)) { return node; } diff --git a/tests/lib/rules/function-component-definition.js b/tests/lib/rules/function-component-definition.js index 6fd8269a1f..f75338592f 100644 --- a/tests/lib/rules/function-component-definition.js +++ b/tests/lib/rules/function-component-definition.js @@ -68,6 +68,32 @@ ruleTester.run('function-component-definition', rule, { }, { code: 'var Foo = React.memo(function Foo() { return

})', options: [{namedComponents: 'function-declaration'}] + }, { + // shouldn't trigger this rule since functions stating with a lowercase + // letter are not considered components + code: ` + const selectAvatarByUserId = (state, id) => { + const user = selectUserById(state, id) + return null + } + `, + options: [{namedComponents: 'function-declaration'}] + }, { + // shouldn't trigger this rule since functions stating with a lowercase + // letter are not considered components + code: ` + function ensureValidSourceType(sourceType: string) { + switch (sourceType) { + case 'ALBUM': + case 'PLAYLIST': + return sourceType; + default: + return null; + } + } + `, + options: [{namedComponents: 'arrow-function'}], + parser: parsers.TYPESCRIPT_ESLINT }, { code: 'function Hello(props: Test) { return

}', options: [{namedComponents: 'function-declaration'}], diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index 316410f76b..bd0572a6b3 100755 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -2504,7 +2504,27 @@ ruleTester.run('prop-types', rule, { } `, parser: parsers.TYPESCRIPT_ESLINT - } + }, + // shouldn't trigger this rule since functions stating with a lowercase + // letter are not considered components + ` + function noAComponent(props) { + return

{props.text}
+ } + `, + // shouldn't trigger this rule for 'render' since functions stating with a lowercase + // letter are not considered components + ` + const MyComponent = (props) => { + const render = () => { + return {props.hello}; + } + return render(); + }; + MyComponent.propTypes = { + hello: PropTypes.string.isRequired, + }; + ` ], invalid: [