Skip to content

Commit 3033ca0

Browse files
authored
Add support for forwardRef<generics> (#923)
1 parent 57b57d3 commit 3033ca0

File tree

4 files changed

+80
-8
lines changed

4 files changed

+80
-8
lines changed

.changeset/tiny-days-taste.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'react-docgen': minor
3+
---
4+
5+
Support generic types on `React.forwardRef` calls.
6+
7+
Example:
8+
9+
`react-docgen` will now find `IButtonProps`.
10+
11+
```ts
12+
export const FullWidthButton = forwardRef<HTMLButtonElement, IButtonProps>(() => {});
13+
```

packages/react-docgen/src/utils/__tests__/__snapshots__/getTypeFromReactComponent-test.ts.snap

+14
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@ exports[`getTypeFromReactComponent > TypeScript > classes > finds props type in
107107
]
108108
`;
109109

110+
exports[`getTypeFromReactComponent > TypeScript > stateless > does not find generic forwardRef type annotation on typo 1`] = `[]`;
111+
112+
exports[`getTypeFromReactComponent > TypeScript > stateless > finds generic forwardRef type annotation 1`] = `
113+
[
114+
Node {
115+
"type": "TSTypeReference",
116+
"typeName": Node {
117+
"name": "Props",
118+
"type": "Identifier",
119+
},
120+
},
121+
]
122+
`;
123+
110124
exports[`getTypeFromReactComponent > TypeScript > stateless > finds multiple variable type annotation 1`] = `
111125
[
112126
Node {

packages/react-docgen/src/utils/__tests__/getTypeFromReactComponent-test.ts

+24
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,30 @@ describe('getTypeFromReactComponent', () => {
5252
expect(getTypeFromReactComponent(path)).toMatchSnapshot();
5353
});
5454

55+
test('finds generic forwardRef type annotation', () => {
56+
const path = parseTypescript
57+
.statementLast<VariableDeclaration>(
58+
`import React from 'react';
59+
const x = React.forwardRef<HTMLDivElement, React.PropsWithChildren<Props>>((props, ref) => {})`,
60+
)
61+
.get('declarations')[0]
62+
.get('init') as NodePath<ArrowFunctionExpression>;
63+
64+
expect(getTypeFromReactComponent(path)).toMatchSnapshot();
65+
});
66+
67+
test('does not find generic forwardRef type annotation on typo', () => {
68+
const path = parseTypescript
69+
.statementLast<VariableDeclaration>(
70+
`import React from 'react';
71+
const x = React.backwardRef<HTMLDivElement, React.PropsWithChildren<Props>>((props, ref) => {})`,
72+
)
73+
.get('declarations')[0]
74+
.get('init') as NodePath<ArrowFunctionExpression>;
75+
76+
expect(getTypeFromReactComponent(path)).toMatchSnapshot();
77+
});
78+
5579
test('finds param inline type', () => {
5680
const path = parseTypescript
5781
.statementLast<VariableDeclaration>(

packages/react-docgen/src/utils/getTypeFromReactComponent.ts

+29-8
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,28 @@ import getTypeIdentifier from './getTypeIdentifier.js';
2525
import isReactBuiltinReference from './isReactBuiltinReference.js';
2626
import unwrapBuiltinTSPropTypes from './unwrapBuiltinTSPropTypes.js';
2727

28-
// TODO TESTME
29-
3028
function getStatelessPropsPath(
3129
componentDefinition: NodePath,
3230
): NodePath | undefined {
33-
let value = componentDefinition;
31+
if (!componentDefinition.isFunction()) return;
3432

35-
if (isReactForwardRefCall(value)) {
36-
value = resolveToValue(value.get('arguments')[0]!);
37-
}
33+
return componentDefinition.get('params')[0];
34+
}
3835

39-
if (!value.isFunction()) return;
36+
function getForwardRefGenericsType(
37+
componentDefinition: NodePath,
38+
): NodePath<TSType> | null {
39+
const typeParameters = componentDefinition.get('typeParameters') as NodePath<
40+
TSTypeParameterInstantiation | null | undefined
41+
>;
42+
43+
if (typeParameters && typeParameters.hasNode()) {
44+
const params = typeParameters.get('params');
45+
46+
return params[1] ?? null;
47+
}
4048

41-
return value.get('params')[0];
49+
return null;
4250
}
4351

4452
function findAssignedVariableType(
@@ -106,6 +114,19 @@ export default (componentDefinition: NodePath): NodePath[] => {
106114
}
107115
}
108116
} else {
117+
if (isReactForwardRefCall(componentDefinition)) {
118+
const genericTypeAnnotation =
119+
getForwardRefGenericsType(componentDefinition);
120+
121+
if (genericTypeAnnotation) {
122+
typePaths.push(genericTypeAnnotation);
123+
}
124+
125+
componentDefinition = resolveToValue(
126+
componentDefinition.get('arguments')[0]!,
127+
);
128+
}
129+
109130
const propsParam = getStatelessPropsPath(componentDefinition);
110131

111132
if (propsParam) {

0 commit comments

Comments
 (0)