Skip to content

Commit 8eca876

Browse files
authored
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 f0a3c06 commit 8eca876

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
@@ -246,6 +246,20 @@ describe('flowTypeHandler', () => {
246246
});
247247
});
248248

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

src/handlers/flowTypeHandler.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,14 @@ import getFlowTypeFromReactComponent, {
1717
} from '../utils/getFlowTypeFromReactComponent';
1818
import resolveToValue from '../utils/resolveToValue';
1919
import setPropDescription from '../utils/setPropDescription';
20-
import {
21-
isSupportedUtilityType,
22-
unwrapUtilityType,
23-
} from '../utils/flowUtilityTypes';
20+
import { unwrapUtilityType } from '../utils/flowUtilityTypes';
2421

2522
const {
2623
types: { namedTypes: types },
2724
} = recast;
2825
function setPropDescriptor(documentation: Documentation, path: NodePath): void {
2926
if (types.ObjectTypeSpreadProperty.check(path.node)) {
30-
let argument = path.get('argument');
31-
while (isSupportedUtilityType(argument)) {
32-
argument = unwrapUtilityType(argument);
33-
}
27+
const argument = unwrapUtilityType(path.get('argument'));
3428

3529
if (types.ObjectTypeAnnotation.check(argument.node)) {
3630
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
@@ -21,7 +21,7 @@ const supportedUtilityTypes = new Set(['$Exact', '$ReadOnly']);
2121
export function isSupportedUtilityType(path: NodePath): boolean {
2222
if (types.GenericTypeAnnotation.check(path.node)) {
2323
const idPath = path.get('id');
24-
return Boolean(idPath) && supportedUtilityTypes.has(idPath.node.name);
24+
return !!idPath && supportedUtilityTypes.has(idPath.node.name);
2525
}
2626
return false;
2727
}
@@ -32,5 +32,9 @@ export function isSupportedUtilityType(path: NodePath): boolean {
3232
* $ReadOnly<T> => T
3333
*/
3434
export function unwrapUtilityType(path: NodePath): NodePath {
35-
return path.get('typeParameters', 'params', 0);
35+
while (isSupportedUtilityType(path)) {
36+
path = path.get('typeParameters', 'params', 0);
37+
}
38+
39+
return path;
3640
}

src/utils/getFlowTypeFromReactComponent.js

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,8 @@ import getTypeAnnotation from '../utils/getTypeAnnotation';
1111
import getMemberValuePath from '../utils/getMemberValuePath';
1212
import isReactComponentClass from '../utils/isReactComponentClass';
1313
import isStatelessComponent from '../utils/isStatelessComponent';
14-
import isUnreachableFlowType from '../utils/isUnreachableFlowType';
15-
import recast from 'recast';
16-
import resolveToValue from '../utils/resolveToValue';
17-
import {
18-
isSupportedUtilityType,
19-
unwrapUtilityType,
20-
} from '../utils/flowUtilityTypes';
2114
import resolveGenericTypeAnnotation from '../utils/resolveGenericTypeAnnotation';
2215

23-
const {
24-
types: { namedTypes: types },
25-
} = recast;
26-
2716
/**
2817
* Given an React component (stateless or class) tries to find the
2918
* flow type for the props. If not found or not one of the supported
@@ -56,21 +45,6 @@ export default (path: NodePath): ?NodePath => {
5645
typePath = getTypeAnnotation(param);
5746
}
5847

59-
if (typePath && isSupportedUtilityType(typePath)) {
60-
typePath = unwrapUtilityType(typePath);
61-
} else if (typePath && types.GenericTypeAnnotation.check(typePath.node)) {
62-
typePath = resolveToValue(typePath.get('id'));
63-
if (
64-
!typePath ||
65-
types.Identifier.check(typePath.node) ||
66-
isUnreachableFlowType(typePath)
67-
) {
68-
return;
69-
}
70-
71-
typePath = typePath.get('right');
72-
}
73-
7448
return typePath;
7549
};
7650

src/utils/resolveGenericTypeAnnotation.js

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

1515
const {
1616
types: { namedTypes: types },
1717
} = recast;
1818

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

37-
typePath = typePath.get('right');
38-
}
44+
const typePath = tryResolveGenericTypeAnnotation(path);
45+
46+
if (!typePath || typePath === path) return;
3947

4048
return typePath;
4149
}

0 commit comments

Comments
 (0)