diff --git a/src/__tests__/__snapshots__/main-test.js.snap b/src/__tests__/__snapshots__/main-test.js.snap index 9a90c78330c..a8ee25e4442 100644 --- a/src/__tests__/__snapshots__/main-test.js.snap +++ b/src/__tests__/__snapshots__/main-test.js.snap @@ -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, + }, + }, +} +`; diff --git a/src/__tests__/fixtures/component_23.tsx b/src/__tests__/fixtures/component_23.tsx new file mode 100644 index 00000000000..7143b4680d3 --- /dev/null +++ b/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 { + render() { + return

Hello world

; + } +} diff --git a/src/__tests__/fixtures/component_24.js b/src/__tests__/fixtures/component_24.js new file mode 100644 index 00000000000..7143b4680d3 --- /dev/null +++ b/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 typescript class component + */ +export default class TSComponent extends Component { + render() { + return

Hello world

; + } +} diff --git a/src/utils/getFlowTypeFromReactComponent.js b/src/utils/getFlowTypeFromReactComponent.js index 0b6e49ca1bf..97d5913687e 100644 --- a/src/utils/getFlowTypeFromReactComponent.js +++ b/src/utils/getFlowTypeFromReactComponent.js @@ -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' @@ -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); + } + }); +} diff --git a/src/utils/resolveGenericTypeAnnotation.js b/src/utils/resolveGenericTypeAnnotation.js index b93997348cc..9d7934c79fc 100644 --- a/src/utils/resolveGenericTypeAnnotation.js +++ b/src/utils/resolveGenericTypeAnnotation.js @@ -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; diff --git a/src/utils/resolveToValue.js b/src/utils/resolveToValue.js index 398797df103..4dc3f290b93 100644 --- a/src/utils/resolveToValue.js +++ b/src/utils/resolveToValue.js @@ -52,7 +52,9 @@ function findScopePath(paths: Array, 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)) {