diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b95e4560f..fdf6c826a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`jsx-key`]: detect conditional returns ([#3630][] @yialo) * [`jsx-newline`]: prevent a crash when `allowMultilines ([#3633][] @ljharb) * [`no-unknown-property`]: use a better regex to avoid a crash ([#3666][] @ljharb @SCH227) +* [`prop-types`]: handle nested forwardRef + memo ([#3679][] @developer-bandi) ### Changed * [Refactor] `propTypes`: extract type params to var ([#3634][] @HenryBrown0) @@ -33,6 +34,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [Docs] [`jsx-key`]: fix correct example ([#3656][] @developer-bandi) * [Tests] `jsx-wrap-multilines`: passing tests ([#3545][] @burtek) +[#3679]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3679 [#3677]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3677 [#3675]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3675 [#3674]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3674 diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js index bb317a2467..1cbc2f56b4 100644 --- a/lib/rules/prop-types.js +++ b/lib/rules/prop-types.js @@ -154,7 +154,7 @@ module.exports = { while (node) { const component = components.get(node); - const isDeclared = component && component.confidence === 2 + const isDeclared = component && component.confidence >= 2 && internalIsDeclaredInComponent(component.declaredPropTypes || {}, names); if (isDeclared) { @@ -176,6 +176,7 @@ module.exports = { && !isIgnored(propType.allNames[0]) && !isDeclaredInComponent(component.node, propType.allNames) )); + undeclareds.forEach((propType) => { report(context, messages.missingPropType, 'missingPropType', { node: propType.node, @@ -186,13 +187,32 @@ module.exports = { }); } + /** + * @param {Object} curComponent The current component to process + * @param {Object} prevComponent The prev component to process + * @returns {Boolean} True if the component is nested False if not. + */ + function checkNestedComponent(curComponent, prevComponent) { + if (curComponent.node.callee && curComponent.node.callee.name === 'memo' && prevComponent) { + const prevComponentRange = prevComponent.node.range; + const currentComponentRange = curComponent.node.arguments[0].range; + + if (prevComponentRange[0] === currentComponentRange[0] + && prevComponentRange[1] === currentComponentRange[1]) { + return true; + } + } + return false; + } + return { 'Program:exit'() { const list = components.list(); // Report undeclared proptypes for all classes values(list) .filter((component) => mustBeValidated(component)) - .forEach((component) => { + .forEach((component, index, array) => { + if (checkNestedComponent(component, array[index - 1])) return; reportUndeclaredPropTypes(component); }); }, diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index 049e387da6..0a5b7d8308 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -4141,6 +4141,49 @@ ruleTester.run('prop-types', rule, { }; `, features: ['ts', 'no-babel'], + }, + { + code: ` + import React, { forwardRef, memo } from 'react'; + + interface Props1 { + age: number; + } + const HelloTemp = memo(({ age }: Props1) => { + return
Hello {age}
; + }); + export const Hello = HelloTemp + `, + features: ['types'], + }, + { + code: ` + import React, { forwardRef, memo } from 'react'; + + interface Props1 { + age: number; + } + const HelloTemp = forwardRef(({ age }: Props1) => { + return
Hello {age}
; + }); + export const Hello = memo(HelloTemp); + `, + features: ['types'], + }, + { + code: ` + import React, { forwardRef, memo } from 'react'; + + interface Props1 { + age: number; + } + export const Hello = memo( + forwardRef(({ age }: Props1) => { + return
Hello {age}
; + }) + ); + `, + features: ['types'], } )),