Skip to content

Commit 3152057

Browse files
committed
Fix resolving of more than two Flow Utility types (#345)
* Refactor code to do unwrapping of flow utility types in one place * Simplify unwrapping of utility types * Restore old functionality of returning falsey value
1 parent a0824f0 commit 3152057

File tree

7 files changed

+138
-49
lines changed

7 files changed

+138
-49
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`flowTypeHandler does support utility types inline 1`] = `
4+
Object {
5+
"foo": Object {
6+
"description": "",
7+
"flowType": Object {},
8+
"required": true,
9+
},
10+
}
11+
`;

src/handlers/__tests__/flowTypeHandler-test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,20 @@ describe('flowTypeHandler', () => {
248248
});
249249
});
250250

251+
it('does support utility types inline', () => {
252+
const definition = statement(`
253+
(props: $ReadOnly<Props>) => <div />;
254+
255+
var React = require('React');
256+
257+
type Props = { foo: 'fooValue' };
258+
`).get('expression');
259+
260+
expect(() => flowTypeHandler(documentation, definition)).not.toThrow();
261+
262+
expect(documentation.descriptors).toMatchSnapshot();
263+
});
264+
251265
it('does not support union proptypes', () => {
252266
const definition = statement(`
253267
(props: Props) => <div />;

src/handlers/flowTypeHandler.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,14 @@ import getFlowTypeFromReactComponent, {
2020
} from '../utils/getFlowTypeFromReactComponent';
2121
import resolveToValue from '../utils/resolveToValue';
2222
import setPropDescription from '../utils/setPropDescription';
23-
import {
24-
isSupportedUtilityType,
25-
unwrapUtilityType,
26-
} from '../utils/flowUtilityTypes';
23+
import { unwrapUtilityType } from '../utils/flowUtilityTypes';
2724

2825
const {
2926
types: { namedTypes: types },
3027
} = recast;
3128
function setPropDescriptor(documentation: Documentation, path: NodePath): void {
3229
if (types.ObjectTypeSpreadProperty.check(path.node)) {
33-
let argument = path.get('argument');
34-
while (isSupportedUtilityType(argument)) {
35-
argument = unwrapUtilityType(argument);
36-
}
30+
const argument = unwrapUtilityType(path.get('argument'));
3731

3832
if (types.ObjectTypeAnnotation.check(argument.node)) {
3933
applyToFlowTypeProperties(argument, propertyPath => {
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
/*global describe, it, expect*/
10+
11+
import { unwrapUtilityType, isSupportedUtilityType } from '../flowUtilityTypes';
12+
13+
import { statement } from '../../../tests/utils';
14+
15+
describe('flowTypeUtilities', () => {
16+
describe('unwrapUtilityType', () => {
17+
it('correctly unwraps', () => {
18+
const def = statement(`
19+
type A = $ReadOnly<{ foo: string }>
20+
`);
21+
22+
expect(unwrapUtilityType(def.get('right'))).toBe(
23+
def.get('right', 'typeParameters', 'params', 0),
24+
);
25+
});
26+
27+
it('correctly unwraps double', () => {
28+
const def = statement(`
29+
type A = $ReadOnly<$ReadOnly<{ foo: string }>>
30+
`);
31+
32+
expect(unwrapUtilityType(def.get('right'))).toBe(
33+
def.get(
34+
'right',
35+
'typeParameters',
36+
'params',
37+
0,
38+
'typeParameters',
39+
'params',
40+
0,
41+
),
42+
);
43+
});
44+
45+
it('correctly unwraps triple', () => {
46+
const def = statement(`
47+
type A = $ReadOnly<$ReadOnly<$ReadOnly<{ foo: string }>>>
48+
`);
49+
50+
expect(unwrapUtilityType(def.get('right'))).toBe(
51+
def.get(
52+
'right',
53+
'typeParameters',
54+
'params',
55+
0,
56+
'typeParameters',
57+
'params',
58+
0,
59+
'typeParameters',
60+
'params',
61+
0,
62+
),
63+
);
64+
});
65+
});
66+
67+
describe('isSupportedUtilityType', () => {
68+
it('correctly returns true for valid type', () => {
69+
const def = statement(`
70+
type A = $Exact<{ foo: string }>
71+
`);
72+
73+
expect(isSupportedUtilityType(def.get('right'))).toBe(true);
74+
});
75+
76+
it('correctly returns false for invalid type', () => {
77+
const def = statement(`
78+
type A = $Homer<{ foo: string }>
79+
`);
80+
81+
expect(isSupportedUtilityType(def.get('right'))).toBe(false);
82+
});
83+
});
84+
});

src/utils/flowUtilityTypes.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const supportedUtilityTypes = new Set(['$Exact', '$ReadOnly']);
2424
export function isSupportedUtilityType(path: NodePath): boolean {
2525
if (types.GenericTypeAnnotation.check(path.node)) {
2626
const idPath = path.get('id');
27-
return Boolean(idPath) && supportedUtilityTypes.has(idPath.node.name);
27+
return !!idPath && supportedUtilityTypes.has(idPath.node.name);
2828
}
2929
return false;
3030
}
@@ -35,5 +35,9 @@ export function isSupportedUtilityType(path: NodePath): boolean {
3535
* $ReadOnly<T> => T
3636
*/
3737
export function unwrapUtilityType(path: NodePath): NodePath {
38-
return path.get('typeParameters', 'params', 0);
38+
while (isSupportedUtilityType(path)) {
39+
path = path.get('typeParameters', 'params', 0);
40+
}
41+
42+
return path;
3943
}

src/utils/getFlowTypeFromReactComponent.js

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,8 @@ import getTypeAnnotation from '../utils/getTypeAnnotation';
1414
import getMemberValuePath from '../utils/getMemberValuePath';
1515
import isReactComponentClass from '../utils/isReactComponentClass';
1616
import isStatelessComponent from '../utils/isStatelessComponent';
17-
import isUnreachableFlowType from '../utils/isUnreachableFlowType';
18-
import recast from 'recast';
19-
import resolveToValue from '../utils/resolveToValue';
20-
import {
21-
isSupportedUtilityType,
22-
unwrapUtilityType,
23-
} from '../utils/flowUtilityTypes';
2417
import resolveGenericTypeAnnotation from '../utils/resolveGenericTypeAnnotation';
2518

26-
const {
27-
types: { namedTypes: types },
28-
} = recast;
29-
3019
/**
3120
* Given an React component (stateless or class) tries to find the
3221
* flow type for the props. If not found or not one of the supported
@@ -59,21 +48,6 @@ export default (path: NodePath): ?NodePath => {
5948
typePath = getTypeAnnotation(param);
6049
}
6150

62-
if (typePath && isSupportedUtilityType(typePath)) {
63-
typePath = unwrapUtilityType(typePath);
64-
} else if (typePath && types.GenericTypeAnnotation.check(typePath.node)) {
65-
typePath = resolveToValue(typePath.get('id'));
66-
if (
67-
!typePath ||
68-
types.Identifier.check(typePath.node) ||
69-
isUnreachableFlowType(typePath)
70-
) {
71-
return;
72-
}
73-
74-
typePath = typePath.get('right');
75-
}
76-
7751
return typePath;
7852
};
7953

src/utils/resolveGenericTypeAnnotation.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,40 @@
1313
import isUnreachableFlowType from '../utils/isUnreachableFlowType';
1414
import recast from 'recast';
1515
import resolveToValue from '../utils/resolveToValue';
16-
import { isSupportedUtilityType, unwrapUtilityType } from './flowUtilityTypes';
16+
import { unwrapUtilityType } from './flowUtilityTypes';
1717

1818
const {
1919
types: { namedTypes: types },
2020
} = recast;
2121

22+
function tryResolveGenericTypeAnnotation(path: NodePath): ?NodePath {
23+
let typePath = unwrapUtilityType(path);
24+
25+
if (types.GenericTypeAnnotation.check(typePath.node)) {
26+
typePath = resolveToValue(typePath.get('id'));
27+
if (isUnreachableFlowType(typePath)) {
28+
return;
29+
}
30+
31+
return tryResolveGenericTypeAnnotation(typePath.get('right'));
32+
}
33+
34+
return typePath;
35+
}
36+
2237
/**
2338
* Given an React component (stateless or class) tries to find the
2439
* flow type for the props. If not found or not one of the supported
25-
* component types returns null.
40+
* component types returns undefined.
2641
*/
2742
export default function resolveGenericTypeAnnotation(
2843
path: NodePath,
2944
): ?NodePath {
30-
// If the node doesn't have types or properties, try to get the type.
31-
let typePath: ?NodePath;
32-
if (path && isSupportedUtilityType(path)) {
33-
typePath = unwrapUtilityType(path);
34-
} else if (path && types.GenericTypeAnnotation.check(path.node)) {
35-
typePath = resolveToValue(path.get('id'));
36-
if (isUnreachableFlowType(typePath)) {
37-
return;
38-
}
45+
if (!path) return;
3946

40-
typePath = typePath.get('right');
41-
}
47+
const typePath = tryResolveGenericTypeAnnotation(path);
48+
49+
if (!typePath || typePath === path) return;
4250

4351
return typePath;
4452
}

0 commit comments

Comments
 (0)