fix(javascript): preserve TypeScript loader and dependency semantics

Share loader selection across static findings and module relationships: CTS static imports and re-exports use CommonJS lookup while dynamic imports retain ESM lookup. Retain runtime import-equals dependencies, bindings, and exported names; do not resolve require file-URL strings as local artifacts. Restore import-map descriptions in CLI help.

Validated resolver and semantic tests, application reconstruction boundaries, and CLI help. Installed TypeScript NodeNext emission and Node require behavior were checked independently. Refs #2056, #2057, #2058, #2053.
This commit is contained in:
morluto committed 2026-10-12 14:12:19 +08:00
1 parent 966f7cc84b
commit e4532b5376
8 files changed
+242 -13

No files matched your search

@@ -69,7 +69,8 @@ export const collectSemanticModuleLink = (
node: t.Node,
state: JavaScriptSemanticAnalysisState,
): void => {
if (t.isImportDeclaration(node)) collectImports(node, state);
if (t.isTSImportEqualsDeclaration(node)) collectImportEquals(node, state);
else if (t.isImportDeclaration(node)) collectImports(node, state);
else if (t.isExportAllDeclaration(node))
addModuleLink(
state,
@@ -103,6 +104,36 @@ export const collectSemanticModuleLink = (
else if (t.isCallExpression(node)) collectUnboundRequireLink(node, state);
};
const collectImportEquals = (
node: t.TSImportEqualsDeclaration,
state: JavaScriptSemanticAnalysisState,
): void => {
const specifier = semanticImportEqualsSpecifier(node);
if (specifier === null) return;
addModuleLink(
state,
{
kind: "require",
specifier,
importedName: null,
localName: node.id.name,
exportedName: null,
location: range(node),
},
node,
);
};
/** 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,
@@ -707,6 +738,11 @@ const declarationNames = (declaration: t.Declaration): string[] => {
t.isIdentifier(declaration.id)
)
return [declaration.id.name];
if (
t.isTSImportEqualsDeclaration(declaration) &&
semanticImportEqualsSpecifier(declaration) !== null
)
return [declaration.id.name];
return [];
};