rea/tests/boundary/cli/javascriptSelfImportRelatio...

223 lines
7.7 KiB
TypeScript

import { expect } from "vitest";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
import {
findExportNode,
findGraphEdge,
findSourceModule,
writeFixtureFiles,
} from "../../support/javascriptApplicationFixture.js";
cliTest(
"resolves emitted module imports without substituting Electron runtime paths",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-typescript-runtime-paths-");
await writeFixtureFiles(root, {
"package.json": JSON.stringify({ main: "main.ts" }),
"main.ts": `import { BrowserWindow, utilityProcess } from "electron";
import "./worker.js";
import "./config.mjs";
import "./legacy.cjs";
import "./view.js";
import "./runtime.js";
const win = new BrowserWindow({webPreferences:{preload:"./worker.js"}});
win.loadURL("./renderer.js");
utilityProcess.fork("./worker.js");
utilityProcess.fork("./runtime.js");
throw new Error("source must stay inert");`,
"worker.ts": "export const worker = 1;",
"config.mts": "export const config = 1;",
"legacy.cts": "export const legacy = 1;",
"view.tsx": "export const view = <div />;",
"renderer.ts": "export const renderer = 1;",
"runtime.ts": "export const source = 1;",
"runtime.js": "throw new Error('runtime must stay inert');",
});
const output = await cli.run({
arguments: ["analyze-javascript-application", root, "--json"],
});
expect(output.exitCode, output.stderr).toBe(0);
const result = javascriptApplicationAnalysisResultSchema.parse(
parseEvidence(output.json).normalized_result,
);
const { graph } = result;
const source = findSourceModule(graph, "main.ts");
for (const [specifier, target] of [
["./worker.js", "worker.ts"],
["./config.mjs", "config.mts"],
["./legacy.cjs", "legacy.cts"],
["./view.js", "view.tsx"],
["./runtime.js", "runtime.js"],
] as const)
expect(
findGraphEdge(graph, source, findSourceModule(graph, target), "imports")
?.properties,
).toMatchObject({ specifier, resolved_path: target });
expect(result.summary).toMatchObject({
utility_processes: 2,
resolved_utility_entrypoints: 1,
});
expect(
graph.edges.find(
(edge) =>
edge.evidence.extractor.operation ===
"associate-browser-window-preload",
)?.properties,
).toMatchObject({
declared_path: "./worker.js",
resolved_path: null,
resolution_status: "not-found",
});
expect(
graph.edges
.filter(
(edge) =>
edge.properties.declared_path === "./worker.js" ||
edge.properties.declared_path === "./renderer.js",
)
.some((edge) => edge.properties.resolved_path != null),
).toBe(false);
},
);
cliTest(
"returns literal self-import evidence while preserving sibling imports and self-re-exports",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-self-import-");
await writeFixtureFiles(root, {
"a.js":
'import "./a.js";\nexport const value = 1;\nexport { value as forwarded } from "./a.js";\n',
"b.js": 'import { value } from "./a.js";\nconsole.log(value);\n',
});
const output = await cli.run({
arguments: ["analyze-javascript-application", root, "--json"],
});
expect(output.exitCode).toBe(0);
const result = javascriptApplicationAnalysisResultSchema.parse(
parseEvidence(output.json).normalized_result,
);
const { graph } = result;
expect(
graph.edges.filter((edge) => edge.source_node_id === edge.target_node_id),
).toEqual([]);
expect(graph.limitations).toContainEqual(
expect.stringMatching(/^1 import specifier .*was omitted;/u),
);
expect(result.limitations).toEqual(
expect.arrayContaining(graph.limitations),
);
const source = findSourceModule(graph, "a.js");
expect(source).toBeDefined();
expect(
findGraphEdge(graph, findSourceModule(graph, "b.js"), source, "imports")
?.properties,
).toMatchObject({
specifier: "./a.js",
resolution_status: "resolved",
});
expect(
findGraphEdge(
graph,
findExportNode(graph, "a.js", "forwarded"),
source,
"imports",
)?.properties,
).toMatchObject({
specifier: "./a.js",
resolution_status: "resolved",
});
expect(
graph.edges.find(
(edge) =>
edge.source_node_id === source?.node_id &&
edge.properties.kind === "static-import" &&
edge.properties.specifier === "./a.js" &&
edge.evidence.location.available &&
edge.evidence.location.value.kind === "source-range" &&
edge.evidence.location.value.start.line === 1,
)?.evidence.location,
).toMatchObject({
available: true,
value: { kind: "source-range", source: "a.js", start: { line: 1 } },
});
},
);
cliTest(
"discloses CommonJS and TypeScript self-imports including emitted-name rewrites",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-self-require-");
await writeFixtureFiles(root, {
"c.cjs": 'const self = require("./c.cjs");\nmodule.exports = self;\n',
"util.ts":
'import "./util.ts";\nimport "./util.js";\nexport const value = 1;\n',
});
const output = await cli.run({
arguments: ["analyze-javascript-application", root, "--json"],
});
expect(output.exitCode).toBe(0);
const result = javascriptApplicationAnalysisResultSchema.parse(
parseEvidence(output.json).normalized_result,
);
const { graph } = result;
expect(
graph.edges.filter((edge) => edge.source_node_id === edge.target_node_id),
).toEqual([]);
// ./util.ts is a literal self-import and ./util.js resolves under NodeNext
// emitted-name semantics back to util.ts itself, as ./c.cjs does for CJS.
expect(graph.limitations).toContainEqual(
expect.stringMatching(/^3 import specifiers .*were omitted;/u),
);
expect(result.limitations).toEqual(
expect.arrayContaining(graph.limitations),
);
for (const [path, specifier] of [
["c.cjs", "./c.cjs"],
["util.ts", "./util.ts"],
] as const) {
const source = findSourceModule(graph, path);
expect(source).toBeDefined();
expect(
graph.edges.find(
(edge) =>
edge.source_node_id === source?.node_id &&
edge.properties.specifier === specifier &&
edge.properties.resolved_path === path,
)?.evidence.location,
).toMatchObject({
available: true,
value: { kind: "source-range", source: path, start: { line: 1 } },
});
expect(
graph.nodes.some((node) =>
node.observations.some(
({ properties }) =>
properties.semantic_role === "module-reference" &&
properties.declared_specifier === specifier,
),
),
).toBe(false);
}
expect(
graph.edges.some(
(edge) =>
edge.properties.specifier === "./util.js" &&
(edge.properties.module_link_kind === "import" ||
edge.properties.resolution_status === "not-found"),
),
).toBe(false);
expect(
graph.nodes.some((node) =>
node.observations.some(
({ properties }) =>
properties.semantic_role === "module-reference" &&
properties.declared_specifier === "./util.js",
),
),
).toBe(false);
},
);