From b2638263d3ad49cccbbf2088834f6d6252b4eb9d Mon Sep 17 00:00:00 2001 From: Daniel Tschinder Date: Sun, 22 Oct 2017 22:47:33 +0200 Subject: [PATCH 1/2] Resolve flow $Keys to union type --- src/utils/__tests__/getFlowType-test.js | 15 ++++++++++++++ src/utils/getFlowType.js | 26 +++++++++++++++++++++++++ src/utils/resolveObjectKeysToArray.js | 4 ++-- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/utils/__tests__/getFlowType-test.js b/src/utils/__tests__/getFlowType-test.js index 675c7919164..ef0a06d881c 100644 --- a/src/utils/__tests__/getFlowType-test.js +++ b/src/utils/__tests__/getFlowType-test.js @@ -248,4 +248,19 @@ describe('getFlowType', () => { it(type, () => test(type, types[type])); }); }); + + it('resolves $Keys to union', () => { + var typePath = statement(` + var x: $Keys = 2; + const CONTENTS = { + 'apple': '🍎', + 'banana': '🍌', + }; + `).get('declarations', 0).get('id').get('typeAnnotation').get('typeAnnotation'); + + expect(getFlowType(typePath)).toEqual({name: 'union', elements: [ + { name: 'literal', value: "'apple'" }, + { name: 'literal', value: "'banana'" }, + ], raw: '$Keys'}); + }); }); diff --git a/src/utils/getFlowType.js b/src/utils/getFlowType.js index e6209dc9e2d..a49ad29ac6c 100644 --- a/src/utils/getFlowType.js +++ b/src/utils/getFlowType.js @@ -17,6 +17,7 @@ import printValue from './printValue'; import recast from 'recast'; import getTypeAnnotation from '../utils/getTypeAnnotation'; import resolveToValue from '../utils/resolveToValue'; +import { resolveObjectExpressionToNameArray } from '../utils/resolveObjectKeysToArray'; const { types: { namedTypes: types } } = recast; @@ -54,7 +55,32 @@ function getFlowTypeWithRequirements(path: NodePath): FlowTypeDescriptor { return type; } +function handleKeysHelper(path: NodePath) { + let value = path.get('typeParameters', 'params', 0); + if (types.TypeofTypeAnnotation.check(value.node)) { + value = value.get('argument', 'id'); + } else { + value = value.get('id'); + } + const resolvedPath = resolveToValue(value); + if (resolvedPath && types.ObjectExpression.check(resolvedPath.node)) { + const keys = resolveObjectExpressionToNameArray(resolvedPath, true); + + if (keys) { + return { + name: 'union', + raw: printValue(path), + elements: keys.map(value => ({ name: 'literal', value })), + }; + } + } +} + function handleGenericTypeAnnotation(path: NodePath) { + if (path.node.id.name === '$Keys' && path.node.typeParameters) { + return handleKeysHelper(path); + } + let type; if (types.QualifiedTypeIdentifier.check(path.node.id)) { type = handleQualifiedTypeIdentifier(path.get('id')); diff --git a/src/utils/resolveObjectKeysToArray.js b/src/utils/resolveObjectKeysToArray.js index a9527a648c2..15242e6bd00 100644 --- a/src/utils/resolveObjectKeysToArray.js +++ b/src/utils/resolveObjectKeysToArray.js @@ -32,7 +32,7 @@ function isObjectKeysCall(node: ASTNode): bool { node.callee.property.name === 'keys'; } -function resolveObjectExpressionToNameArray(objectExpression: NodePath): ?Array { +export function resolveObjectExpressionToNameArray(objectExpression: NodePath, raw: boolean = false): ?Array { if ( types.ObjectExpression.check(objectExpression.value) && objectExpression.value.properties.every( @@ -53,7 +53,7 @@ function resolveObjectExpressionToNameArray(objectExpression: NodePath): ?Array< if (types.Property.check(prop)) { // Key is either Identifier or Literal - const name = prop.key.name || prop.key.value; + const name = prop.key.name || (raw ? prop.key.raw : prop.key.value); values.push(name); } else if (types.SpreadProperty.check(prop)) { From 95f52bb221f14f3cbfdbda49264b2f8fd6b35a3a Mon Sep 17 00:00:00 2001 From: Daniel Tschinder Date: Sun, 22 Oct 2017 22:48:39 +0200 Subject: [PATCH 2/2] Add one more test without typeof --- src/utils/__tests__/getFlowType-test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/utils/__tests__/getFlowType-test.js b/src/utils/__tests__/getFlowType-test.js index ef0a06d881c..4e6c9202a00 100644 --- a/src/utils/__tests__/getFlowType-test.js +++ b/src/utils/__tests__/getFlowType-test.js @@ -263,4 +263,19 @@ describe('getFlowType', () => { { name: 'literal', value: "'banana'" }, ], raw: '$Keys'}); }); + + it('resolves $Keys without typeof to union', () => { + var typePath = statement(` + var x: $Keys = 2; + const CONTENTS = { + 'apple': '🍎', + 'banana': '🍌', + }; + `).get('declarations', 0).get('id').get('typeAnnotation').get('typeAnnotation'); + + expect(getFlowType(typePath)).toEqual({name: 'union', elements: [ + { name: 'literal', value: "'apple'" }, + { name: 'literal', value: "'banana'" }, + ], raw: '$Keys'}); + }); });