Skip to content

Commit 179550b

Browse files
Wesitosljharb
authored andcommitted
[Fix] no-this-in-sfc, component detection: Improve stateless component detection
Fixes #3054.
1 parent 261d93a commit 179550b

File tree

3 files changed

+54
-20
lines changed

3 files changed

+54
-20
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
55

66
## Unreleased
77

8+
### Fixed
9+
* [`no-this-in-sfc`], component detection: Improve stateless component detection ([#3056][] @Wesitos)
10+
11+
[#3056]: https://github.com/yannickcr/eslint-plugin-react/pull/3056
12+
813
## [7.25.0] - 2021.08.27
914

1015
### Added

lib/util/Components.js

+33-18
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@ function componentRule(rule, context) {
608608
* @returns {ASTNode | undefined}
609609
*/
610610
getStatelessComponent(node) {
611+
const parent = node.parent;
611612
if (
612613
node.type === 'FunctionDeclaration'
613614
&& (!node.id || isFirstLetterCapitalized(node.id.name))
@@ -617,6 +618,13 @@ function componentRule(rule, context) {
617618
}
618619

619620
if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
621+
const isMethod = parent.type === 'Property' && parent.method;
622+
const isPropertyAssignment = parent.type === 'AssignmentExpression'
623+
&& parent.left.type === 'MemberExpression';
624+
const isModuleExportsAssignment = isPropertyAssignment
625+
&& parent.left.object.name === 'module'
626+
&& parent.left.property.name === 'exports';
627+
620628
if (node.parent.type === 'ExportDefaultDeclaration') {
621629
if (utils.isReturningJSX(node)) {
622630
return node;
@@ -630,31 +638,38 @@ function componentRule(rule, context) {
630638
}
631639
return undefined;
632640
}
633-
if (utils.isInAllowedPositionForComponent(node) && utils.isReturningJSXOrNull(node)) {
634-
if (utils.isParentComponentNotStatelessComponent(node)) return undefined;
635641

636-
const isMethod = node.parent.type === 'Property' && node.parent.method;
642+
// Case like `React.memo(() => <></>)` or `React.forwardRef(...)`
643+
const pragmaComponentWrapper = utils.getPragmaComponentWrapper(node);
644+
if (pragmaComponentWrapper) {
645+
return pragmaComponentWrapper;
646+
}
637647

638-
if (isMethod && !isFirstLetterCapitalized(node.parent.key.name)) {
639-
return utils.isReturningJSX(node) ? node : undefined;
640-
}
648+
if (!(utils.isInAllowedPositionForComponent(node) && utils.isReturningJSXOrNull(node))) {
649+
return undefined;
650+
}
641651

642-
if (node.id && isFirstLetterCapitalized(node.id.name)) {
643-
return node;
644-
}
652+
if (utils.isParentComponentNotStatelessComponent(node)) {
653+
return undefined;
654+
}
645655

646-
if (!node.id) {
647-
return node;
648-
}
656+
if (isMethod && !isFirstLetterCapitalized(node.parent.key.name)) {
657+
return utils.isReturningJSX(node) ? node : undefined;
658+
}
649659

650-
return undefined;
660+
if (node.id) {
661+
return isFirstLetterCapitalized(node.id.name) ? node : undefined;
651662
}
652663

653-
// Case like `React.memo(() => <></>)` or `React.forwardRef(...)`
654-
const pragmaComponentWrapper = utils.getPragmaComponentWrapper(node);
655-
if (pragmaComponentWrapper) {
656-
return pragmaComponentWrapper;
664+
if (
665+
isPropertyAssignment
666+
&& !isModuleExportsAssignment
667+
&& !isFirstLetterCapitalized(parent.left.property.name)
668+
) {
669+
return undefined;
657670
}
671+
672+
return node;
658673
}
659674

660675
return undefined;
@@ -783,7 +798,7 @@ function componentRule(rule, context) {
783798
},
784799

785800
isParentComponentNotStatelessComponent(node) {
786-
return (
801+
return !!(
787802
node.parent
788803
&& node.parent.key
789804
&& node.parent.key.type === 'Identifier'

tests/lib/rules/no-this-in-sfc.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const parserOptions = {
2323

2424
const ruleTester = new RuleTester({parserOptions});
2525
ruleTester.run('no-this-in-sfc', rule, {
26-
valid: [{
26+
valid: [].concat({
2727
code: `
2828
function Foo(props) {
2929
const { foo } = props;
@@ -141,7 +141,21 @@ ruleTester.run('no-this-in-sfc', rule, {
141141
},
142142
});
143143
`
144-
}],
144+
}, {
145+
code: `
146+
obj.notAComponent = function () {
147+
return this.a || null;
148+
};`
149+
}, parsers.TS([{
150+
code: `
151+
$.fn.getValueAsStringWeak = function (): string | null {
152+
const val = this.length === 1 ? this.val() : null;
153+
154+
return typeof val === 'string' ? val : null;
155+
};
156+
`,
157+
parser: parsers['@TYPESCRIPT_ESLINT']
158+
}])),
145159
invalid: [{
146160
code: `
147161
function Foo(props) {

0 commit comments

Comments
 (0)