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..9ab3640938 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) { @@ -186,6 +186,28 @@ module.exports = { }); } + /** + * @param {Object} component The current component to process + * @param {Array} list The all components to process + * @returns {Boolean} True if the component is nested False if not. + */ + function checkNestedComponent(component, list) { + const componentIsMemo = component.node.callee && component.node.callee.name === 'memo'; + const argumentIsForwardRef = component.node.arguments && component.node.arguments[0].callee && component.node.arguments[0].callee.name === 'forwardRef'; + if (componentIsMemo && argumentIsForwardRef) { + const forwardComponent = list.find( + (innerComponent) => ( + innerComponent.node.range[0] === component.node.arguments[0].range[0] + && innerComponent.node.range[0] === component.node.arguments[0].range[0] + )); + + const isValidated = mustBeValidated(forwardComponent); + const isIgnorePropsValidation = forwardComponent.ignorePropsValidation; + + return isIgnorePropsValidation || isValidated; + } + } + return { 'Program:exit'() { const list = components.list(); @@ -193,6 +215,7 @@ module.exports = { values(list) .filter((component) => mustBeValidated(component)) .forEach((component) => { + if (checkNestedComponent(component, values(list))) return; reportUndeclaredPropTypes(component); }); }, diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index 049e387da6..df762d74ef 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -4141,6 +4141,46 @@ ruleTester.run('prop-types', rule, { }; `, features: ['ts', 'no-babel'], + }, + { + code: ` + import React, { 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'], } )),