Skip to content

Commit 3a8b8b6

Browse files
committed
feat(flow): Add support for $Exact<> and $ReadOnly<>
Flow proptype definitions may be "wrapped" in `$Exact` or `$ReadOnly` and we should support that.
1 parent 48b6068 commit 3a8b8b6

File tree

2 files changed

+55
-3
lines changed

2 files changed

+55
-3
lines changed

src/handlers/__tests__/flowTypeHandler-test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,30 @@ describe('flowTypeHandler', () => {
139139
},
140140
});
141141
});
142+
143+
describe('special generic type annotations', () => {
144+
['$ReadOnly', '$Exact'].forEach(annotation => {
145+
it(`unwraps ${annotation}<...>`, () => {
146+
var flowTypesSrc = `
147+
${annotation}<{
148+
foo: string | number,
149+
}>
150+
`;
151+
152+
var definition = getSrc(flowTypesSrc);
153+
154+
flowTypeHandler(documentation, definition);
155+
156+
expect(documentation.descriptors).toEqual({
157+
foo: {
158+
flowType: {},
159+
required: true,
160+
description: '',
161+
},
162+
});
163+
});
164+
});
165+
});
142166
}
143167

144168
describe('TypeAlias', () => {

src/utils/getFlowTypeFromReactComponent.js

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import isUnreachableFlowType from '../utils/isUnreachableFlowType';
1818
import recast from 'recast';
1919
import resolveToValue from '../utils/resolveToValue';
2020

21-
var {types: {namedTypes: types}} = recast;
21+
const {types: {namedTypes: types}} = recast;
22+
23+
const supportedUtilityTypes = new Set(['$Exact', '$ReadOnly']);
2224

2325
/**
2426
* Given an React component (stateless or class) tries to find the
@@ -52,7 +54,9 @@ export default (path: NodePath): ?NodePath => {
5254
typePath = getTypeAnnotation(param);
5355
}
5456

55-
if (typePath && types.GenericTypeAnnotation.check(typePath.node)) {
57+
if (typePath && isSupportedUtilityType(typePath)) {
58+
typePath = unwrapUtilityType(typePath);
59+
} else if (typePath && types.GenericTypeAnnotation.check(typePath.node)) {
5660
typePath = resolveToValue(typePath.get('id'));
5761
if (
5862
!typePath ||
@@ -93,7 +97,9 @@ export function applyToFlowTypeProperties(
9397
function resolveGenericTypeAnnotation(path: NodePath): ?NodePath {
9498
// If the node doesn't have types or properties, try to get the type.
9599
let typePath: ?NodePath;
96-
if (path && types.GenericTypeAnnotation.check(path.node)) {
100+
if (path && isSupportedUtilityType(path)) {
101+
typePath = unwrapUtilityType(path);
102+
} else if(path && types.GenericTypeAnnotation.check(path.node)) {
97103
typePath = resolveToValue(path.get('id'));
98104
if (isUnreachableFlowType(typePath)) {
99105
return;
@@ -104,3 +110,25 @@ function resolveGenericTypeAnnotation(path: NodePath): ?NodePath {
104110

105111
return typePath;
106112
}
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) &&
122+
supportedUtilityTypes.has(idPath.node.name);
123+
}
124+
return false;
125+
}
126+
127+
/**
128+
* Unwraps well known utility types. For example:
129+
*
130+
* $ReadOnly<T> => T
131+
*/
132+
function unwrapUtilityType(path: NodePath): ?NodePath {
133+
return path.get('typeParameters', 'params', 0);
134+
}

0 commit comments

Comments
 (0)