Skip to content

Commit ee4bad3

Browse files
jzabalaljharb
authored andcommitted
[Fix] prop-types/function-component-definition: Add check for first letter capitalization in functional component detection
Fixes #2554. Fixes #2495. Fixes #2607. Fixes #2352.
1 parent 5bab611 commit ee4bad3

File tree

3 files changed

+67
-5
lines changed

3 files changed

+67
-5
lines changed

lib/util/Components.js

+20-4
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ function isReturnsLogicalJSX(node, property, strict) {
7070
: (returnsLogicalJSXLeft || returnsLogicalJSXRight);
7171
}
7272

73+
function isFirstLetterCapitalized(word) {
74+
if (!word) {
75+
return false;
76+
}
77+
const firstLetter = word.charAt(0);
78+
return firstLetter.toUpperCase() === firstLetter;
79+
}
80+
7381
const Lists = new WeakMap();
7482

7583
/**
@@ -624,13 +632,21 @@ function componentRule(rule, context) {
624632
* @returns {ASTNode | undefined}
625633
*/
626634
getStatelessComponent(node) {
627-
if (node.type === 'FunctionDeclaration') {
628-
if (utils.isReturningJSXOrNull(node)) {
629-
return node;
630-
}
635+
if (
636+
node.type === 'FunctionDeclaration'
637+
&& isFirstLetterCapitalized(node.id.name)
638+
&& utils.isReturningJSXOrNull(node)
639+
) {
640+
return node;
631641
}
632642

633643
if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
644+
if (node.parent.type === 'VariableDeclarator' && utils.isReturningJSXOrNull(node)) {
645+
if (isFirstLetterCapitalized(node.parent.id.name)) {
646+
return node;
647+
}
648+
return undefined;
649+
}
634650
if (utils.isInAllowedPositionForComponent(node) && utils.isReturningJSXOrNull(node)) {
635651
return node;
636652
}

tests/lib/rules/function-component-definition.js

+26
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,32 @@ ruleTester.run('function-component-definition', rule, {
6868
}, {
6969
code: 'var Foo = React.memo(function Foo() { return <p/> })',
7070
options: [{namedComponents: 'function-declaration'}]
71+
}, {
72+
// shouldn't trigger this rule since functions stating with a lowercase
73+
// letter are not considered components
74+
code: `
75+
const selectAvatarByUserId = (state, id) => {
76+
const user = selectUserById(state, id)
77+
return null
78+
}
79+
`,
80+
options: [{namedComponents: 'function-declaration'}]
81+
}, {
82+
// shouldn't trigger this rule since functions stating with a lowercase
83+
// letter are not considered components
84+
code: `
85+
function ensureValidSourceType(sourceType: string) {
86+
switch (sourceType) {
87+
case 'ALBUM':
88+
case 'PLAYLIST':
89+
return sourceType;
90+
default:
91+
return null;
92+
}
93+
}
94+
`,
95+
options: [{namedComponents: 'arrow-function'}],
96+
parser: parsers.TYPESCRIPT_ESLINT
7197
}, {
7298
code: 'function Hello(props: Test) { return <p/> }',
7399
options: [{namedComponents: 'function-declaration'}],

tests/lib/rules/prop-types.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -2504,7 +2504,27 @@ ruleTester.run('prop-types', rule, {
25042504
}
25052505
`,
25062506
parser: parsers.TYPESCRIPT_ESLINT
2507-
}
2507+
},
2508+
// shouldn't trigger this rule since functions stating with a lowercase
2509+
// letter are not considered components
2510+
`
2511+
function noAComponent(props) {
2512+
return <div>{props.text}</div>
2513+
}
2514+
`,
2515+
// shouldn't trigger this rule for 'render' since functions stating with a lowercase
2516+
// letter are not considered components
2517+
`
2518+
const MyComponent = (props) => {
2519+
const render = () => {
2520+
return <test>{props.hello}</test>;
2521+
}
2522+
return render();
2523+
};
2524+
MyComponent.propTypes = {
2525+
hello: PropTypes.string.isRequired,
2526+
};
2527+
`
25082528
],
25092529

25102530
invalid: [

0 commit comments

Comments
 (0)