Skip to content

Commit 9ef2f84

Browse files
authored
Resolve flow $Keys<> to union type (#225)
* Resolve flow $Keys to union type * Add one more test without typeof
1 parent efdfeee commit 9ef2f84

File tree

3 files changed

+58
-2
lines changed

3 files changed

+58
-2
lines changed

src/utils/__tests__/getFlowType-test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,34 @@ describe('getFlowType', () => {
248248
it(type, () => test(type, types[type]));
249249
});
250250
});
251+
252+
it('resolves $Keys to union', () => {
253+
var typePath = statement(`
254+
var x: $Keys<typeof CONTENTS> = 2;
255+
const CONTENTS = {
256+
'apple': '🍎',
257+
'banana': '🍌',
258+
};
259+
`).get('declarations', 0).get('id').get('typeAnnotation').get('typeAnnotation');
260+
261+
expect(getFlowType(typePath)).toEqual({name: 'union', elements: [
262+
{ name: 'literal', value: "'apple'" },
263+
{ name: 'literal', value: "'banana'" },
264+
], raw: '$Keys<typeof CONTENTS>'});
265+
});
266+
267+
it('resolves $Keys without typeof to union', () => {
268+
var typePath = statement(`
269+
var x: $Keys<CONTENTS> = 2;
270+
const CONTENTS = {
271+
'apple': '🍎',
272+
'banana': '🍌',
273+
};
274+
`).get('declarations', 0).get('id').get('typeAnnotation').get('typeAnnotation');
275+
276+
expect(getFlowType(typePath)).toEqual({name: 'union', elements: [
277+
{ name: 'literal', value: "'apple'" },
278+
{ name: 'literal', value: "'banana'" },
279+
], raw: '$Keys<CONTENTS>'});
280+
});
251281
});

src/utils/getFlowType.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import printValue from './printValue';
1717
import recast from 'recast';
1818
import getTypeAnnotation from '../utils/getTypeAnnotation';
1919
import resolveToValue from '../utils/resolveToValue';
20+
import { resolveObjectExpressionToNameArray } from '../utils/resolveObjectKeysToArray';
2021

2122
const { types: { namedTypes: types } } = recast;
2223

@@ -54,7 +55,32 @@ function getFlowTypeWithRequirements(path: NodePath): FlowTypeDescriptor {
5455
return type;
5556
}
5657

58+
function handleKeysHelper(path: NodePath) {
59+
let value = path.get('typeParameters', 'params', 0);
60+
if (types.TypeofTypeAnnotation.check(value.node)) {
61+
value = value.get('argument', 'id');
62+
} else {
63+
value = value.get('id');
64+
}
65+
const resolvedPath = resolveToValue(value);
66+
if (resolvedPath && types.ObjectExpression.check(resolvedPath.node)) {
67+
const keys = resolveObjectExpressionToNameArray(resolvedPath, true);
68+
69+
if (keys) {
70+
return {
71+
name: 'union',
72+
raw: printValue(path),
73+
elements: keys.map(value => ({ name: 'literal', value })),
74+
};
75+
}
76+
}
77+
}
78+
5779
function handleGenericTypeAnnotation(path: NodePath) {
80+
if (path.node.id.name === '$Keys' && path.node.typeParameters) {
81+
return handleKeysHelper(path);
82+
}
83+
5884
let type;
5985
if (types.QualifiedTypeIdentifier.check(path.node.id)) {
6086
type = handleQualifiedTypeIdentifier(path.get('id'));

src/utils/resolveObjectKeysToArray.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function isObjectKeysCall(node: ASTNode): bool {
3232
node.callee.property.name === 'keys';
3333
}
3434

35-
function resolveObjectExpressionToNameArray(objectExpression: NodePath): ?Array<string> {
35+
export function resolveObjectExpressionToNameArray(objectExpression: NodePath, raw: boolean = false): ?Array<string> {
3636
if (
3737
types.ObjectExpression.check(objectExpression.value) &&
3838
objectExpression.value.properties.every(
@@ -53,7 +53,7 @@ function resolveObjectExpressionToNameArray(objectExpression: NodePath): ?Array<
5353

5454
if (types.Property.check(prop)) {
5555
// Key is either Identifier or Literal
56-
const name = prop.key.name || prop.key.value;
56+
const name = prop.key.name || (raw ? prop.key.raw : prop.key.value);
5757

5858
values.push(name);
5959
} else if (types.SpreadProperty.check(prop)) {

0 commit comments

Comments
 (0)