Skip to content

Commit

Permalink
fix: avoid circular reference when resolving name from within assignment
Browse files Browse the repository at this point in the history
This minimal tweak to `resolveToValue` allows us, in particular, to follow some reassignments of the variable holding a component, e.g. when wrapping it with a HOC.
  • Loading branch information
motiz88 authored and danez committed Feb 13, 2020
1 parent b34977a commit 04f573f
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 3 deletions.
20 changes: 20 additions & 0 deletions src/handlers/__tests__/flowTypeHandler-test.js
Expand Up @@ -368,5 +368,25 @@ describe('flowTypeHandler', () => {
},
});
});

it('resolves when the function is rebound and not inline', () => {
const src = `
import React from 'react';
type Props = { foo: string };
let Component = (props: Props, ref) => <div ref={ref}>{props.foo}</div>;
Component = React.forwardRef(Component);
`;
flowTypeHandler(
documentation,
parse(src).get('body', 3, 'expression', 'right'),
);
expect(documentation.descriptors).toEqual({
foo: {
flowType: {},
required: true,
description: '',
},
});
});
});
});
20 changes: 20 additions & 0 deletions src/utils/__tests__/resolveToValue-test.js
Expand Up @@ -109,6 +109,26 @@ describe('resolveToValue', () => {

expect(resolveToValue(path)).toEqualASTNode(builders.literal(42));
});

it('resolves to other assigned value if ref is in an assignment lhs', () => {
const path = parsePath(
['var foo;', 'foo = 42;', 'foo = wrap(foo);'].join('\n'),
);

expect(resolveToValue(path.get('left'))).toEqualASTNode(
builders.literal(42),
);
});

it('resolves to other assigned value if ref is in an assignment rhs', () => {
const path = parsePath(
['var foo;', 'foo = 42;', 'foo = wrap(foo);'].join('\n'),
);

expect(resolveToValue(path.get('right', 'arguments', 0))).toEqualASTNode(
builders.literal(42),
);
});
});

describe('ImportDeclaration', () => {
Expand Down
16 changes: 13 additions & 3 deletions src/utils/resolveToValue.js
Expand Up @@ -72,22 +72,32 @@ function findScopePath(paths: Array<NodePath>, path: NodePath): ?NodePath {
* Tries to find the last value assigned to `name` in the scope created by
* `scope`. We are not descending into any statements (blocks).
*/
function findLastAssignedValue(scope, name) {
function findLastAssignedValue(scope, idPath) {
const results = [];
const name = idPath.node.name;

traverseShallow(scope.path, {
visitAssignmentExpression: function(path) {
const node = path.node;
// Skip anything that is not an assignment to a variable with the
// passed name.
// Ensure the LHS isn't the reference we're trying to resolve.
if (
!t.Identifier.check(node.left) ||
node.left === idPath.node ||
node.left.name !== name ||
node.operator !== '='
) {
return this.traverse(path);
}
results.push(path.get('right'));
// Ensure the RHS doesn't contain the reference we're trying to resolve.
const candidatePath = path.get('right');
for (let p = idPath; p && p.node != null; p = p.parent) {
if (p.node === candidatePath.node) {
return this.traverse(path);
}
}
results.push(candidatePath);
return false;
},
});
Expand Down Expand Up @@ -159,7 +169,7 @@ export default function resolveToValue(path: NodePath): NodePath {
// The variable may be assigned a different value after initialization.
// We are first trying to find all assignments to the variable in the
// block where it is defined (i.e. we are not traversing into statements)
resolvedPath = findLastAssignedValue(scope, node.name);
resolvedPath = findLastAssignedValue(scope, path);
if (!resolvedPath) {
const bindings = scope.getBindings()[node.name];
resolvedPath = findScopePath(bindings, path);
Expand Down

0 comments on commit 04f573f

Please sign in to comment.