Skip to content

Commit 8fe3dbf

Browse files
authored
fix crash when handling mapped types (#745)
1 parent da91511 commit 8fe3dbf

File tree

6 files changed

+127
-17
lines changed

6 files changed

+127
-17
lines changed

.changeset/famous-needles-wonder.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'react-docgen': patch
3+
---
4+
5+
Fix crash when using TypeScript mapped types

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,75 @@ exports[`getTSType > handles long type cycles 1`] = `
661661
}
662662
`;
663663
664+
exports[`getTSType > handles mapped type 1`] = `
665+
{
666+
"name": "signature",
667+
"raw": "{
668+
[Property in keyof Type]: number;
669+
}",
670+
"signature": {
671+
"properties": [
672+
{
673+
"key": {
674+
"name": "X",
675+
"required": true,
676+
},
677+
"value": {
678+
"name": "number",
679+
},
680+
},
681+
],
682+
},
683+
"type": "object",
684+
}
685+
`;
686+
687+
exports[`getTSType > handles mapped type with implicit any 1`] = `
688+
{
689+
"name": "signature",
690+
"raw": "{
691+
[Property in keyof Type];
692+
}",
693+
"signature": {
694+
"properties": [
695+
{
696+
"key": {
697+
"name": "X",
698+
"required": true,
699+
},
700+
"value": {
701+
"name": "any",
702+
},
703+
},
704+
],
705+
},
706+
"type": "object",
707+
}
708+
`;
709+
710+
exports[`getTSType > handles mapped type without typeParam 1`] = `
711+
{
712+
"name": "signature",
713+
"raw": "{
714+
[Property in keyof X]: string;
715+
}",
716+
"signature": {
717+
"properties": [
718+
{
719+
"key": {
720+
"name": "X",
721+
"required": true,
722+
},
723+
"value": {
724+
"name": "string",
725+
},
726+
},
727+
],
728+
},
729+
"type": "object",
730+
}
731+
`;
732+
664733
exports[`getTSType > handles mapped types 1`] = `
665734
{
666735
"name": "signature",

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,4 @@ describe('getMemberExpressionValuePath', () => {
7171
);
7272
});
7373
});
74-
75-
//TODO test arrow assigned to destructuring
7674
});

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,4 +651,46 @@ describe('getTSType', () => {
651651

652652
expect(getTSType(typePath)).toMatchSnapshot();
653653
});
654+
655+
test('handles mapped type', () => {
656+
const typePath = typeAlias(`
657+
let action: OptionsFlags<X>;
658+
type OptionsFlags<Type> = {
659+
[Property in keyof Type]: number;
660+
};
661+
interface X {
662+
foo: string
663+
}
664+
`);
665+
666+
expect(getTSType(typePath)).toMatchSnapshot();
667+
});
668+
669+
test('handles mapped type with implicit any', () => {
670+
const typePath = typeAlias(`
671+
let action: OptionsFlags<X>;
672+
type OptionsFlags<Type> = {
673+
[Property in keyof Type];
674+
};
675+
interface X {
676+
foo: string
677+
}
678+
`);
679+
680+
expect(getTSType(typePath)).toMatchSnapshot();
681+
});
682+
683+
test('handles mapped type without typeParam', () => {
684+
const typePath = typeAlias(`
685+
let action: OptionsFlags;
686+
type OptionsFlags = {
687+
[Property in keyof X]: string;
688+
};
689+
interface X {
690+
foo: string
691+
}
692+
`);
693+
694+
expect(getTSType(typePath)).toMatchSnapshot();
695+
});
654696
});

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ function getEnumValuesFromArrayExpression(
2222
const values: Array<Record<string, unknown>> = [];
2323

2424
path.get('elements').forEach(elementPath => {
25-
// Array holes TODO test
26-
if (elementPath.node == null) return;
25+
if (!elementPath.hasNode()) return;
2726

2827
if (elementPath.isSpreadElement()) {
2928
const value = resolveToValue(elementPath.get('argument'));
@@ -85,7 +84,6 @@ function getPropTypeOneOfType(argumentPath: NodePath): PropTypeDescriptor {
8584
type.value = printValue(argumentPath);
8685
} else {
8786
type.value = argumentPath.get('elements').map(elementPath => {
88-
// Array holes TODO test
8987
if (!elementPath.hasNode()) return;
9088
const descriptor: PropTypeDescriptor = getPropType(elementPath);
9189
const docs = getDocblock(

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

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ function handleTSIntersectionType(
252252
};
253253
}
254254

255+
// type OptionsFlags<Type> = { [Property in keyof Type]; };
255256
function handleTSMappedType(
256257
path: NodePath<TSMappedType>,
257258
typeParams: TypeParameters | null,
@@ -269,12 +270,7 @@ function handleTSMappedType(
269270
if (typeAnnotation.hasNode()) {
270271
value = getTSTypeWithResolvedTypes(typeAnnotation, typeParams);
271272
} else {
272-
value = { name: 'any' }; //TODO test
273-
/**
274-
type OptionsFlags<Type> = {
275-
[Property in keyof Type];
276-
};
277-
*/
273+
value = { name: 'any' };
278274
}
279275

280276
return {
@@ -385,6 +381,7 @@ function handleTSTypeQuery(
385381

386382
function handleTSTypeOperator(
387383
path: NodePath<TSTypeOperator>,
384+
typeParams: TypeParameters | null,
388385
): TypeDescriptor<TSFunctionSignatureType> | null {
389386
if (path.node.operator !== 'keyof') {
390387
return null;
@@ -396,14 +393,13 @@ function handleTSTypeOperator(
396393
value = value.get('exprName');
397394
} else if ('id' in value.node) {
398395
value = value.get('id') as NodePath;
396+
} else if (value.isTSTypeReference()) {
397+
return getTSTypeWithResolvedTypes(value, typeParams);
399398
}
400399

401400
const resolvedPath = resolveToValue(value);
402401

403-
if (
404-
resolvedPath &&
405-
(resolvedPath.isObjectExpression() || resolvedPath.isTSTypeLiteral())
406-
) {
402+
if (resolvedPath.isObjectExpression() || resolvedPath.isTSTypeLiteral()) {
407403
const keys = resolveObjectToNameArray(resolvedPath, true);
408404

409405
if (keys) {
@@ -468,7 +464,7 @@ function getTSTypeWithResolvedTypes(
468464
}
469465

470466
const node = path.node;
471-
let type: TypeDescriptor;
467+
let type: TypeDescriptor | null = null;
472468
let typeAliasName: string | null = null;
473469

474470
if (path.parentPath.isTSTypeAliasDeclaration()) {
@@ -503,7 +499,9 @@ function getTSTypeWithResolvedTypes(
503499
};
504500
} else if (node.type in namedTypes) {
505501
type = namedTypes[node.type](path, typeParams);
506-
} else {
502+
}
503+
504+
if (!type) {
507505
type = { name: 'unknown' };
508506
}
509507

0 commit comments

Comments
 (0)