Skip to content
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

Add forwardRef as a valid component definition #311

Merged
merged 2 commits into from
Dec 7, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/__tests__/__snapshots__/main-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -933,3 +933,33 @@ Object {
},
}
`;

exports[`main fixtures processes component "component_18.js" without errors 1`] = `
Object {
"description": "",
"displayName": "UncoloredView",
"methods": Array [],
"props": Object {
"color": Object {
"description": "",
"required": false,
"type": Object {
"name": "custom",
"raw": "PropTypes.string.isRequired",
},
},
"id": Object {
"defaultValue": Object {
"computed": false,
"value": "'test-forward-ref-default'",
},
"description": "",
"required": false,
"type": Object {
"name": "custom",
"raw": "PropTypes.string",
},
},
},
}
`;
22 changes: 22 additions & 0 deletions src/__tests__/fixtures/component_18.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

import React from 'react';
import extendStyles from 'enhancers/extendStyles';

type Props = $ReadOnly<{|
color?: ?string,
|}>;

const ColoredView = React.forwardRef((props: Props, ref) => (
<div ref={ref} style={{backgroundColor: props.color}} />
));

ColoredView.displayName = 'UncoloredView';
ColoredView.propTypes = {
color: PropTypes.string.isRequired,
id: PropTypes.string
}
ColoredView.defaultProps = {
id: 'test-forward-ref-default'
}

module.exports = extendStyles(ColoredView);
6 changes: 5 additions & 1 deletion src/resolver/findExportedComponentDefinition.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

import isExportsOrModuleAssignment from '../utils/isExportsOrModuleAssignment';
import isReactForwardRefCall from '../utils/isReactForwardRefCall';
import isReactComponentClass from '../utils/isReactComponentClass';
import isReactCreateClassCall from '../utils/isReactCreateClassCall';
import isStatelessComponent from '../utils/isStatelessComponent';
Expand All @@ -29,7 +30,8 @@ function isComponentDefinition(path) {
return (
isReactCreateClassCall(path) ||
isReactComponentClass(path) ||
isStatelessComponent(path)
isStatelessComponent(path) ||
isReactForwardRefCall(path)
);
}

Expand All @@ -45,6 +47,8 @@ function resolveDefinition(definition, types) {
return definition;
} else if (isStatelessComponent(definition)) {
return definition;
} else if (isReactForwardRefCall(definition)) {
return definition;
}
return null;
}
Expand Down
6 changes: 4 additions & 2 deletions src/utils/getMemberExpressionValuePath.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import getNameOrValue from './getNameOrValue';
import { String as toString } from './expressionTo';
import isReactForwardRefCall from './isReactForwardRefCall';
import recast from 'recast';

const {
Expand Down Expand Up @@ -40,7 +41,8 @@ function resolveName(path) {
if (
types.FunctionExpression.check(path.node) ||
types.ArrowFunctionExpression.check(path.node) ||
types.TaggedTemplateExpression.check(path.node)
types.TaggedTemplateExpression.check(path.node) ||
isReactForwardRefCall(path)
) {
let currentPath = path;
while (currentPath.parent) {
Expand All @@ -56,7 +58,7 @@ function resolveName(path) {

throw new TypeError(
'Attempted to resolveName for an unsupported path. resolveName accepts a ' +
'VariableDeclaration, FunctionDeclaration, or FunctionExpression. Got "' +
'VariableDeclaration, FunctionDeclaration, FunctionExpression or CallExpression. Got "' +
path.node.type +
'".',
);
Expand Down
6 changes: 4 additions & 2 deletions src/utils/getMemberValuePath.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const POSTPROCESS_MEMBERS = {

const LOOKUP_METHOD = {
[types.ArrowFunctionExpression.name]: getMemberExpressionValuePath,
[types.CallExpression.name]: getMemberExpressionValuePath,
[types.FunctionExpression.name]: getMemberExpressionValuePath,
[types.FunctionDeclaration.name]: getMemberExpressionValuePath,
[types.VariableDeclaration.name]: getMemberExpressionValuePath,
Expand Down Expand Up @@ -62,7 +63,8 @@ function isSupportedDefinitionType({ node }) {
types.VariableDeclaration.check(node) ||
types.ArrowFunctionExpression.check(node) ||
types.FunctionDeclaration.check(node) ||
types.FunctionExpression.check(node)
types.FunctionExpression.check(node) ||
types.CallExpression.check(node)
);
}

Expand All @@ -88,7 +90,7 @@ export default function getMemberValuePath(
'Got unsupported definition type. Definition must be one of ' +
'ObjectExpression, ClassDeclaration, ClassExpression,' +
'VariableDeclaration, ArrowFunctionExpression, FunctionExpression, ' +
'TaggedTemplateExpression or FunctionDeclaration. Got "' +
'TaggedTemplateExpression, FunctionDeclaration or CallExpression. Got "' +
componentDefinition.node.type +
'"' +
'instead.',
Expand Down
34 changes: 34 additions & 0 deletions src/utils/isReactForwardRefCall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
import isReactModuleName from './isReactModuleName';
import match from './match';
import recast from 'recast';
import resolveToModule from './resolveToModule';

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

/**
* Returns true if the expression is a function call of the form
* `React.forwardRef(...)`.
*/
export default function isReactForwardRefCall(path: NodePath): boolean {
if (types.ExpressionStatement.check(path.node)) {
path = path.get('expression');
}

if (!match(path.node, { callee: { property: { name: 'forwardRef' } } })) {
return false;
}
const module = resolveToModule(path.get('callee', 'object'));
return Boolean(module && isReactModuleName(module));
}