Skip to content

Commit d797ca7

Browse files
authored
fix: handle circular TypeScript types (#1140)
1 parent 3259c5e commit d797ca7

5 files changed

Lines changed: 74 additions & 8 deletions

File tree

‎.changeset/fuzzy-tools-smile.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'react-docgen': patch
3+
---
4+
5+
Prevent stack overflows caused by circular interface inheritance and type
6+
aliases. Thanks [@router0mail](https://github.com/router0mail) for reporting
7+
this issue.

‎packages/react-docgen/src/handlers/__tests__/codeTypeHandler-test.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,20 @@ describe('codeTypeHandler', () => {
249249
expect(documentation.descriptors).toMatchSnapshot();
250250
});
251251

252+
test('handles circular interface inheritance', () => {
253+
const definition = parseTypescript
254+
.statement(
255+
`(props: A) => null;
256+
interface A extends B { a: number }
257+
interface B extends A { b: number }`,
258+
)
259+
.get('expression') as NodePath<ArrowFunctionExpression>;
260+
261+
codeTypeHandler(documentation, definition);
262+
263+
expect(Object.keys(documentation.descriptors)).toEqual(['b', 'a']);
264+
});
265+
252266
test('does support utility types inline', () => {
253267
const definition = parse
254268
.statement(

‎packages/react-docgen/src/utils/__tests__/resolveGenericTypeAnnotations-test.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,23 @@ describe('resolveGenericTypeAnnotation', () => {
2020
),
2121
).toMatchSnapshot();
2222
});
23+
24+
test('does not resolve circular type aliases', () => {
25+
const code = `
26+
type Foo<T> = Bar<T>;
27+
type Bar<T> = Foo<T>;
28+
var x: Foo<string>;
29+
`;
30+
31+
expect(
32+
resolveGenericTypeAnnotation(
33+
parse
34+
.statement(code, -1)
35+
.get('declarations')[0]
36+
.get('id')
37+
.get('typeAnnotation')
38+
.get('typeAnnotation'),
39+
),
40+
).toBeUndefined();
41+
});
2342
});

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,11 @@ export function applyToTypeProperties(
158158
path: NodePath,
159159
callback: (propertyPath: NodePath, params: TypeParameters | null) => void,
160160
typeParams: TypeParameters | null,
161+
visited = new WeakSet<object>(),
161162
): void {
163+
if (visited.has(path.node)) return;
164+
visited.add(path.node);
165+
162166
if (path.isObjectTypeAnnotation()) {
163167
path
164168
.get('properties')
@@ -168,14 +172,14 @@ export function applyToTypeProperties(
168172
.get('members')
169173
.forEach((propertyPath) => callback(propertyPath, typeParams));
170174
} else if (path.isInterfaceDeclaration()) {
171-
applyExtends(documentation, path, callback, typeParams);
175+
applyExtends(documentation, path, callback, typeParams, visited);
172176

173177
path
174178
.get('body')
175179
.get('properties')
176180
.forEach((propertyPath) => callback(propertyPath, typeParams));
177181
} else if (path.isTSInterfaceDeclaration()) {
178-
applyExtends(documentation, path, callback, typeParams);
182+
applyExtends(documentation, path, callback, typeParams, visited);
179183

180184
path
181185
.get('body')
@@ -187,15 +191,27 @@ export function applyToTypeProperties(
187191
) {
188192
(path.get('types') as Array<NodePath<FlowType | TSType>>).forEach(
189193
(typesPath) =>
190-
applyToTypeProperties(documentation, typesPath, callback, typeParams),
194+
applyToTypeProperties(
195+
documentation,
196+
typesPath,
197+
callback,
198+
typeParams,
199+
visited,
200+
),
191201
);
192202
} else if (!path.isUnionTypeAnnotation()) {
193203
// The react-docgen output format does not currently allow
194204
// for the expression of union types
195205
const typePath = resolveGenericTypeAnnotation(path);
196206

197207
if (typePath) {
198-
applyToTypeProperties(documentation, typePath, callback, typeParams);
208+
applyToTypeProperties(
209+
documentation,
210+
typePath,
211+
callback,
212+
typeParams,
213+
visited,
214+
);
199215
}
200216
}
201217
}
@@ -205,6 +221,7 @@ function applyExtends(
205221
path: NodePath<InterfaceDeclaration | TSInterfaceDeclaration>,
206222
callback: (propertyPath: NodePath, params: TypeParameters | null) => void,
207223
typeParams: TypeParameters | null,
224+
visited: WeakSet<object>,
208225
): void {
209226
const classExtends = path.get('extends');
210227

@@ -238,6 +255,7 @@ function applyExtends(
238255
resolvedPath,
239256
callback,
240257
typeParams,
258+
visited,
241259
);
242260
} else {
243261
const idPath = getTypeIdentifier(extendsPath);

‎packages/react-docgen/src/utils/resolveGenericTypeAnnotation.ts‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,28 @@ import resolveToValue from '../utils/resolveToValue.js';
44
import { unwrapUtilityType } from './flowUtilityTypes.js';
55
import getTypeIdentifier from './getTypeIdentifier.js';
66

7-
function tryResolveGenericTypeAnnotation(path: NodePath): NodePath | undefined {
7+
function tryResolveGenericTypeAnnotation(
8+
path: NodePath,
9+
seen = new WeakSet<object>(),
10+
): NodePath | undefined {
811
let typePath = unwrapUtilityType(path);
912
const idPath = getTypeIdentifier(typePath);
1013

1114
if (idPath) {
1215
typePath = resolveToValue(idPath);
13-
if (isUnreachableFlowType(typePath)) {
16+
if (isUnreachableFlowType(typePath) || seen.has(typePath.node)) {
1417
return;
1518
}
1619

20+
seen.add(typePath.node);
21+
1722
if (typePath.isTypeAlias()) {
18-
return tryResolveGenericTypeAnnotation(typePath.get('right'));
23+
return tryResolveGenericTypeAnnotation(typePath.get('right'), seen);
1924
} else if (typePath.isTSTypeAliasDeclaration()) {
20-
return tryResolveGenericTypeAnnotation(typePath.get('typeAnnotation'));
25+
return tryResolveGenericTypeAnnotation(
26+
typePath.get('typeAnnotation'),
27+
seen,
28+
);
2129
}
2230

2331
return typePath;

0 commit comments

Comments
 (0)