Skip to content

Commit b048d58

Browse files
eps1londanez
authored andcommitted
Add forwardRef as a valid component definition (#311)
* test: Add failing test case for missing definition on hoc+forwardRef * feat: Add forwardRef as a valid component definition
1 parent 0d4f9fd commit b048d58

File tree

6 files changed

+99
-5
lines changed

6 files changed

+99
-5
lines changed

src/__tests__/__snapshots__/main-test.js.snap

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -933,3 +933,33 @@ Object {
933933
},
934934
}
935935
`;
936+
937+
exports[`main fixtures processes component "component_18.js" without errors 1`] = `
938+
Object {
939+
"description": "",
940+
"displayName": "UncoloredView",
941+
"methods": Array [],
942+
"props": Object {
943+
"color": Object {
944+
"description": "",
945+
"required": false,
946+
"type": Object {
947+
"name": "custom",
948+
"raw": "PropTypes.string.isRequired",
949+
},
950+
},
951+
"id": Object {
952+
"defaultValue": Object {
953+
"computed": false,
954+
"value": "'test-forward-ref-default'",
955+
},
956+
"description": "",
957+
"required": false,
958+
"type": Object {
959+
"name": "custom",
960+
"raw": "PropTypes.string",
961+
},
962+
},
963+
},
964+
}
965+
`;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
import React from 'react';
3+
import extendStyles from 'enhancers/extendStyles';
4+
5+
type Props = $ReadOnly<{|
6+
color?: ?string,
7+
|}>;
8+
9+
const ColoredView = React.forwardRef((props: Props, ref) => (
10+
<div ref={ref} style={{backgroundColor: props.color}} />
11+
));
12+
13+
ColoredView.displayName = 'UncoloredView';
14+
ColoredView.propTypes = {
15+
color: PropTypes.string.isRequired,
16+
id: PropTypes.string
17+
}
18+
ColoredView.defaultProps = {
19+
id: 'test-forward-ref-default'
20+
}
21+
22+
module.exports = extendStyles(ColoredView);

src/resolver/findExportedComponentDefinition.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*/
1111

1212
import isExportsOrModuleAssignment from '../utils/isExportsOrModuleAssignment';
13+
import isReactForwardRefCall from '../utils/isReactForwardRefCall';
1314
import isReactComponentClass from '../utils/isReactComponentClass';
1415
import isReactCreateClassCall from '../utils/isReactCreateClassCall';
1516
import isStatelessComponent from '../utils/isStatelessComponent';
@@ -29,7 +30,8 @@ function isComponentDefinition(path) {
2930
return (
3031
isReactCreateClassCall(path) ||
3132
isReactComponentClass(path) ||
32-
isStatelessComponent(path)
33+
isStatelessComponent(path) ||
34+
isReactForwardRefCall(path)
3335
);
3436
}
3537

@@ -45,6 +47,8 @@ function resolveDefinition(definition, types) {
4547
return definition;
4648
} else if (isStatelessComponent(definition)) {
4749
return definition;
50+
} else if (isReactForwardRefCall(definition)) {
51+
return definition;
4852
}
4953
return null;
5054
}

src/utils/getMemberExpressionValuePath.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import getNameOrValue from './getNameOrValue';
1414
import { String as toString } from './expressionTo';
15+
import isReactForwardRefCall from './isReactForwardRefCall';
1516
import recast from 'recast';
1617

1718
const {
@@ -40,7 +41,8 @@ function resolveName(path) {
4041
if (
4142
types.FunctionExpression.check(path.node) ||
4243
types.ArrowFunctionExpression.check(path.node) ||
43-
types.TaggedTemplateExpression.check(path.node)
44+
types.TaggedTemplateExpression.check(path.node) ||
45+
isReactForwardRefCall(path)
4446
) {
4547
let currentPath = path;
4648
while (currentPath.parent) {
@@ -56,7 +58,7 @@ function resolveName(path) {
5658

5759
throw new TypeError(
5860
'Attempted to resolveName for an unsupported path. resolveName accepts a ' +
59-
'VariableDeclaration, FunctionDeclaration, or FunctionExpression. Got "' +
61+
'VariableDeclaration, FunctionDeclaration, FunctionExpression or CallExpression. Got "' +
6062
path.node.type +
6163
'".',
6264
);

src/utils/getMemberValuePath.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const POSTPROCESS_MEMBERS = {
3434

3535
const LOOKUP_METHOD = {
3636
[types.ArrowFunctionExpression.name]: getMemberExpressionValuePath,
37+
[types.CallExpression.name]: getMemberExpressionValuePath,
3738
[types.FunctionExpression.name]: getMemberExpressionValuePath,
3839
[types.FunctionDeclaration.name]: getMemberExpressionValuePath,
3940
[types.VariableDeclaration.name]: getMemberExpressionValuePath,
@@ -62,7 +63,8 @@ function isSupportedDefinitionType({ node }) {
6263
types.VariableDeclaration.check(node) ||
6364
types.ArrowFunctionExpression.check(node) ||
6465
types.FunctionDeclaration.check(node) ||
65-
types.FunctionExpression.check(node)
66+
types.FunctionExpression.check(node) ||
67+
types.CallExpression.check(node)
6668
);
6769
}
6870

@@ -88,7 +90,7 @@ export default function getMemberValuePath(
8890
'Got unsupported definition type. Definition must be one of ' +
8991
'ObjectExpression, ClassDeclaration, ClassExpression,' +
9092
'VariableDeclaration, ArrowFunctionExpression, FunctionExpression, ' +
91-
'TaggedTemplateExpression or FunctionDeclaration. Got "' +
93+
'TaggedTemplateExpression, FunctionDeclaration or CallExpression. Got "' +
9294
componentDefinition.node.type +
9395
'"' +
9496
'instead.',

src/utils/isReactForwardRefCall.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) 2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @flow
10+
*/
11+
import isReactModuleName from './isReactModuleName';
12+
import match from './match';
13+
import recast from 'recast';
14+
import resolveToModule from './resolveToModule';
15+
16+
const {
17+
types: { namedTypes: types },
18+
} = recast;
19+
20+
/**
21+
* Returns true if the expression is a function call of the form
22+
* `React.forwardRef(...)`.
23+
*/
24+
export default function isReactForwardRefCall(path: NodePath): boolean {
25+
if (types.ExpressionStatement.check(path.node)) {
26+
path = path.get('expression');
27+
}
28+
29+
if (!match(path.node, { callee: { property: { name: 'forwardRef' } } })) {
30+
return false;
31+
}
32+
const module = resolveToModule(path.get('callee', 'object'));
33+
return Boolean(module && isReactModuleName(module));
34+
}

0 commit comments

Comments
 (0)