diff --git a/CHANGELOG.md b/CHANGELOG.md
index 944a9f1472..f8b7f6a127 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* `propTypes`: Handle TSTypeReference in no-unused-prop-type ([#3195][] @niik)
* [`sort-prop-types`]: avoid repeated warnings of the same node/reason ([#519][] @ljharb)
* [`jsx-indent`]: Fix indent handling for closing parentheses ([#620][] @stefanbuck])
+* [`prop-types`/`propTypes`]: follow a returned identifier to see if it is JSX ([#1046][] @ljharb)
### Changed
* [readme] change [`jsx-runtime`] link from branch to sha ([#3160][] @tatsushitoji)
@@ -41,6 +42,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
[#3133]: https://github.com/yannickcr/eslint-plugin-react/pull/3133
[#2921]: https://github.com/yannickcr/eslint-plugin-react/pull/2921
[#2753]: https://github.com/yannickcr/eslint-plugin-react/pull/2753
+[#1046]: https://github.com/yannickcr/eslint-plugin-react/issues/1046
[#620]: https://github.com/yannickcr/eslint-plugin-react/pull/620
[#519]: https://github.com/yannickcr/eslint-plugin-react/issues/519
diff --git a/lib/util/jsx.js b/lib/util/jsx.js
index 8e84b05d43..e7ae2357d7 100644
--- a/lib/util/jsx.js
+++ b/lib/util/jsx.js
@@ -8,6 +8,7 @@ const estraverse = require('estraverse');
const elementType = require('jsx-ast-utils/elementType');
const astUtil = require('./ast');
+const variableUtil = require('./variable');
// See https://github.com/babel/babel/blob/ce420ba51c68591e057696ef43e028f41c6e04cd/packages/babel-types/src/validators/react/isCompatTag.js
// for why we only test for the first character
@@ -125,7 +126,8 @@ function isReturningJSX(isCreateElement, ASTnode, context, strict, ignoreNull) {
break;
case 'JSXElement':
case 'JSXFragment':
- setFound(); break;
+ setFound();
+ break;
case 'CallExpression':
if (isCreateElement(childNode)) {
setFound();
@@ -137,6 +139,13 @@ function isReturningJSX(isCreateElement, ASTnode, context, strict, ignoreNull) {
setFound();
}
break;
+ case 'Identifier': {
+ const variable = variableUtil.findVariableByName(context, childNode.name);
+ if (isJSX(variable)) {
+ setFound();
+ }
+ break;
+ }
default:
}
},
diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js
index eba83ebfba..26259d43d0 100644
--- a/tests/lib/rules/prop-types.js
+++ b/tests/lib/rules/prop-types.js
@@ -7624,6 +7624,33 @@ ruleTester.run('prop-types', rule, {
},
],
features: ['ts', 'no-babel'],
+ },
+ {
+ code: `
+ const Foo = ({ foo }) => {
+ return ;
+ }
+ `,
+ errors: [
+ {
+ messageId: 'missingPropType',
+ data: { name: 'foo' },
+ },
+ ],
+ },
+ {
+ code: `
+ const Foo = ({ foo }) => {
+ const returnValue = ;
+ return returnValue;
+ }
+ `,
+ errors: [
+ {
+ messageId: 'missingPropType',
+ data: { name: 'foo' },
+ },
+ ],
}
)),
});
diff --git a/tests/util/jsx.js b/tests/util/jsx.js
index b06ea70c1f..6aef83e0a4 100644
--- a/tests/util/jsx.js
+++ b/tests/util/jsx.js
@@ -20,7 +20,16 @@ const parseCode = (code) => {
return ASTnode.body[0];
};
-const mockContext = {};
+const mockContext = {
+ getScope() {
+ return {
+ type: 'global',
+ upper: null,
+ childScopes: [],
+ variables: [],
+ };
+ },
+};
describe('jsxUtil', () => {
describe('isReturningJSX', () => {