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

@@ -14,6 +14,90 @@ import {
writeFixtureFiles,
} from "../../../support/javascriptApplicationFixture.js";
describe("TypeScript source module loader semantics", () => {
it("uses emitted TypeScript loader semantics without changing dynamic imports", async () => {
const root = await createTestTempDirectory("rea-cts-module-loader-");
await writeFixtureFiles(root, {
"package.json": '{"type":"module"}',
"main.cts": `
import local = require("./local");
import selected from "conditional";
export { retained } from "conditional";
void import("conditional");
void local;
void selected;
`,
"consumer.mts": `
import local = require("./local");
export { local };
`,
"local.js": "module.exports = 42;",
"node_modules/conditional/package.json": JSON.stringify({
exports: {
".": {
import: "./esm.mjs",
require: "./cjs.cjs",
},
},
}),
"node_modules/conditional/esm.mjs": "export const retained = 'esm';",
"node_modules/conditional/cjs.cjs": "exports.retained = 'cjs';",
});
const { graph } = await reconstructJavaScriptFixture(root);
const localRequireEdge = (sourcePath: string) => {
const source = findSourceModule(graph, sourcePath);
return graph.edges.find(
({ source_node_id, relation, properties }) =>
source_node_id === source?.node_id &&
relation === "imports" &&
properties.module_link_kind === "require" &&
properties.specifier === "./local",
);
};
expect(
findSourceModule(graph, "main.cts")?.observations[0]?.properties,
).toMatchObject({ module_format: "commonjs" });
expect(localRequireEdge("main.cts")).toMatchObject({
properties: { resolved_path: "local.js", local_name: "local" },
});
expect(
findRelationshipEdge(graph, {
kind: "import",
specifier: "conditional",
resolvedPath: "node_modules/conditional/cjs.cjs",
localName: "selected",
}),
).toBeDefined();
expect(
findRelationshipEdge(graph, {
kind: "re-export",
specifier: "conditional",
resolvedPath: "node_modules/conditional/cjs.cjs",
}),
).toBeDefined();
expect(
graph.edges.find(
({ relation, properties }) =>
relation === "imports" &&
properties.kind === "dynamic-import" &&
properties.specifier === "conditional",
),
).toMatchObject({
properties: {
resolved_path: "node_modules/conditional/esm.mjs",
},
});
expect(
findSourceModule(graph, "consumer.mts")?.observations[0]?.properties,
).toMatchObject({ module_format: "esm" });
expect(localRequireEdge("consumer.mts")).toMatchObject({
properties: { resolved_path: "local.js", local_name: "local" },
});
});
});
describe("CommonJS and ESM module relationships", () => {
it("composes bindings, re-exports, dynamic imports, and JSON modules", async () => {
const root = await moduleFixture();