Skip to content

Commit

Permalink
Support typescript and flow interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed Apr 23, 2019
1 parent ea424ae commit 6747dff
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 9 deletions.
128 changes: 128 additions & 0 deletions src/__tests__/__snapshots__/main-test.js.snap
Expand Up @@ -1176,3 +1176,131 @@ Object {
},
}
`;
exports[`main fixtures processes component "component_23.tsx" without errors 1`] = `
Object {
"description": "This is a typescript class component",
"displayName": "TSComponent",
"methods": Array [],
"props": Object {
"bar": Object {
"description": "Required prop",
"flowType": Object {
"name": "number",
},
"required": true,
},
"baz": Object {
"description": "Complex union prop",
"flowType": Object {
"elements": Array [
Object {
"name": "number",
},
Object {
"name": "signature",
"raw": "{ enter?: number, exit?: number }",
"signature": Object {
"properties": Array [
Object {
"key": "enter",
"value": Object {
"name": "number",
"required": false,
},
},
Object {
"key": "exit",
"value": Object {
"name": "number",
"required": false,
},
},
],
},
"type": "object",
},
Object {
"name": "literal",
"value": "'auto'",
},
],
"name": "union",
"raw": "number | { enter?: number, exit?: number } | 'auto'",
},
"required": true,
},
"foo": Object {
"description": "Optional prop",
"flowType": Object {
"name": "string",
},
"required": false,
},
},
}
`;
exports[`main fixtures processes component "component_24.js" without errors 1`] = `
Object {
"description": "This is a typescript class component",
"displayName": "TSComponent",
"methods": Array [],
"props": Object {
"bar": Object {
"description": "Required prop",
"flowType": Object {
"name": "number",
},
"required": true,
},
"baz": Object {
"description": "Complex union prop",
"flowType": Object {
"elements": Array [
Object {
"name": "number",
},
Object {
"name": "signature",
"raw": "{ enter?: number, exit?: number }",
"signature": Object {
"properties": Array [
Object {
"key": "enter",
"value": Object {
"name": "number",
"required": false,
},
},
Object {
"key": "exit",
"value": Object {
"name": "number",
"required": false,
},
},
],
},
"type": "object",
},
Object {
"name": "literal",
"value": "'auto'",
},
],
"name": "union",
"raw": "number | { enter?: number, exit?: number } | 'auto'",
},
"required": true,
},
"foo": Object {
"description": "Optional prop",
"flowType": Object {
"name": "string",
},
"required": false,
},
},
}
`;
32 changes: 32 additions & 0 deletions src/__tests__/fixtures/component_23.tsx
@@ -0,0 +1,32 @@
/**
* 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.
*
*/

import React, { Component } from 'react';

interface BaseProps {
/** Optional prop */
foo?: string,
/** Required prop */
bar: number
}

type TransitionDuration = number | { enter?: number, exit?: number } | 'auto';

interface Props extends BaseProps {
/** Complex union prop */
baz: TransitionDuration
}

/**
* This is a typescript class component
*/
export default class TSComponent extends Component<Props> {
render() {
return <h1>Hello world</h1>;
}
}
32 changes: 32 additions & 0 deletions src/__tests__/fixtures/component_24.js
@@ -0,0 +1,32 @@
/**
* 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.
*
*/

import React, { Component } from 'react';

interface BaseProps {
/** Optional prop */
foo?: string,
/** Required prop */
bar: number
}

type TransitionDuration = number | { enter?: number, exit?: number } | 'auto';

interface Props extends BaseProps {
/** Complex union prop */
baz: TransitionDuration
}

/**
* This is a flow class component with an interface as props
*/
export default class FlowComponent extends Component<Props> {
render() {
return <h1>Hello world</h1>;
}
}
21 changes: 21 additions & 0 deletions src/utils/getFlowTypeFromReactComponent.js
Expand Up @@ -56,6 +56,18 @@ export function applyToFlowTypeProperties(
path.get('properties').each(propertyPath => callback(propertyPath));
} else if (path.node.members) {
path.get('members').each(propertyPath => callback(propertyPath));
} else if (path.node.type === 'InterfaceDeclaration') {
if (path.node.extends) {
applyExtends(path, callback);
}

path.get('body', 'properties').each(propertyPath => callback(propertyPath));
} else if (path.node.type === 'TSInterfaceDeclaration') {
if (path.node.extends) {
applyExtends(path, callback);
}

path.get('body', 'body').each(propertyPath => callback(propertyPath));
} else if (
path.node.type === 'IntersectionTypeAnnotation' ||
path.node.type === 'TSIntersectionType'
Expand All @@ -72,3 +84,12 @@ export function applyToFlowTypeProperties(
}
}
}

function applyExtends(path, callback) {
path.get('extends').each((extendsPath: NodePath) => {
const resolvedPath = resolveGenericTypeAnnotation(extendsPath);
if (resolvedPath) {
applyToFlowTypeProperties(resolvedPath, callback);
}
});
}
24 changes: 16 additions & 8 deletions src/utils/resolveGenericTypeAnnotation.js
Expand Up @@ -18,21 +18,29 @@ const {

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

if (types.GenericTypeAnnotation.check(typePath.node)) {
typePath = resolveToValue(typePath.get('id'));
if (typePath.node.id) {
idPath = typePath.get('id');
} else if (types.TSTypeReference.check(typePath.node)) {
idPath = typePath.get('typeName');
} else if (types.TSExpressionWithTypeArguments.check(typePath.node)) {
idPath = typePath.get('expression');
}

if (idPath) {
typePath = resolveToValue(idPath);
if (isUnreachableFlowType(typePath)) {
return;
}

return tryResolveGenericTypeAnnotation(typePath.get('right'));
} else if (types.TSTypeReference.check(typePath.node)) {
typePath = resolveToValue(typePath.get('typeName'));
if (isUnreachableFlowType(typePath)) {
return;
if (types.TypeAlias.check(typePath.node)) {
return tryResolveGenericTypeAnnotation(typePath.get('right'));
} else if (types.TSTypeAliasDeclaration.check(typePath.node)) {
return tryResolveGenericTypeAnnotation(typePath.get('typeAnnotation'));
}

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

return typePath;
Expand Down
4 changes: 3 additions & 1 deletion src/utils/resolveToValue.js
Expand Up @@ -52,7 +52,9 @@ function findScopePath(paths: Array<NodePath>, path: NodePath): ?NodePath {
types.ImportNamespaceSpecifier.check(parentPath.node) ||
types.VariableDeclarator.check(parentPath.node) ||
types.TypeAlias.check(parentPath.node) ||
types.TSTypeAliasDeclaration.check(parentPath.node)
types.InterfaceDeclaration.check(parentPath.node) ||
types.TSTypeAliasDeclaration.check(parentPath.node) ||
types.TSInterfaceDeclaration.check(parentPath.node)
) {
resultPath = parentPath;
} else if (types.Property.check(parentPath.node)) {
Expand Down

0 comments on commit 6747dff

Please sign in to comment.