Skip to content

Commit

Permalink
Add forwardRef as a valid component definition (#311)
Browse files Browse the repository at this point in the history
* test: Add failing test case for missing definition on hoc+forwardRef

* feat: Add forwardRef as a valid component definition
  • Loading branch information
eps1lon authored and danez committed Dec 7, 2018
1 parent 0d4f9fd commit b048d58
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 5 deletions.
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));
}

0 comments on commit b048d58

Please sign in to comment.