Skip to content

Commit

Permalink
[Fix] prop-types: handle nested forwardRef + memo
Browse files Browse the repository at this point in the history
Fixes #3521.

Co-authored-by: 김상두 <[email protected]>
Co-authored-by: Jordan Harband <[email protected]>
  • Loading branch information
developer-bandi and ljharb committed Jan 14, 2024
1 parent 9f4b2b9 commit 88f7f09
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
25 changes: 24 additions & 1 deletion lib/rules/prop-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -186,13 +186,36 @@ 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();
// Report undeclared proptypes for all classes
values(list)
.filter((component) => mustBeValidated(component))
.forEach((component) => {
if (checkNestedComponent(component, values(list))) return;
reportUndeclaredPropTypes(component);
});
},
Expand Down
40 changes: 40 additions & 0 deletions tests/lib/rules/prop-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <div>Hello {age}</div>;
});
export const Hello = HelloTemp
`,
features: ['types'],
},
{
code: `
import React, { forwardRef, memo } from 'react';
interface Props1 {
age: number;
}
const HelloTemp = forwardRef(({ age }: Props1) => {
return <div>Hello {age}</div>;
});
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 <div>Hello {age}</div>;
}),
);
`,
features: ['types'],
}
)),

Expand Down

0 comments on commit 88f7f09

Please sign in to comment.