Skip to content

Commit 39a4096

Browse files
authored
fix: Correctly unwrap utility types when resolving type spreads (#280)
1 parent 91c8936 commit 39a4096

File tree

6 files changed

+193
-44
lines changed

6 files changed

+193
-44
lines changed

src/__tests__/__snapshots__/main-test.js.snap

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,3 +631,66 @@ Object {
631631
},
632632
}
633633
`;
634+
635+
exports[`main fixtures processes component "component_15.js" without errors 1`] = `
636+
Object {
637+
"composes": Array [
638+
"BarProps",
639+
"BarProps2",
640+
"BarProps3",
641+
],
642+
"description": "",
643+
"displayName": "Foo",
644+
"methods": Array [],
645+
"props": Object {
646+
"other": Object {
647+
"description": "",
648+
"flowType": Object {
649+
"name": "literal",
650+
"value": "'a'",
651+
},
652+
"required": true,
653+
},
654+
"other2": Object {
655+
"description": "",
656+
"flowType": Object {
657+
"name": "literal",
658+
"value": "'b'",
659+
},
660+
"required": true,
661+
},
662+
"other3": Object {
663+
"description": "",
664+
"flowType": Object {
665+
"name": "literal",
666+
"value": "'c'",
667+
},
668+
"required": true,
669+
},
670+
"other4": Object {
671+
"description": "",
672+
"flowType": Object {
673+
"name": "literal",
674+
"value": "'g'",
675+
},
676+
"required": true,
677+
},
678+
"other5": Object {
679+
"description": "",
680+
"flowType": Object {
681+
"name": "literal",
682+
"value": "'f'",
683+
},
684+
"required": true,
685+
},
686+
"somePropOverride": Object {
687+
"description": "",
688+
"flowType": Object {
689+
"name": "literal",
690+
"value": "'baz'",
691+
},
692+
"required": true,
693+
},
694+
},
695+
}
696+
`;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type {Props as BarProps} from 'Bar.react';
2+
3+
const Bar = require('Bar.react');
4+
5+
type A = { other3: 'c' };
6+
type B = $Exact<{ other4: 'g' }>;
7+
type C = $Exact<$ReadOnly<{ other5: 'f' }>>;
8+
9+
type Props = {|
10+
...$Exact<BarProps>,
11+
...$Exact<$ReadOnly<BarProps2>>,
12+
...BarProps3,
13+
...{ other: 'a' },
14+
...$Exact<{ other2: 'b' }>,
15+
...A,
16+
...B,
17+
...C,
18+
somePropOverride: 'baz',
19+
|};
20+
21+
export default class Foo extends React.Component<Props> {
22+
render() {
23+
return <Bar {...this.props} />;
24+
}
25+
}

src/handlers/flowTypeHandler.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,29 @@ 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';
2327

2428
const {
2529
types: { namedTypes: types },
2630
} = recast;
2731
function setPropDescriptor(documentation: Documentation, path: NodePath): void {
2832
if (types.ObjectTypeSpreadProperty.check(path.node)) {
29-
const name = path
30-
.get('argument')
31-
.get('id')
32-
.get('name');
33+
let argument = path.get('argument');
34+
while (isSupportedUtilityType(argument)) {
35+
argument = unwrapUtilityType(argument);
36+
}
37+
38+
if (types.ObjectTypeAnnotation.check(argument.node)) {
39+
applyToFlowTypeProperties(argument, propertyPath => {
40+
setPropDescriptor(documentation, propertyPath);
41+
});
42+
return;
43+
}
44+
45+
const name = argument.get('id').get('name');
3346
const resolvedPath = resolveToValue(name);
3447

3548
if (resolvedPath && types.TypeAlias.check(resolvedPath.node)) {

src/utils/flowUtilityTypes.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @flow
10+
*
11+
*/
12+
import recast from 'recast';
13+
14+
const {
15+
types: { namedTypes: types },
16+
} = recast;
17+
18+
const supportedUtilityTypes = new Set(['$Exact', '$ReadOnly']);
19+
20+
/**
21+
* See `supportedUtilityTypes` for which types are supported and
22+
* https://flow.org/en/docs/types/utilities/ for which types are available.
23+
*/
24+
export function isSupportedUtilityType(path: NodePath): boolean {
25+
if (types.GenericTypeAnnotation.check(path.node)) {
26+
const idPath = path.get('id');
27+
return Boolean(idPath) && supportedUtilityTypes.has(idPath.node.name);
28+
}
29+
return false;
30+
}
31+
32+
/**
33+
* Unwraps well known utility types. For example:
34+
*
35+
* $ReadOnly<T> => T
36+
*/
37+
export function unwrapUtilityType(path: NodePath): NodePath {
38+
return path.get('typeParameters', 'params', 0);
39+
}

src/utils/getFlowTypeFromReactComponent.js

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@ import isStatelessComponent from '../utils/isStatelessComponent';
1717
import isUnreachableFlowType from '../utils/isUnreachableFlowType';
1818
import recast from 'recast';
1919
import resolveToValue from '../utils/resolveToValue';
20+
import {
21+
isSupportedUtilityType,
22+
unwrapUtilityType,
23+
} from '../utils/flowUtilityTypes';
24+
import resolveGenericTypeAnnotation from '../utils/resolveGenericTypeAnnotation';
2025

2126
const {
2227
types: { namedTypes: types },
2328
} = recast;
2429

25-
const supportedUtilityTypes = new Set(['$Exact', '$ReadOnly']);
26-
2730
/**
2831
* Given an React component (stateless or class) tries to find the
2932
* flow type for the props. If not found or not one of the supported
@@ -93,41 +96,3 @@ export function applyToFlowTypeProperties(
9396
}
9497
}
9598
}
96-
97-
function resolveGenericTypeAnnotation(path: NodePath): ?NodePath {
98-
// If the node doesn't have types or properties, try to get the type.
99-
let typePath: ?NodePath;
100-
if (path && isSupportedUtilityType(path)) {
101-
typePath = unwrapUtilityType(path);
102-
} else if (path && types.GenericTypeAnnotation.check(path.node)) {
103-
typePath = resolveToValue(path.get('id'));
104-
if (isUnreachableFlowType(typePath)) {
105-
return;
106-
}
107-
108-
typePath = typePath.get('right');
109-
}
110-
111-
return typePath;
112-
}
113-
114-
/**
115-
* See `supportedUtilityTypes` for which types are supported and
116-
* https://flow.org/en/docs/types/utilities/ for which types are available.
117-
*/
118-
function isSupportedUtilityType(path: NodePath): boolean {
119-
if (types.GenericTypeAnnotation.check(path.node)) {
120-
const idPath = path.get('id');
121-
return Boolean(idPath) && supportedUtilityTypes.has(idPath.node.name);
122-
}
123-
return false;
124-
}
125-
126-
/**
127-
* Unwraps well known utility types. For example:
128-
*
129-
* $ReadOnly<T> => T
130-
*/
131-
function unwrapUtilityType(path: NodePath): ?NodePath {
132-
return path.get('typeParameters', 'params', 0);
133-
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @flow
10+
*
11+
*/
12+
13+
import isUnreachableFlowType from '../utils/isUnreachableFlowType';
14+
import recast from 'recast';
15+
import resolveToValue from '../utils/resolveToValue';
16+
import { isSupportedUtilityType, unwrapUtilityType } from './flowUtilityTypes';
17+
18+
const {
19+
types: { namedTypes: types },
20+
} = recast;
21+
22+
/**
23+
* Given an React component (stateless or class) tries to find the
24+
* flow type for the props. If not found or not one of the supported
25+
* component types returns null.
26+
*/
27+
export default function resolveGenericTypeAnnotation(
28+
path: NodePath,
29+
): ?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+
}
39+
40+
typePath = typePath.get('right');
41+
}
42+
43+
return typePath;
44+
}

0 commit comments

Comments
 (0)