diff --git a/src/utils/__tests__/isDestructuringAssignment-test.ts b/src/utils/__tests__/isDestructuringAssignment-test.ts new file mode 100644 index 00000000000..d6de528e775 --- /dev/null +++ b/src/utils/__tests__/isDestructuringAssignment-test.ts @@ -0,0 +1,36 @@ +import { parse } from '../../../tests/utils'; +import isDestructuringAssignment from '../isDestructuringAssignment'; + +describe('isDestructuringAssignment', () => { + it('detects destructuring', () => { + const def = parse(` + var { Component } = require('react'); + `).get('body', 0, 'declarations', 0, 'id', 'properties', 0, 'key'); + + expect(isDestructuringAssignment(def, 'Component')).toBe(true); + }); + + it('fails if name does not match', () => { + const def = parse(` + var { Component } = require('react'); + `).get('body', 0, 'declarations', 0, 'id', 'properties', 0, 'key'); + + expect(isDestructuringAssignment(def, 'Component2')).toBe(false); + }); + + it('detects destructuring with alias', () => { + const def = parse(` + var { Component: C } = require('react'); + `).get('body', 0, 'declarations', 0, 'id', 'properties', 0, 'value'); + + expect(isDestructuringAssignment(def, 'Component')).toBe(true); + }); + + it('fails if name does not match with alias', () => { + const def = parse(` + var { Component: C } = require('react'); + `).get('body', 0, 'declarations', 0, 'id', 'properties', 0, 'value'); + + expect(isDestructuringAssignment(def, 'Component2')).toBe(false); + }); +}); diff --git a/src/utils/__tests__/isReactComponentClass-test.ts b/src/utils/__tests__/isReactComponentClass-test.ts index ffc4a5e079d..cd3c38bce6d 100644 --- a/src/utils/__tests__/isReactComponentClass-test.ts +++ b/src/utils/__tests__/isReactComponentClass-test.ts @@ -86,7 +86,7 @@ describe('isReactComponentClass', () => { it('resolves the super class reference', () => { const def = parse(` - var {Component} = require('react'); + var { Component } = require('react'); var C = Component; class Foo extends C {} `).get('body', 2); @@ -94,9 +94,18 @@ describe('isReactComponentClass', () => { expect(isReactComponentClass(def, noopImporter)).toBe(true); }); + it('resolves the super class reference with alias', () => { + const def = parse(` + var { Component: C } = require('react'); + class Foo extends C {} + `).get('body', 1); + + expect(isReactComponentClass(def, noopImporter)).toBe(true); + }); + it('does not accept references to other modules', () => { const def = parse(` - var {Component} = require('FakeReact'); + var { Component } = require('FakeReact'); class Foo extends Component {} `).get('body', 1); diff --git a/src/utils/__tests__/resolveToValue-test.ts b/src/utils/__tests__/resolveToValue-test.ts index fe947106873..6eeb1f29a11 100644 --- a/src/utils/__tests__/resolveToValue-test.ts +++ b/src/utils/__tests__/resolveToValue-test.ts @@ -15,15 +15,9 @@ describe('resolveToValue', () => { ['var {foo: {bar: baz}} = bar;', 'baz;'].join('\n'), ); - // Node should be equal to bar.foo.bar + // Resolves to identifier in destructuring expect(resolveToValue(path, noopImporter)).toEqualASTNode( - builders.memberExpression( - builders.memberExpression( - builders.identifier('bar'), - builders.identifier('foo'), - ), - builders.identifier('bar'), - ), + builders.identifier('baz'), ); }); diff --git a/src/utils/isDestructuringAssignment.ts b/src/utils/isDestructuringAssignment.ts new file mode 100644 index 00000000000..49035dd6c21 --- /dev/null +++ b/src/utils/isDestructuringAssignment.ts @@ -0,0 +1,18 @@ +import { namedTypes as t } from 'ast-types'; +import type { NodePath } from 'ast-types/lib/node-path'; + +/** + * Checks if the input Identifier is part of a destructuring Assignment + * and the name of the property key matches the input name + */ +export default function isDestructuringAssignment( + path: NodePath, + name: string, +): boolean { + return ( + t.Identifier.check(path.node) && + t.Property.check(path.parentPath.node) && + path.parentPath.node.key.name === name && + t.ObjectPattern.check(path.parentPath.parentPath.node) + ); +} diff --git a/src/utils/isReactBuiltinCall.ts b/src/utils/isReactBuiltinCall.ts index 9f905868ae8..c96316ec723 100644 --- a/src/utils/isReactBuiltinCall.ts +++ b/src/utils/isReactBuiltinCall.ts @@ -5,6 +5,7 @@ import resolveToModule from './resolveToModule'; import resolveToValue from './resolveToValue'; import type { Importer } from '../parse'; import type { NodePath } from 'ast-types/lib/node-path'; +import isDestructuringAssignment from './isDestructuringAssignment'; /** * Returns true if the expression is a function call of the form @@ -28,7 +29,12 @@ export default function isReactBuiltinCall( const value = resolveToValue(path.get('callee'), importer); if (value === path.get('callee')) return false; - if ( + // Check if this is a destructuring assignment + // const { x } = require('react') + if (isDestructuringAssignment(value, name)) { + const module = resolveToModule(value, importer); + return Boolean(module && isReactModuleName(module)); + } else if ( // `require('react').createElement` (t.MemberExpression.check(value.node) && t.Identifier.check(value.get('property').node) && diff --git a/src/utils/isReactComponentClass.ts b/src/utils/isReactComponentClass.ts index 9562d6a5411..3069633f0d3 100644 --- a/src/utils/isReactComponentClass.ts +++ b/src/utils/isReactComponentClass.ts @@ -5,6 +5,7 @@ import resolveToModule from './resolveToModule'; import resolveToValue from './resolveToValue'; import type { Importer } from '../parse'; import type { NodePath } from 'ast-types/lib/node-path'; +import isDestructuringAssignment from './isDestructuringAssignment'; function isRenderMethod(node: ASTNode): boolean { const isProperty = node.type === 'ClassProperty'; @@ -43,7 +44,9 @@ export default function isReactComponentClass( const superClass = resolveToValue(path.get('superClass'), importer); if ( match(superClass.node, { property: { name: 'Component' } }) || - match(superClass.node, { property: { name: 'PureComponent' } }) + match(superClass.node, { property: { name: 'PureComponent' } }) || + isDestructuringAssignment(superClass, 'Component') || + isDestructuringAssignment(superClass, 'PureComponent') ) { const module = resolveToModule(superClass, importer); if (module && isReactModuleName(module)) { diff --git a/src/utils/resolveObjectValuesToArray.ts b/src/utils/resolveObjectValuesToArray.ts index c6c17341e10..4891228d024 100644 --- a/src/utils/resolveObjectValuesToArray.ts +++ b/src/utils/resolveObjectValuesToArray.ts @@ -2,7 +2,6 @@ import { ASTNode, namedTypes as t } from 'ast-types'; import resolveToValue from './resolveToValue'; import type { Importer } from '../parse'; import type { NodePath } from 'ast-types/lib/node-path'; -import { LiteralBuilder } from 'ast-types/gen/builders'; interface ObjectPropMap { properties: string[]; @@ -139,7 +138,7 @@ export default function resolveObjectValuesToArray( if (propMap) { const nodes = propMap.properties.map(prop => { - const value = propMap.values[prop] as Parameters[0]; + const value = propMap.values[prop]; return typeof value === 'undefined' ? 'null' diff --git a/src/utils/resolveToModule.ts b/src/utils/resolveToModule.ts index 5a22714818e..a08ecf7e29c 100644 --- a/src/utils/resolveToModule.ts +++ b/src/utils/resolveToModule.ts @@ -37,9 +37,15 @@ export default function resolveToModule( if (valuePath !== path) { return resolveToModule(valuePath, importer); } - break; + if (!t.Property.check(path.parentPath.node)) { + break; + } } + // @ts-ignore // fall through + case t.Property.name: // @ts-ignore + case t.ObjectPattern.name: + return resolveToModule(path.parentPath, importer); // @ts-ignore case t.ImportDeclaration.name: return node.source.value; diff --git a/src/utils/resolveToValue.ts b/src/utils/resolveToValue.ts index e2d56a41d00..0dc9e8fe072 100644 --- a/src/utils/resolveToValue.ts +++ b/src/utils/resolveToValue.ts @@ -1,4 +1,4 @@ -import { NodePath as Path, builders, namedTypes as t } from 'ast-types'; +import { namedTypes as t } from 'ast-types'; import getMemberExpressionRoot from './getMemberExpressionRoot'; import getPropertyValuePath from './getPropertyValuePath'; import { Array as toArray } from './expressionTo'; @@ -11,28 +11,6 @@ import type { NodePath } from 'ast-types/lib/node-path'; import { Scope } from 'ast-types/lib/scope'; import { Context } from 'ast-types/lib/path-visitor'; -function buildMemberExpressionFromPattern(path: NodePath): NodePath | null { - const node = path.node; - if (t.Property.check(node)) { - const objPath = buildMemberExpressionFromPattern(path.parent); - if (objPath) { - return new Path( - builders.memberExpression( - objPath.node, - node.key, - t.Literal.check(node.key), - ), - objPath, - ); - } - } else if (t.ObjectPattern.check(node)) { - return buildMemberExpressionFromPattern(path.parent); - } else if (t.VariableDeclarator.check(node)) { - return path.get('init'); - } - return null; -} - function findScopePath( paths: NodePath[], path: NodePath, @@ -74,12 +52,6 @@ function findScopePath( t.TSInterfaceDeclaration.check(parentPath.node) ) { resultPath = parentPath; - } else if (t.Property.check(parentPath.node)) { - // must be inside a pattern - const memberExpressionPath = buildMemberExpressionFromPattern(parentPath); - if (memberExpressionPath) { - return memberExpressionPath; - } } if (resultPath.node !== path.node) {