From 179550babbe3621bd857bb3b8b7910a5fc968c96 Mon Sep 17 00:00:00 2001 From: Pedro Palacios Avila Date: Sun, 29 Aug 2021 17:20:55 -0500 Subject: [PATCH] [Fix] `no-this-in-sfc`, component detection: Improve stateless component detection Fixes #3054. --- CHANGELOG.md | 5 +++ lib/util/Components.js | 51 ++++++++++++++++++++----------- tests/lib/rules/no-this-in-sfc.js | 18 +++++++++-- 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60f4e36c46..b7b22ecdca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ## Unreleased +### Fixed +* [`no-this-in-sfc`], component detection: Improve stateless component detection ([#3056][] @Wesitos) + +[#3056]: https://github.com/yannickcr/eslint-plugin-react/pull/3056 + ## [7.25.0] - 2021.08.27 ### Added diff --git a/lib/util/Components.js b/lib/util/Components.js index 15627285dd..c0e62b46bf 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -608,6 +608,7 @@ function componentRule(rule, context) { * @returns {ASTNode | undefined} */ getStatelessComponent(node) { + const parent = node.parent; if ( node.type === 'FunctionDeclaration' && (!node.id || isFirstLetterCapitalized(node.id.name)) @@ -617,6 +618,13 @@ function componentRule(rule, context) { } if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') { + const isMethod = parent.type === 'Property' && parent.method; + const isPropertyAssignment = parent.type === 'AssignmentExpression' + && parent.left.type === 'MemberExpression'; + const isModuleExportsAssignment = isPropertyAssignment + && parent.left.object.name === 'module' + && parent.left.property.name === 'exports'; + if (node.parent.type === 'ExportDefaultDeclaration') { if (utils.isReturningJSX(node)) { return node; @@ -630,31 +638,38 @@ function componentRule(rule, context) { } return undefined; } - if (utils.isInAllowedPositionForComponent(node) && utils.isReturningJSXOrNull(node)) { - if (utils.isParentComponentNotStatelessComponent(node)) return undefined; - const isMethod = node.parent.type === 'Property' && node.parent.method; + // Case like `React.memo(() => <>)` or `React.forwardRef(...)` + const pragmaComponentWrapper = utils.getPragmaComponentWrapper(node); + if (pragmaComponentWrapper) { + return pragmaComponentWrapper; + } - if (isMethod && !isFirstLetterCapitalized(node.parent.key.name)) { - return utils.isReturningJSX(node) ? node : undefined; - } + if (!(utils.isInAllowedPositionForComponent(node) && utils.isReturningJSXOrNull(node))) { + return undefined; + } - if (node.id && isFirstLetterCapitalized(node.id.name)) { - return node; - } + if (utils.isParentComponentNotStatelessComponent(node)) { + return undefined; + } - if (!node.id) { - return node; - } + if (isMethod && !isFirstLetterCapitalized(node.parent.key.name)) { + return utils.isReturningJSX(node) ? node : undefined; + } - return undefined; + if (node.id) { + return isFirstLetterCapitalized(node.id.name) ? node : undefined; } - // Case like `React.memo(() => <>)` or `React.forwardRef(...)` - const pragmaComponentWrapper = utils.getPragmaComponentWrapper(node); - if (pragmaComponentWrapper) { - return pragmaComponentWrapper; + if ( + isPropertyAssignment + && !isModuleExportsAssignment + && !isFirstLetterCapitalized(parent.left.property.name) + ) { + return undefined; } + + return node; } return undefined; @@ -783,7 +798,7 @@ function componentRule(rule, context) { }, isParentComponentNotStatelessComponent(node) { - return ( + return !!( node.parent && node.parent.key && node.parent.key.type === 'Identifier' diff --git a/tests/lib/rules/no-this-in-sfc.js b/tests/lib/rules/no-this-in-sfc.js index cd53447734..724825d544 100644 --- a/tests/lib/rules/no-this-in-sfc.js +++ b/tests/lib/rules/no-this-in-sfc.js @@ -23,7 +23,7 @@ const parserOptions = { const ruleTester = new RuleTester({parserOptions}); ruleTester.run('no-this-in-sfc', rule, { - valid: [{ + valid: [].concat({ code: ` function Foo(props) { const { foo } = props; @@ -141,7 +141,21 @@ ruleTester.run('no-this-in-sfc', rule, { }, }); ` - }], + }, { + code: ` + obj.notAComponent = function () { + return this.a || null; + };` + }, parsers.TS([{ + code: ` + $.fn.getValueAsStringWeak = function (): string | null { + const val = this.length === 1 ? this.val() : null; + + return typeof val === 'string' ? val : null; + }; + `, + parser: parsers['@TYPESCRIPT_ESLINT'] + }])), invalid: [{ code: ` function Foo(props) {