From 8686b32209f409369910fb9bba0453d03940aaf2 Mon Sep 17 00:00:00 2001 From: Kaoru0822 Date: Thu, 15 Oct 2026 09:34:18 -0700 Subject: [PATCH] fix(browser): omit erased type-only import-equals from source-map dependencies (#4165) Source-map original-source parsing reported `import type X = require("m")` as a require dependency, although TypeScript erases the declaration. The fetcher re-derived import-equals targets instead of using the existing value-only rule. That rule now lives with the other type-only module declaration predicates in javascriptModuleDeclaration, and the fetcher, static analysis, and semantic analysis all use it. Fixes #3932 Co-authored-by: Claude Opus 5.5 --- src/browser/WebSourceMapFetcher.ts | 18 +++++++----------- .../javascript/javascriptModuleDeclaration.ts | 13 +++++++++++++ .../javascript/javascriptSemanticAnalysis.ts | 6 ++++-- .../javascript/javascriptSemanticProjection.ts | 11 +---------- .../javascript/javascriptStaticAnalysis.ts | 2 +- .../browser/webSourceMapFetcher.test.ts | 5 +++++ 6 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/browser/WebSourceMapFetcher.ts b/src/browser/WebSourceMapFetcher.ts index 567ed4aa97..b7bb390db9 100644 --- a/src/browser/WebSourceMapFetcher.ts +++ b/src/browser/WebSourceMapFetcher.ts @@ -14,6 +14,7 @@ import { parseJavaScriptSource } from "../domain/javascript/javascriptSourcePars import { isTypeOnlyExportDeclaration, isTypeOnlyImportDeclaration, + semanticImportEqualsSpecifier, } from "../domain/javascript/javascriptModuleDeclaration.js"; import { stringValue } from "../domain/javascript/javascriptStaticAnalysisHelpers.js"; import type { @@ -1100,16 +1101,11 @@ const originalDependency = ( : { kind: "require", specifier, requireCallee: node.callee }; } // `import x = require("m")` is only legal at module top level and always - // refers to the host loader, so it needs no unbound-`require` check. The - // `moduleReference` is an entity name for a local alias instead, which - // declares no dependency. - if ( - t.isTSImportEqualsDeclaration(node) && - t.isTSExternalModuleReference(node.moduleReference) - ) - return { - kind: "require", - specifier: node.moduleReference.expression.value, - }; + // refers to the host loader, so it needs no unbound-`require` check. An + // entity-name alias or an erased `import type` declares no dependency. + if (t.isTSImportEqualsDeclaration(node)) { + const specifier = semanticImportEqualsSpecifier(node); + return specifier === null ? null : { kind: "require", specifier }; + } return null; }; diff --git a/src/domain/javascript/javascriptModuleDeclaration.ts b/src/domain/javascript/javascriptModuleDeclaration.ts index d7a8d67540..30e705b085 100644 --- a/src/domain/javascript/javascriptModuleDeclaration.ts +++ b/src/domain/javascript/javascriptModuleDeclaration.ts @@ -16,6 +16,19 @@ export const isTypeOnlyExportDeclaration = ( t.isTSTypeAliasDeclaration(node.declaration) || t.isTSDeclareFunction(node.declaration))); +/** + * Runtime external-module target for a value import-equals declaration; + * `import type X = require("m")` is erased and declares no dependency. + */ +export const semanticImportEqualsSpecifier = ( + node: t.TSImportEqualsDeclaration, +): string | null => + node.importKind !== "type" && + t.isTSExternalModuleReference(node.moduleReference) && + t.isStringLiteral(node.moduleReference.expression) + ? node.moduleReference.expression.value + : null; + /** Whether this import binding is explicitly type-only. */ export const isTypeOnlyImportSpecifier = (node: t.ImportSpecifier): boolean => node.importKind === "type" || node.importKind === "typeof"; diff --git a/src/domain/javascript/javascriptSemanticAnalysis.ts b/src/domain/javascript/javascriptSemanticAnalysis.ts index 176be51dda..a44914ab03 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.ts @@ -10,7 +10,6 @@ import { import { collectSemanticModuleLink, collectSemanticCallable, - semanticImportEqualsSpecifier, collectSemanticReferences, collectSemanticReferencesSteps, immutableSemanticBindings, @@ -47,7 +46,10 @@ import { semanticRequireOrigin } from "./javascriptSemanticRequireOrigin.js"; import { semanticBundledRequireOrigin } from "./javascriptBundledRequireOrigin.js"; import { collectSemanticMemberMutationsSteps } from "./javascriptSemanticMemberMutations.js"; import { indexJavaScriptAmbientWritesSteps } from "./javascriptSemanticAmbientWrites.js"; -import { hasUncertainTypeOnlyModuleSideEffect } from "./javascriptModuleDeclaration.js"; +import { + hasUncertainTypeOnlyModuleSideEffect, + semanticImportEqualsSpecifier, +} from "./javascriptModuleDeclaration.js"; import { collectSemanticReturns, resolveSemanticModuleCallables, diff --git a/src/domain/javascript/javascriptSemanticProjection.ts b/src/domain/javascript/javascriptSemanticProjection.ts index 9389427d8f..27f2fd36c7 100644 --- a/src/domain/javascript/javascriptSemanticProjection.ts +++ b/src/domain/javascript/javascriptSemanticProjection.ts @@ -36,6 +36,7 @@ import { isTypeOnlyExportSpecifier, isTypeOnlyImportDeclaration, isTypeOnlyImportSpecifier, + semanticImportEqualsSpecifier, } from "./javascriptModuleDeclaration.js"; import { commonJsExportName, @@ -149,16 +150,6 @@ const collectImportEquals = ( ); }; -/** Runtime external-module target for a value import-equals declaration. */ -export const semanticImportEqualsSpecifier = ( - node: t.TSImportEqualsDeclaration, -): string | null => - node.importKind !== "type" && - t.isTSExternalModuleReference(node.moduleReference) && - t.isStringLiteral(node.moduleReference.expression) - ? node.moduleReference.expression.value - : null; - /** Collect lexical references after definitions exist, optionally for one name. */ export const collectSemanticReferences = ( program: t.Program, diff --git a/src/domain/javascript/javascriptStaticAnalysis.ts b/src/domain/javascript/javascriptStaticAnalysis.ts index 49158abe68..a630ff5783 100644 --- a/src/domain/javascript/javascriptStaticAnalysis.ts +++ b/src/domain/javascript/javascriptStaticAnalysis.ts @@ -51,11 +51,11 @@ import { } from "./javascriptSourceParser.js"; import { createJavaScriptRpcScan } from "./javascriptRpcEndpoints.js"; -import { semanticImportEqualsSpecifier } from "./javascriptSemanticProjection.js"; import { hasUncertainTypeOnlyModuleSideEffect, isTypeOnlyExportDeclaration, isTypeOnlyImportDeclaration, + semanticImportEqualsSpecifier, } from "./javascriptModuleDeclaration.js"; /** Parse one JavaScript artifact and recover static structure only. */ diff --git a/tests/boundary/browser/webSourceMapFetcher.test.ts b/tests/boundary/browser/webSourceMapFetcher.test.ts index 782ff8cf07..7ff80d5341 100644 --- a/tests/boundary/browser/webSourceMapFetcher.test.ts +++ b/tests/boundary/browser/webSourceMapFetcher.test.ts @@ -1102,6 +1102,11 @@ describe("source-map dependency coverage of hard-to-parse sources", () => { 'import lib = require("./lib.js");', ["./lib.js"], ], + [ + "type-only import equals", + 'import type Types = require("./types.js");\nimport lib = require("./lib.js");', + ["./lib.js"], + ], [ "whole-statement type-only import", 'import type { Value } from "./types.js";',