Skip to content

Commit 5bcf56c

Browse files
committed
feat: Remove building out of scope AST Nodes from resolveToValue
BREAKING CHANGE: `resolveToValue` will not create a `MemberExpression` for targets ending in destructuring. It will now simply resolve to the `Identifier` inside the destructuring. Use new helper `isDestructuringAssignment` to further check this identifier.
1 parent 4a53def commit 5bcf56c

8 files changed

+86
-42
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { parse } from '../../../tests/utils';
2+
import isDestructuringAssignment from '../isDestructuringAssignment';
3+
4+
describe('isDestructuringAssignment', () => {
5+
it('detects destructuring', () => {
6+
const def = parse(`
7+
var { Component } = require('react');
8+
`).get('body', 0, 'declarations', 0, 'id', 'properties', 0, 'key');
9+
10+
expect(isDestructuringAssignment(def, 'Component')).toBe(true);
11+
});
12+
13+
it('fails if name does not match', () => {
14+
const def = parse(`
15+
var { Component } = require('react');
16+
`).get('body', 0, 'declarations', 0, 'id', 'properties', 0, 'key');
17+
18+
expect(isDestructuringAssignment(def, 'Component2')).toBe(false);
19+
});
20+
21+
it('detects destructuring with alias', () => {
22+
const def = parse(`
23+
var { Component: C } = require('react');
24+
`).get('body', 0, 'declarations', 0, 'id', 'properties', 0, 'value');
25+
26+
expect(isDestructuringAssignment(def, 'Component')).toBe(true);
27+
});
28+
29+
it('fails if name does not match with alias', () => {
30+
const def = parse(`
31+
var { Component: C } = require('react');
32+
`).get('body', 0, 'declarations', 0, 'id', 'properties', 0, 'value');
33+
34+
expect(isDestructuringAssignment(def, 'Component2')).toBe(false);
35+
});
36+
});

src/utils/__tests__/isReactComponentClass-test.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,26 @@ describe('isReactComponentClass', () => {
8686

8787
it('resolves the super class reference', () => {
8888
const def = parse(`
89-
var {Component} = require('react');
89+
var { Component } = require('react');
9090
var C = Component;
9191
class Foo extends C {}
9292
`).get('body', 2);
9393

9494
expect(isReactComponentClass(def, noopImporter)).toBe(true);
9595
});
9696

97+
it('resolves the super class reference with alias', () => {
98+
const def = parse(`
99+
var { Component: C } = require('react');
100+
class Foo extends C {}
101+
`).get('body', 1);
102+
103+
expect(isReactComponentClass(def, noopImporter)).toBe(true);
104+
});
105+
97106
it('does not accept references to other modules', () => {
98107
const def = parse(`
99-
var {Component} = require('FakeReact');
108+
var { Component } = require('FakeReact');
100109
class Foo extends Component {}
101110
`).get('body', 1);
102111

src/utils/__tests__/resolveToValue-test.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,9 @@ describe('resolveToValue', () => {
1515
['var {foo: {bar: baz}} = bar;', 'baz;'].join('\n'),
1616
);
1717

18-
// Node should be equal to bar.foo.bar
18+
// Resolves to identifier in destructuring
1919
expect(resolveToValue(path, noopImporter)).toEqualASTNode(
20-
builders.memberExpression(
21-
builders.memberExpression(
22-
builders.identifier('bar'),
23-
builders.identifier('foo'),
24-
),
25-
builders.identifier('bar'),
26-
),
20+
builders.identifier('baz'),
2721
);
2822
});
2923

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { namedTypes as t } from 'ast-types';
2+
import type { NodePath } from 'ast-types/lib/node-path';
3+
4+
/**
5+
* Checks if the input Identifier is part of a destructuring Assignment
6+
* and the name of the property key matches the input name
7+
*/
8+
export default function isDestructuringAssignment(
9+
path: NodePath,
10+
name: string,
11+
): boolean {
12+
return (
13+
t.Identifier.check(path.node) &&
14+
t.Property.check(path.parentPath.node) &&
15+
path.parentPath.node.key.name === name &&
16+
t.ObjectPattern.check(path.parentPath.parentPath.node)
17+
);
18+
}

src/utils/isReactBuiltinCall.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import resolveToModule from './resolveToModule';
55
import resolveToValue from './resolveToValue';
66
import type { Importer } from '../parse';
77
import type { NodePath } from 'ast-types/lib/node-path';
8+
import isDestructuringAssignment from './isDestructuringAssignment';
89

910
/**
1011
* Returns true if the expression is a function call of the form
@@ -28,7 +29,12 @@ export default function isReactBuiltinCall(
2829
const value = resolveToValue(path.get('callee'), importer);
2930
if (value === path.get('callee')) return false;
3031

31-
if (
32+
// Check if this is a destructuring assignment
33+
// const { x } = require('react')
34+
if (isDestructuringAssignment(value, name)) {
35+
const module = resolveToModule(value, importer);
36+
return Boolean(module && isReactModuleName(module));
37+
} else if (
3238
// `require('react').createElement`
3339
(t.MemberExpression.check(value.node) &&
3440
t.Identifier.check(value.get('property').node) &&

src/utils/isReactComponentClass.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import resolveToModule from './resolveToModule';
55
import resolveToValue from './resolveToValue';
66
import type { Importer } from '../parse';
77
import type { NodePath } from 'ast-types/lib/node-path';
8+
import isDestructuringAssignment from './isDestructuringAssignment';
89

910
function isRenderMethod(node: ASTNode): boolean {
1011
const isProperty = node.type === 'ClassProperty';
@@ -43,7 +44,9 @@ export default function isReactComponentClass(
4344
const superClass = resolveToValue(path.get('superClass'), importer);
4445
if (
4546
match(superClass.node, { property: { name: 'Component' } }) ||
46-
match(superClass.node, { property: { name: 'PureComponent' } })
47+
match(superClass.node, { property: { name: 'PureComponent' } }) ||
48+
isDestructuringAssignment(superClass, 'Component') ||
49+
isDestructuringAssignment(superClass, 'PureComponent')
4750
) {
4851
const module = resolveToModule(superClass, importer);
4952
if (module && isReactModuleName(module)) {

src/utils/resolveToModule.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,15 @@ export default function resolveToModule(
3737
if (valuePath !== path) {
3838
return resolveToModule(valuePath, importer);
3939
}
40-
break;
40+
if (!t.Property.check(path.parentPath.node)) {
41+
break;
42+
}
4143
}
4244

45+
// @ts-ignore // fall through
46+
case t.Property.name: // @ts-ignore
47+
case t.ObjectPattern.name:
48+
return resolveToModule(path.parentPath, importer);
4349
// @ts-ignore
4450
case t.ImportDeclaration.name:
4551
return node.source.value;

src/utils/resolveToValue.ts

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NodePath as Path, builders, namedTypes as t } from 'ast-types';
1+
import { namedTypes as t } from 'ast-types';
22
import getMemberExpressionRoot from './getMemberExpressionRoot';
33
import getPropertyValuePath from './getPropertyValuePath';
44
import { Array as toArray } from './expressionTo';
@@ -11,28 +11,6 @@ import type { NodePath } from 'ast-types/lib/node-path';
1111
import { Scope } from 'ast-types/lib/scope';
1212
import { Context } from 'ast-types/lib/path-visitor';
1313

14-
function buildMemberExpressionFromPattern(path: NodePath): NodePath | null {
15-
const node = path.node;
16-
if (t.Property.check(node)) {
17-
const objPath = buildMemberExpressionFromPattern(path.parent);
18-
if (objPath) {
19-
return new Path(
20-
builders.memberExpression(
21-
objPath.node,
22-
node.key,
23-
t.Literal.check(node.key),
24-
),
25-
objPath,
26-
);
27-
}
28-
} else if (t.ObjectPattern.check(node)) {
29-
return buildMemberExpressionFromPattern(path.parent);
30-
} else if (t.VariableDeclarator.check(node)) {
31-
return path.get('init');
32-
}
33-
return null;
34-
}
35-
3614
function findScopePath(
3715
paths: NodePath[],
3816
path: NodePath,
@@ -74,12 +52,6 @@ function findScopePath(
7452
t.TSInterfaceDeclaration.check(parentPath.node)
7553
) {
7654
resultPath = parentPath;
77-
} else if (t.Property.check(parentPath.node)) {
78-
// must be inside a pattern
79-
const memberExpressionPath = buildMemberExpressionFromPattern(parentPath);
80-
if (memberExpressionPath) {
81-
return memberExpressionPath;
82-
}
8355
}
8456

8557
if (resultPath.node !== path.node) {

0 commit comments

Comments
 (0)