diff --git a/.changeset/tiny-days-taste.md b/.changeset/tiny-days-taste.md new file mode 100644 index 00000000000..89d63407cce --- /dev/null +++ b/.changeset/tiny-days-taste.md @@ -0,0 +1,13 @@ +--- +'react-docgen': minor +--- + +Support generic types on `React.forwardRef` calls. + +Example: + +`react-docgen` will now find `IButtonProps`. + +```ts +export const FullWidthButton = forwardRef(() => {}); +``` diff --git a/packages/react-docgen/src/utils/__tests__/__snapshots__/getTypeFromReactComponent-test.ts.snap b/packages/react-docgen/src/utils/__tests__/__snapshots__/getTypeFromReactComponent-test.ts.snap index ce2f988c66b..e2a00ad3a7d 100644 --- a/packages/react-docgen/src/utils/__tests__/__snapshots__/getTypeFromReactComponent-test.ts.snap +++ b/packages/react-docgen/src/utils/__tests__/__snapshots__/getTypeFromReactComponent-test.ts.snap @@ -107,6 +107,20 @@ exports[`getTypeFromReactComponent > TypeScript > classes > finds props type in ] `; +exports[`getTypeFromReactComponent > TypeScript > stateless > does not find generic forwardRef type annotation on typo 1`] = `[]`; + +exports[`getTypeFromReactComponent > TypeScript > stateless > finds generic forwardRef type annotation 1`] = ` +[ + Node { + "type": "TSTypeReference", + "typeName": Node { + "name": "Props", + "type": "Identifier", + }, + }, +] +`; + exports[`getTypeFromReactComponent > TypeScript > stateless > finds multiple variable type annotation 1`] = ` [ Node { diff --git a/packages/react-docgen/src/utils/__tests__/getTypeFromReactComponent-test.ts b/packages/react-docgen/src/utils/__tests__/getTypeFromReactComponent-test.ts index 3e89e65fc11..47e6d26836d 100644 --- a/packages/react-docgen/src/utils/__tests__/getTypeFromReactComponent-test.ts +++ b/packages/react-docgen/src/utils/__tests__/getTypeFromReactComponent-test.ts @@ -52,6 +52,30 @@ describe('getTypeFromReactComponent', () => { expect(getTypeFromReactComponent(path)).toMatchSnapshot(); }); + test('finds generic forwardRef type annotation', () => { + const path = parseTypescript + .statementLast( + `import React from 'react'; + const x = React.forwardRef>((props, ref) => {})`, + ) + .get('declarations')[0] + .get('init') as NodePath; + + expect(getTypeFromReactComponent(path)).toMatchSnapshot(); + }); + + test('does not find generic forwardRef type annotation on typo', () => { + const path = parseTypescript + .statementLast( + `import React from 'react'; + const x = React.backwardRef>((props, ref) => {})`, + ) + .get('declarations')[0] + .get('init') as NodePath; + + expect(getTypeFromReactComponent(path)).toMatchSnapshot(); + }); + test('finds param inline type', () => { const path = parseTypescript .statementLast( diff --git a/packages/react-docgen/src/utils/getTypeFromReactComponent.ts b/packages/react-docgen/src/utils/getTypeFromReactComponent.ts index b0241c121a0..1e0752a8b8f 100644 --- a/packages/react-docgen/src/utils/getTypeFromReactComponent.ts +++ b/packages/react-docgen/src/utils/getTypeFromReactComponent.ts @@ -25,20 +25,28 @@ import getTypeIdentifier from './getTypeIdentifier.js'; import isReactBuiltinReference from './isReactBuiltinReference.js'; import unwrapBuiltinTSPropTypes from './unwrapBuiltinTSPropTypes.js'; -// TODO TESTME - function getStatelessPropsPath( componentDefinition: NodePath, ): NodePath | undefined { - let value = componentDefinition; + if (!componentDefinition.isFunction()) return; - if (isReactForwardRefCall(value)) { - value = resolveToValue(value.get('arguments')[0]!); - } + return componentDefinition.get('params')[0]; +} - if (!value.isFunction()) return; +function getForwardRefGenericsType( + componentDefinition: NodePath, +): NodePath | null { + const typeParameters = componentDefinition.get('typeParameters') as NodePath< + TSTypeParameterInstantiation | null | undefined + >; + + if (typeParameters && typeParameters.hasNode()) { + const params = typeParameters.get('params'); + + return params[1] ?? null; + } - return value.get('params')[0]; + return null; } function findAssignedVariableType( @@ -106,6 +114,19 @@ export default (componentDefinition: NodePath): NodePath[] => { } } } else { + if (isReactForwardRefCall(componentDefinition)) { + const genericTypeAnnotation = + getForwardRefGenericsType(componentDefinition); + + if (genericTypeAnnotation) { + typePaths.push(genericTypeAnnotation); + } + + componentDefinition = resolveToValue( + componentDefinition.get('arguments')[0]!, + ); + } + const propsParam = getStatelessPropsPath(componentDefinition); if (propsParam) {