From b713026c48a7292488426f98f5f0b22968033a79 Mon Sep 17 00:00:00 2001 From: Rodrigo Bondoc Date: Mon, 2 Dec 2024 20:17:55 -0800 Subject: [PATCH] [Fix] `prop-types`: props missing in validation when using generic types from a namespace import --- CHANGELOG.md | 2 + lib/util/propTypes.js | 4 +- tests/lib/rules/prop-types.js | 464 ++++++++++++++++++++++++++++++++++ 3 files changed, 469 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8497f49c61..9de24f1426 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,13 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`no-danger`]: avoid a crash on a nested component name ([#3833][] @ljharb) * [Fix] types: correct generated type declaration ([#3840][] @ocavue) * [`no-unknown-property`]: support `precedence` prop in react 19 ([#3829][] @acusti) +* [`prop-types`]: props missing in validation when using generic types from a namespace import ([#3859][] @rbondoc96) ### Changed * [Tests] [`jsx-no-script-url`]: Improve tests ([#3849][] @radu2147) * [Docs] fix broken links: [`default-props-match-prop-types`], [`jsx-boolean-value`], [`jsx-curly-brace-presence`], [`jsx-no-bind`], [`no-array-index-key`], [`no-is-mounted`], [`no-render-return-value`], [`require-default-props`] ([#3841][] @bastiendmt) +[#3859]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3859 [#3849]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3849 [#3841]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3841 [#3840]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3840 diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index f26a6510c9..3a8cc67f93 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -107,8 +107,10 @@ module.exports = function propTypesInstructions(context, components, utils) { const defaults = { customValidators: [] }; const configuration = Object.assign({}, defaults, context.options[0] || {}); const customValidators = configuration.customValidators; - const allowedGenericTypes = new Set(['forwardRef', 'ForwardRefRenderFunction', 'VFC', 'VoidFunctionComponent', 'PropsWithChildren', 'SFC', 'StatelessComponent', 'FunctionComponent', 'FC']); + const allowedGenericTypes = new Set(['ComponentProps', 'ComponentPropsWithoutRef', 'forwardRef', 'ForwardRefRenderFunction', 'VFC', 'VoidFunctionComponent', 'PropsWithChildren', 'SFC', 'StatelessComponent', 'FunctionComponent', 'FC']); const genericTypeParamIndexWherePropsArePresent = { + ComponentProps: 0, + ComponentPropsWithoutRef: 0, ForwardRefRenderFunction: 1, forwardRef: 1, VoidFunctionComponent: 0, diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js index 39dc8bad3c..4a7d05ad6f 100644 --- a/tests/lib/rules/prop-types.js +++ b/tests/lib/rules/prop-types.js @@ -4117,6 +4117,470 @@ ruleTester.run('prop-types', rule, { `, features: ['ts', 'no-babel'], }, + { + code: ` + import {ComponentPropsWithoutRef, forwardRef} from "react"; + + export const FancyButton = forwardRef>( + ({ className, children, ...props }, ref) => ( + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import {ComponentProps, forwardRef} from "react"; + + export const FancyButton = forwardRef>( + ({ className, children, ...props }, ref) => ( + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import {ComponentPropsWithoutRef, ElementRef, forwardRef} from "react"; + + const BaseButton = forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = forwardRef, ComponentPropsWithoutRef>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import {ComponentProps, ElementRef, forwardRef} from "react"; + + const BaseButton = forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = forwardRef, ComponentProps>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import {ComponentProps, ComponentPropsWithoutRef, ElementRef, forwardRef} from "react"; + + const BaseButton = forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = forwardRef, ComponentPropsWithoutRef>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import {ComponentProps, ComponentPropsWithoutRef, ElementRef, forwardRef} from "react"; + + const BaseButton = forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = forwardRef, ComponentProps>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import React from "react"; + + export const FancyButton = React.forwardRef>( + ({ className, children, ...props }, ref) => ( + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import React from "react"; + + export const FancyButton = React.forwardRef>( + ({ className, children, ...props }, ref) => ( + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import React from "react"; + + const BaseButton = React.forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = React.forwardRef, React.ComponentProps>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import React from "react"; + + const BaseButton = React.forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import React from "react"; + + const BaseButton = React.forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import React from "react"; + + const BaseButton = React.forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = React.forwardRef, React.ComponentProps>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import * as React from "react"; + + export const FancyButton = React.forwardRef>( + ({ className, children, ...props }, ref) => ( + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import * as React from "react"; + + export const FancyButton = React.forwardRef>( + ({ className, children, ...props }, ref) => ( + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import * as React from "react"; + + const BaseButton = React.forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = React.forwardRef, React.ComponentProps>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import * as React from "react"; + + const BaseButton = React.forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import * as React from "react"; + + const BaseButton = React.forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = React.forwardRef, React.ComponentPropsWithoutRef>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import * as React from "react"; + + const BaseButton = React.forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = React.forwardRef, React.ComponentProps>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import React, {ComponentPropsWithoutRef} from "react"; + + export const FancyButton = React.forwardRef>( + ({ className, children, ...props }, ref) => ( + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import React, {ComponentProps} from "react"; + + export const FancyButton = React.forwardRef>( + ({ className, children, ...props }, ref) => ( + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import React, {ComponentPropsWithoutRef, ElementRef} from "react"; + + const BaseButton = React.forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = React.forwardRef, ComponentPropsWithoutRef>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import React, {ComponentProps, ElementRef} from "react"; + + const BaseButton = React.forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = React.forwardRef, ComponentProps>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import React, {ComponentProps, ComponentPropsWithoutRef, ElementRef} from "react"; + + const BaseButton = React.forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = React.forwardRef, ComponentProps>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, + { + code: ` + import React, {ComponentProps, ComponentPropsWithoutRef, ElementRef} from "react"; + + const BaseButton = React.forwardRef>( + ({ children, className, ...props }, ref) => ( + + ), + ); + + export const FancyButton = React.forwardRef, ComponentPropsWithoutRef>( + ({ children, className, ...props }, ref) => ( + + {children} + + ), + ); + `, + features: ['ts', 'no-babel'], + }, { code: ` import React, { forwardRef } from 'react';