mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
148 lines
5.1 KiB
TypeScript
148 lines
5.1 KiB
TypeScript
import { applicationFeatureTraceResultSchema } from "../../../src/domain/javascript/javascriptFeatureTraceSchemas.js";
|
|
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect } from "vitest";
|
|
|
|
import { analysisViewResultSchema } from "../../../src/domain/analysisView/analysisView.js";
|
|
import { parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest, runFileCli } from "../../support/cli/cliFixture.js";
|
|
|
|
cliTest(
|
|
"pages only module units for a CommonJS file that exports symbols",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-js-export-binding-view-");
|
|
await writeFile(
|
|
join(root, "package.json"),
|
|
JSON.stringify({ name: "export-binding-view", version: "1.0.0" }),
|
|
);
|
|
await writeFile(
|
|
join(root, "index.js"),
|
|
[
|
|
"exports.alpha = function alpha() { return 1; };",
|
|
"exports.beta = function beta() { return 2; };",
|
|
].join("\n"),
|
|
);
|
|
const analyzed = await runFileCli(cli, {
|
|
arguments: ["analyze-javascript-application", root, "--format", "json"],
|
|
});
|
|
expect(analyzed.exitCode, analyzed.stdout + analyzed.stderr).toBe(0);
|
|
const evidence = parseEvidence(analyzed.json);
|
|
const nodeIds = (
|
|
evidence.normalized_result as {
|
|
graph: {
|
|
nodes: { node_id: string; observations: { properties: unknown }[] }[];
|
|
};
|
|
}
|
|
).graph.nodes;
|
|
const exportBindingIds = nodeIds
|
|
.filter(({ observations }) =>
|
|
observations.some(
|
|
({ properties }) =>
|
|
(properties as Record<string, unknown>).semantic_role ===
|
|
"export-binding",
|
|
),
|
|
)
|
|
.map(({ node_id }) => node_id);
|
|
expect(exportBindingIds).toHaveLength(2);
|
|
const traceInput = join(root, "trace.json");
|
|
for (const seed of [
|
|
{ kind: "module", value: "index.js", match: "contains" },
|
|
{ kind: "node-id", value: exportBindingIds[0], match: "exact" },
|
|
]) {
|
|
await writeFile(
|
|
traceInput,
|
|
JSON.stringify({ application: evidence, seed }),
|
|
);
|
|
const traced = await runFileCli(cli, {
|
|
arguments: ["trace-application-feature", traceInput, "--json"],
|
|
});
|
|
expect(traced.exitCode, traced.stdout + traced.stderr).toBe(0);
|
|
const trace = applicationFeatureTraceResultSchema.parse(
|
|
parseEvidence(traced.json).normalized_result,
|
|
);
|
|
expect(trace.seed_matches.length).toBeGreaterThan(0);
|
|
if (seed.kind === "module") {
|
|
expect(
|
|
trace.seed_matches.filter((match) =>
|
|
exportBindingIds.includes(match.node_id),
|
|
),
|
|
).toEqual([]);
|
|
} else {
|
|
expect(trace.seed_matches.map((match) => match.node_id)).toEqual([
|
|
exportBindingIds[0],
|
|
]);
|
|
}
|
|
}
|
|
const evidencePath = join(root, "evidence.json");
|
|
await writeFile(evidencePath, JSON.stringify(evidence));
|
|
const queryPath = join(root, "query.json");
|
|
|
|
const inspect = async (view: Record<string, unknown>) => {
|
|
await writeFile(
|
|
queryPath,
|
|
JSON.stringify({
|
|
source: { kind: "evidence-file", path: evidencePath },
|
|
view,
|
|
}),
|
|
);
|
|
const result = await runFileCli(cli, {
|
|
arguments: ["inspect-analysis-view", queryPath, "--json"],
|
|
});
|
|
expect(result.exitCode, result.stdout + result.stderr).toBe(0);
|
|
return analysisViewResultSchema.parse(
|
|
parseEvidence(result.json).normalized_result,
|
|
);
|
|
};
|
|
|
|
const page = await inspect({
|
|
kind: "page",
|
|
collection: "modules",
|
|
offset: 0,
|
|
limit: 100,
|
|
});
|
|
if (page.kind !== "page") throw new Error("expected modules page");
|
|
expect(page.items).toContainEqual(
|
|
expect.objectContaining({ path: "index.js" }),
|
|
);
|
|
const pageNodeIds = page.items.map((item) =>
|
|
String((item as { node_id?: unknown }).node_id),
|
|
);
|
|
expect(
|
|
pageNodeIds.filter((nodeId) => exportBindingIds.includes(nodeId)),
|
|
).toEqual([]);
|
|
expect(page.coverage).toMatchObject({
|
|
total: page.items.length,
|
|
examined: page.items.length,
|
|
exhausted: true,
|
|
});
|
|
|
|
// The remaining path ambiguity names only module units, never export
|
|
// bindings, so an ambiguous selector stays actionable.
|
|
await writeFile(
|
|
queryPath,
|
|
JSON.stringify({
|
|
source: { kind: "evidence-file", path: evidencePath },
|
|
view: {
|
|
kind: "item",
|
|
collection: "modules",
|
|
selector: { path: "index.js" },
|
|
},
|
|
}),
|
|
);
|
|
const item = await cli.run({
|
|
arguments: ["inspect-analysis-view", queryPath, "--json"],
|
|
});
|
|
expect(item.exitCode).not.toBe(0);
|
|
const failure = JSON.parse(item.stdout) as {
|
|
details?: { issues?: { expected?: { node_id: string }[] }[] };
|
|
};
|
|
const candidates =
|
|
failure.details?.issues?.flatMap(({ expected }) => expected ?? []) ?? [];
|
|
expect(candidates.length).toBeGreaterThan(0);
|
|
expect(
|
|
candidates.filter(({ node_id }) => exportBindingIds.includes(node_id)),
|
|
).toEqual([]);
|
|
},
|
|
);
|