Skip to content

Fix resolving of more than two Flow Utility types #345

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/handlers/__tests__/__snapshots__/flowTypeHandler-test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`flowTypeHandler does support utility types inline 1`] = `
Object {
"foo": Object {
"description": "",
"flowType": Object {},
"required": true,
},
}
`;
14 changes: 14 additions & 0 deletions src/handlers/__tests__/flowTypeHandler-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,20 @@ describe('flowTypeHandler', () => {
});
});

it('does support utility types inline', () => {
const definition = statement(`
(props: $ReadOnly<Props>) => <div />;

var React = require('React');

type Props = { foo: 'fooValue' };
`).get('expression');

expect(() => flowTypeHandler(documentation, definition)).not.toThrow();

expect(documentation.descriptors).toMatchSnapshot();
});

it('does not support union proptypes', () => {
const definition = statement(`
(props: Props) => <div />;
Expand Down
10 changes: 2 additions & 8 deletions src/handlers/flowTypeHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,14 @@ import getFlowTypeFromReactComponent, {
} from '../utils/getFlowTypeFromReactComponent';
import resolveToValue from '../utils/resolveToValue';
import setPropDescription from '../utils/setPropDescription';
import {
isSupportedUtilityType,
unwrapUtilityType,
} from '../utils/flowUtilityTypes';
import { unwrapUtilityType } from '../utils/flowUtilityTypes';

const {
types: { namedTypes: types },
} = recast;
function setPropDescriptor(documentation: Documentation, path: NodePath): void {
if (types.ObjectTypeSpreadProperty.check(path.node)) {
let argument = path.get('argument');
while (isSupportedUtilityType(argument)) {
argument = unwrapUtilityType(argument);
}
const argument = unwrapUtilityType(path.get('argument'));

if (types.ObjectTypeAnnotation.check(argument.node)) {
applyToFlowTypeProperties(argument, propertyPath => {
Expand Down
84 changes: 84 additions & 0 deletions src/utils/__tests__/flowUtilityTypes-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

/*global describe, it, expect*/

import { unwrapUtilityType, isSupportedUtilityType } from '../flowUtilityTypes';

import { statement } from '../../../tests/utils';

describe('flowTypeUtilities', () => {
describe('unwrapUtilityType', () => {
it('correctly unwraps', () => {
const def = statement(`
type A = $ReadOnly<{ foo: string }>
`);

expect(unwrapUtilityType(def.get('right'))).toBe(
def.get('right', 'typeParameters', 'params', 0),
);
});

it('correctly unwraps double', () => {
const def = statement(`
type A = $ReadOnly<$ReadOnly<{ foo: string }>>
`);

expect(unwrapUtilityType(def.get('right'))).toBe(
def.get(
'right',
'typeParameters',
'params',
0,
'typeParameters',
'params',
0,
),
);
});

it('correctly unwraps triple', () => {
const def = statement(`
type A = $ReadOnly<$ReadOnly<$ReadOnly<{ foo: string }>>>
`);

expect(unwrapUtilityType(def.get('right'))).toBe(
def.get(
'right',
'typeParameters',
'params',
0,
'typeParameters',
'params',
0,
'typeParameters',
'params',
0,
),
);
});
});

describe('isSupportedUtilityType', () => {
it('correctly returns true for valid type', () => {
const def = statement(`
type A = $Exact<{ foo: string }>
`);

expect(isSupportedUtilityType(def.get('right'))).toBe(true);
});

it('correctly returns false for invalid type', () => {
const def = statement(`
type A = $Homer<{ foo: string }>
`);

expect(isSupportedUtilityType(def.get('right'))).toBe(false);
});
});
});
8 changes: 6 additions & 2 deletions src/utils/flowUtilityTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const supportedUtilityTypes = new Set(['$Exact', '$ReadOnly']);
export function isSupportedUtilityType(path: NodePath): boolean {
if (types.GenericTypeAnnotation.check(path.node)) {
const idPath = path.get('id');
return Boolean(idPath) && supportedUtilityTypes.has(idPath.node.name);
return !!idPath && supportedUtilityTypes.has(idPath.node.name);
}
return false;
}
Expand All @@ -32,5 +32,9 @@ export function isSupportedUtilityType(path: NodePath): boolean {
* $ReadOnly<T> => T
*/
export function unwrapUtilityType(path: NodePath): NodePath {
return path.get('typeParameters', 'params', 0);
while (isSupportedUtilityType(path)) {
path = path.get('typeParameters', 'params', 0);
}

return path;
}
26 changes: 0 additions & 26 deletions src/utils/getFlowTypeFromReactComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,8 @@ import getTypeAnnotation from '../utils/getTypeAnnotation';
import getMemberValuePath from '../utils/getMemberValuePath';
import isReactComponentClass from '../utils/isReactComponentClass';
import isStatelessComponent from '../utils/isStatelessComponent';
import isUnreachableFlowType from '../utils/isUnreachableFlowType';
import recast from 'recast';
import resolveToValue from '../utils/resolveToValue';
import {
isSupportedUtilityType,
unwrapUtilityType,
} from '../utils/flowUtilityTypes';
import resolveGenericTypeAnnotation from '../utils/resolveGenericTypeAnnotation';

const {
types: { namedTypes: types },
} = recast;

/**
* Given an React component (stateless or class) tries to find the
* flow type for the props. If not found or not one of the supported
Expand Down Expand Up @@ -56,21 +45,6 @@ export default (path: NodePath): ?NodePath => {
typePath = getTypeAnnotation(param);
}

if (typePath && isSupportedUtilityType(typePath)) {
typePath = unwrapUtilityType(typePath);
} else if (typePath && types.GenericTypeAnnotation.check(typePath.node)) {
typePath = resolveToValue(typePath.get('id'));
if (
!typePath ||
types.Identifier.check(typePath.node) ||
isUnreachableFlowType(typePath)
) {
return;
}

typePath = typePath.get('right');
}

return typePath;
};

Expand Down
34 changes: 21 additions & 13 deletions src/utils/resolveGenericTypeAnnotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,40 @@
import isUnreachableFlowType from '../utils/isUnreachableFlowType';
import recast from 'recast';
import resolveToValue from '../utils/resolveToValue';
import { isSupportedUtilityType, unwrapUtilityType } from './flowUtilityTypes';
import { unwrapUtilityType } from './flowUtilityTypes';

const {
types: { namedTypes: types },
} = recast;

function tryResolveGenericTypeAnnotation(path: NodePath): ?NodePath {
let typePath = unwrapUtilityType(path);

if (types.GenericTypeAnnotation.check(typePath.node)) {
typePath = resolveToValue(typePath.get('id'));
if (isUnreachableFlowType(typePath)) {
return;
}

return tryResolveGenericTypeAnnotation(typePath.get('right'));
}

return typePath;
}

/**
* Given an React component (stateless or class) tries to find the
* flow type for the props. If not found or not one of the supported
* component types returns null.
* component types returns undefined.
*/
export default function resolveGenericTypeAnnotation(
path: NodePath,
): ?NodePath {
// If the node doesn't have types or properties, try to get the type.
let typePath: ?NodePath;
if (path && isSupportedUtilityType(path)) {
typePath = unwrapUtilityType(path);
} else if (path && types.GenericTypeAnnotation.check(path.node)) {
typePath = resolveToValue(path.get('id'));
if (isUnreachableFlowType(typePath)) {
return;
}
if (!path) return;

typePath = typePath.get('right');
}
const typePath = tryResolveGenericTypeAnnotation(path);

if (!typePath || typePath === path) return;

return typePath;
}