fix(javascript): exclude erased imports from runtime bindings (#6994)

This commit is contained in:
gensokyo-dev
2026-10-24 19:22:23 +08:00
committed by GitHub
parent 0402012a95
commit be2811e414
4 changed files with 53 additions and 27 deletions
@@ -160,6 +160,11 @@ describe("TypeScript type-only module links", () => {
export type { hiddenFunction };
`);
expect(ir.bindings.map(({ name }) => name).sort()).toEqual([
"hiddenFunction",
"value",
]);
const externalLinks = ir.moduleLinks
.filter(({ specifier }) => specifier !== null)
.map(
@@ -80,6 +80,8 @@ import {
} from "./javascriptSemanticAmbientWrites.js";
import {
hasUncertainTypeOnlyModuleSideEffect,
isTypeOnlyImportDeclaration,
isTypeOnlyImportSpecifier,
semanticImportEqualsSpecifier,
} from "./javascriptModuleDeclaration.js";
import {
@@ -2038,7 +2040,10 @@ const bindImports = (
scope: JavaScriptSemanticScopeState,
state: JavaScriptSemanticAnalysisState,
): void => {
if (isTypeOnlyImportDeclaration(node)) return;
for (const specifier of node.specifiers) {
if (t.isImportSpecifier(specifier) && isTypeOnlyImportSpecifier(specifier))
continue;
const importedPath = t.isImportDefaultSpecifier(specifier)
? ["default"]
: t.isImportNamespaceSpecifier(specifier)
@@ -2053,12 +2058,7 @@ const bindImports = (
definitionNode: specifier.local,
initializer: null,
directOrigin: { specifier: node.source.value, importedPath },
runtimeImport:
node.importKind !== "type" &&
node.importKind !== "typeof" &&
(!t.isImportSpecifier(specifier) ||
(specifier.importKind !== "type" &&
specifier.importKind !== "typeof")),
runtimeImport: true,
});
}
};
@@ -1192,16 +1192,7 @@ const provenanceForBindingInitializers = (
: "Catch binding provenance depends on the thrown value.",
);
if (binding.directOrigins.length > 0)
return binding.kind === "import" &&
!binding.runtimeImport &&
binding.directOrigins.every(
({ resolutionMode }) => resolutionMode !== "require",
)
? semanticAmbiguousProvenance(
binding.directOrigins,
"A type-only import does not establish a runtime module value.",
)
: semanticOriginsProvenance(binding.directOrigins);
return semanticOriginsProvenance(binding.directOrigins);
if (binding.initializers.length === 0) return semanticLocalProvenance();
if (binding.initializers.length > 1)
return semanticAmbiguousProvenance(
@@ -1,4 +1,7 @@
import { expect } from "vitest";
import { runInNewContext } from "node:vm";
import { transformSync } from "esbuild";
import { expect, it } from "vitest";
import type { JavaScriptApplicationGraph } from "../../../src/domain/javascript/javascriptApplicationGraph.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
@@ -6,6 +9,37 @@ import { cliTest } from "../../support/cli/cliFixture.js";
import { analyzeJavaScriptDirectory } from "../../support/cli/javascriptApplication.js";
import { writeFixtureFiles } from "../../support/javascriptApplicationFixture.js";
const typeOnlyWorkerImports = [
'import type { Worker } from "node:worker_threads";',
'import { type Worker } from "node:worker_threads";',
];
it.each(typeOnlyWorkerImports)(
"preserves the ambient Worker at runtime after erasing %s",
(prefix) => {
const workerUrls: string[] = [];
const { code } = transformSync(
`${prefix}\nnew Worker(new URL("./worker.js", import.meta.url));`,
{
loader: "ts",
format: "iife",
define: {
"import.meta.url": JSON.stringify("file:///fixture/main.ts"),
},
},
);
runInNewContext(code, {
URL,
Worker: class {
constructor(url: URL) {
workerUrls.push(url.href);
}
},
});
expect(workerUrls).toEqual(["file:///fixture/worker.js"]);
},
);
const workerReferences = (graph: JavaScriptApplicationGraph) =>
graph.nodes
.filter(({ kind }) => kind === "worker")
@@ -84,6 +118,12 @@ cliTest(
suffix: "",
resolves: true,
},
...typeOnlyWorkerImports.map((prefix) => ({
prefix,
constructor: "new Worker",
suffix: "",
resolves: true,
})),
...[
["class Worker {}", "new Worker", ""],
["globalThis.Worker = Fake;", "new Worker", ""],
@@ -94,16 +134,6 @@ cliTest(
["const custom = { Worker: class {} };", "new custom.Worker", ""],
["function start(window) {", "new window.Worker", "}"],
["function start(globalThis) {", "new globalThis.Worker", "}"],
[
'import type { Worker } from "node:worker_threads";',
"new Worker",
"",
],
[
'import { type Worker } from "node:worker_threads";',
"new Worker",
"",
],
[
'import type * as threads from "node:worker_threads";',
"new threads.Worker",