diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js index 8cba850cda..a89530c737 100644 --- a/lib/util/propTypes.js +++ b/lib/util/propTypes.js @@ -100,8 +100,20 @@ 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(['VoidFunctionComponent', 'PropsWithChildren', 'SFC', 'StatelessComponent', 'FunctionComponent', 'FC']); + const allowedGenericTypes = new Set(['forwardRef', 'ForwardRefRenderFunction', 'VoidFunctionComponent', 'PropsWithChildren', 'SFC', 'StatelessComponent', 'FunctionComponent', 'FC']); + const genericTypeParamIndexWherePropsArePresent = { + ForwardRefRenderFunction: 1, + forwardRef: 1, + VoidFunctionComponent: 0, + PropsWithChildren: 0, + SFC: 0, + StatelessComponent: 0, + FunctionComponent: 0, + FC: 0, + }; const genericReactTypesImport = new Set(); + // import { FC as X } from 'react' -> localToImportedMap = { x: FC } + const localToImportedMap = {}; /** * Returns the full scope. @@ -521,9 +533,14 @@ module.exports = function propTypesInstructions(context, components, utils) { * @param {ASTNode} node * @return {string | undefined} */ - function getTypeName(node) { + function getLeftMostTypeName(node) { + if (node.name) return node.name; + if (node.left) return getLeftMostTypeName(node.left); + } + + function getRightMostTypeName(node) { if (node.name) return node.name; - if (node.left) return getTypeName(node.left); + if (node.right) return getRightMostTypeName(node.right); } class DeclarePropTypesForTSTypeAnnotation { @@ -579,14 +596,20 @@ module.exports = function propTypesInstructions(context, components, utils) { let typeName; if (astUtil.isTSTypeReference(node)) { typeName = node.typeName.name; - const shouldTraverseTypeParams = genericReactTypesImport.has(getTypeName(node.typeName)); + const leftMostName = getLeftMostTypeName(node.typeName); + const shouldTraverseTypeParams = genericReactTypesImport.has(leftMostName); if (shouldTraverseTypeParams && node.typeParameters && node.typeParameters.length !== 0) { // All react Generic types are derived from: // type PropsWithChildren
= P & { children?: ReactNode | undefined }
// So we should construct an optional children prop
this.shouldSpecifyOptionalChildrenProps = true;
- const nextNode = node.typeParameters.params[0];
+ const rightMostName = getRightMostTypeName(node.typeName);
+ const importedName = localToImportedMap[rightMostName];
+ const idx = genericTypeParamIndexWherePropsArePresent[
+ leftMostName !== rightMostName ? rightMostName : importedName
+ ];
+ const nextNode = node.typeParameters.params[idx];
this.visitTSNode(nextNode);
return;
}
@@ -941,6 +964,30 @@ module.exports = function propTypesInstructions(context, components, utils) {
return;
}
+ if (
+ node.parent
+ && node.parent.callee
+ && node.parent.typeParameters
+ && node.parent.typeParameters.params
+ && (
+ node.parent.callee.name === 'forwardRef' || (
+ node.parent.callee.object
+ && node.parent.callee.property
+ && node.parent.callee.object.name === 'React'
+ && node.parent.callee.property.name === 'forwardRef'
+ )
+ )
+ ) {
+ const propTypes = node.parent.typeParameters.params[1];
+ const declaredPropTypes = {};
+ const obj = new DeclarePropTypesForTSTypeAnnotation(propTypes, declaredPropTypes);
+ components.set(node, {
+ declaredPropTypes: obj.declaredPropTypes,
+ ignorePropsValidation: false,
+ });
+ return;
+ }
+
const siblingIdentifier = node.parent && node.parent.id;
const siblingHasTypeAnnotation = siblingIdentifier && siblingIdentifier.typeAnnotation;
const isNodeAnnotated = annotations.isAnnotatedFunctionPropsDeclaration(node, context);
@@ -1092,6 +1139,7 @@ module.exports = function propTypesInstructions(context, components, utils) {
// handles import { FC } from 'react' or import { FC as X } from 'react'
if (specifier.type === 'ImportSpecifier' && allowedGenericTypes.has(specifier.imported.name)) {
genericReactTypesImport.add(specifier.local.name);
+ localToImportedMap[specifier.local.name] = specifier.imported.name;
}
});
}
diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js
index afc47d6ca5..b4cd3473b6 100644
--- a/tests/lib/rules/prop-types.js
+++ b/tests/lib/rules/prop-types.js
@@ -3411,6 +3411,66 @@ ruleTester.run('prop-types', rule, {
`,
features: ['ts', 'no-babel'],
},
+ {
+ code: `
+ import React, { ForwardRefRenderFunction as X } from 'react'
+
+ type IfooProps = { e: string };
+ const Foo: X