From 191a654d773ae88f9293f709b502e24f2a4a8758 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 10 Jan 2021 20:08:52 +0100 Subject: [PATCH] feat(referencesImport): support named exports accessed via namespace imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(traverse): referencesImport * feat(referencesImport): support named exports accessed via namespace imports * feat(referencesImport): support `namespaceObj?.importName` and `namespaceObj["importName"]` * refactor(referencesImport): simplify member expression computed branches * test(referencesImport): aliased named import case * fix(referencesImport): do not be fooled by import name "*" at least when detecting accessing a named import via a '*' import Co-authored-by: Nicolò Ribaudo --- .../babel-traverse/src/path/introspection.ts | 23 +++- packages/babel-traverse/test/introspection.js | 104 ++++++++++++++++++ 2 files changed, 124 insertions(+), 3 deletions(-) diff --git a/packages/babel-traverse/src/path/introspection.ts b/packages/babel-traverse/src/path/introspection.ts index 0e01e4f20cc9..91a0906d83ea 100644 --- a/packages/babel-traverse/src/path/introspection.ts +++ b/packages/babel-traverse/src/path/introspection.ts @@ -164,13 +164,30 @@ export function isStatementOrBlock(this: NodePath): boolean { */ export function referencesImport( - this: NodePath, + this: NodePath, moduleSource: string, importName: string, ): boolean { - if (!this.isReferencedIdentifier()) return false; + if (!this.isReferencedIdentifier()) { + if ( + (this.isMemberExpression() || this.isOptionalMemberExpression()) && + (this.node.computed + ? t.isStringLiteral(this.node.property, { value: importName }) + : (this.node.property as t.Identifier).name === importName) + ) { + const object = (this as NodePath< + t.MemberExpression | t.OptionalMemberExpression + >).get("object"); + return ( + object.isReferencedIdentifier() && + object.referencesImport(moduleSource, "*") + ); + } + + return false; + } - const binding = this.scope.getBinding(this.node.name); + const binding = this.scope.getBinding((this.node as t.Identifier).name); if (!binding || binding.kind !== "module") return false; const path = binding.path; diff --git a/packages/babel-traverse/test/introspection.js b/packages/babel-traverse/test/introspection.js index 481561ba9d6e..980a88256609 100644 --- a/packages/babel-traverse/test/introspection.js +++ b/packages/babel-traverse/test/introspection.js @@ -100,4 +100,108 @@ describe("path/introspection", function () { }); }); }); + + describe("referencesImport", function () { + it("accepts a default import", function () { + const program = getPath(`import dep from "source"; dep;`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "default")).toBe(true); + }); + it("rejects a default import from the wrong module", function () { + const program = getPath(`import dep from "wrong-source"; dep;`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "default")).toBe(false); + }); + it("rejects a named instead of default import", function () { + const program = getPath(`import { dep } from "source"; dep;`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "default")).toBe(false); + }); + + it("accepts a named import", function () { + const program = getPath(`import { dep } from "source"; dep;`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "dep")).toBe(true); + }); + it("accepts an aliased named import", function () { + const program = getPath(`import { dep as alias } from "source"; alias;`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "dep")).toBe(true); + }); + it("accepts a named import via a namespace import member expression", function () { + const program = getPath(`import * as ns from "source"; ns.dep;`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "dep")).toBe(true); + }); + it("accepts a named import via a namespace import optional member expression", function () { + const program = getPath(`import * as ns from "source"; ns?.dep;`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "dep")).toBe(true); + }); + it("accepts a named import via a namespace import computed member expression", function () { + const program = getPath(`import * as ns from "source"; ns["😅"];`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "😅")).toBe(true); + }); + it("rejects a named import from the wrong module", function () { + const program = getPath(`import { dep } from "wrong-source"; dep;`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "dep")).toBe(false); + }); + it("rejects a default instead of named import", function () { + const program = getPath(`import dep from "source"; dep;`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "dep")).toBe(false); + }); + it('rejects the "export called *" trick', function () { + const program = getPath(`import * as ns from "source"; ns["*"].nested;`, { + sourceType: "module", + plugins: ["moduleStringNames"], + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "nested")).toBe(false); + }); + + it("accepts a namespace import", function () { + const program = getPath(`import * as dep from "source"; dep;`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "*")).toBe(true); + }); + it("rejects a namespace import from the wrong module", function () { + const program = getPath(`import * as dep from "wrong-source"; dep;`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "*")).toBe(false); + }); + it("rejects a default instead of a namespace import", () => { + const program = getPath(`import dep from "source"; dep;`, { + sourceType: "module", + }); + const reference = program.get("body.1.expression"); + expect(reference.referencesImport("source", "*")).toBe(false); + }); + }); });