Skip to content

Commit

Permalink
fix: type inference on forwardRef components (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense authored and danez committed Oct 16, 2019
1 parent e05219b commit 8f8d09b
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 25 deletions.
27 changes: 26 additions & 1 deletion src/__tests__/__snapshots__/main-test.js.snap
Expand Up @@ -982,7 +982,11 @@ Object {
"props": Object {
"color": Object {
"description": "",
"required": true,
"flowType": Object {
"name": "string",
"nullable": true,
},
"required": false,
"type": Object {
"name": "string",
},
Expand Down Expand Up @@ -1421,3 +1425,24 @@ Object {
},
}
`;
exports[`main fixtures processes component "component_28.tsx" without errors 1`] = `
Object {
"description": "Example component description",
"displayName": "ABC",
"methods": Array [],
"props": Object {
"foo": Object {
"defaultValue": Object {
"computed": false,
"value": "true",
},
"description": "Example prop description",
"required": false,
"tsType": Object {
"name": "boolean",
},
},
},
}
`;
27 changes: 27 additions & 0 deletions src/__tests__/fixtures/component_28.tsx
@@ -0,0 +1,27 @@
/**
* 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 from 'react';

interface Props {
/**
* Example prop description
*/
foo: boolean;
}

/**
* Example component description
*/
const Component = React.forwardRef(({ foo = true }: Props, ref: any) => {
return <div />;
})

Component.displayName = 'ABC';

export default Component;
14 changes: 7 additions & 7 deletions src/__tests__/main-test.js
Expand Up @@ -8,7 +8,7 @@

import fs from 'fs';
import path from 'path';
import { parse, handlers } from '../main';
import { handlers, parse } from '../main';
import { ERROR_MISSING_DEFINITION } from '../parse';

describe('main', () => {
Expand Down Expand Up @@ -45,7 +45,7 @@ describe('main', () => {

describe('React.createClass', () => {
test(`
var React = require("React");
var React = require("react");
var PropTypes = React.PropTypes;
var defaultProps = {
Expand Down Expand Up @@ -74,7 +74,7 @@ describe('main', () => {

describe('Class definition', () => {
test(`
const React = require("React");
const React = require("react");
const PropTypes = React.PropTypes;
const defaultProps = {
Expand All @@ -101,7 +101,7 @@ describe('main', () => {

describe('Stateless Component definition: ArrowFunctionExpression', () => {
test(`
import React, {PropTypes} from "React";
import React, {PropTypes} from "react";
const defaultProps = {
foo: true,
Expand All @@ -127,7 +127,7 @@ describe('main', () => {

describe('Stateless Component definition: FunctionDeclaration', () => {
test(`
import React, {PropTypes} from "React";
import React, {PropTypes} from "react";
const defaultProps = {
foo: true,
Expand Down Expand Up @@ -156,7 +156,7 @@ describe('main', () => {

describe('Stateless Component definition: FunctionExpression', () => {
test(`
import React, {PropTypes} from "React";
import React, {PropTypes} from "react";
const defaultProps = {
foo: true,
Expand Down Expand Up @@ -186,7 +186,7 @@ describe('main', () => {
describe('Stateless Component definition', () => {
it('is not so greedy', () => {
const source = `
import React, {PropTypes} from "React";
import React, {PropTypes} from "react";
/**
* Example component description
Expand Down
10 changes: 5 additions & 5 deletions src/handlers/flowTypeHandler.js
Expand Up @@ -8,17 +8,17 @@
*/

import types from 'ast-types';
import type Documentation from '../Documentation';
import { unwrapUtilityType } from '../utils/flowUtilityTypes';
import getFlowType from '../utils/getFlowType';
import getTSType from '../utils/getTSType';
import getPropertyName from '../utils/getPropertyName';
import getFlowTypeFromReactComponent, {
applyToFlowTypeProperties,
} from '../utils/getFlowTypeFromReactComponent';
import getPropertyName from '../utils/getPropertyName';
import getTSType from '../utils/getTSType';
import { type TypeParameters } from '../utils/getTypeParameters';
import resolveToValue from '../utils/resolveToValue';
import setPropDescription from '../utils/setPropDescription';
import { unwrapUtilityType } from '../utils/flowUtilityTypes';
import { type TypeParameters } from '../utils/getTypeParameters';
import type Documentation from '../Documentation';

const { namedTypes: t } = types;

Expand Down
36 changes: 24 additions & 12 deletions src/utils/getFlowTypeFromReactComponent.js
Expand Up @@ -8,22 +8,30 @@
*/

import type Documentation from '../Documentation';
import getTypeAnnotation from '../utils/getTypeAnnotation';
import getMemberValuePath from '../utils/getMemberValuePath';
import isReactComponentClass from '../utils/isReactComponentClass';
import isStatelessComponent from '../utils/isStatelessComponent';
import resolveGenericTypeAnnotation from '../utils/resolveGenericTypeAnnotation';
import getTypeParameters, {
type TypeParameters,
} from '../utils/getTypeParameters';
import getMemberValuePath from './getMemberValuePath';
import getTypeAnnotation from './getTypeAnnotation';
import getTypeParameters, { type TypeParameters } from './getTypeParameters';
import isReactComponentClass from './isReactComponentClass';
import isReactForwardRefCall from './isReactForwardRefCall';
import resolveGenericTypeAnnotation from './resolveGenericTypeAnnotation';
import resolveToValue from './resolveToValue';

function getStatelessPropsPath(componentDefinition): NodePath {
const value = resolveToValue(componentDefinition);
if (isReactForwardRefCall(value)) {
const inner = value.get('arguments', 0);
return inner.get('params', 0);
}
return value.get('params', 0);
}

/**
* 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.
*/
export default (path: NodePath): ?NodePath => {
let typePath: ?NodePath;
let typePath: ?NodePath = null;

if (isReactComponentClass(path)) {
const superTypes = path.get('superTypeParameters');
Expand All @@ -43,10 +51,14 @@ export default (path: NodePath): ?NodePath => {

typePath = getTypeAnnotation(propsMemberPath.parentPath);
}
} else if (isStatelessComponent(path)) {
const param = path.get('params').get(0);

typePath = getTypeAnnotation(param);
return typePath;
}

const propsParam = getStatelessPropsPath(path);

if (propsParam) {
typePath = getTypeAnnotation(propsParam);
}

return typePath;
Expand Down

0 comments on commit 8f8d09b

Please sign in to comment.