Use the canonical cooked literal value for for-in and for-of string cardinality, preserving possible assignments and prototype uncertainty across quoted and template spellings. Validation: 473 focused tests across six files, including public CLI assignment Evidence and exported return shapes. Refs #4617
76 lines
3.1 KiB
TypeScript
76 lines
3.1 KiB
TypeScript
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
cliTest(
|
|
"preserves template loop assignments in public semantic Evidence",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-js-iteration-cardinality-");
|
|
await writeFile(
|
|
join(root, "main.js"),
|
|
`
|
|
export function template() { let selected = "before"; for ([selected] in \`abc\`) {} return selected; }
|
|
export function quoted() { let selected = "before"; for ([selected] in "abc") {} return selected; }
|
|
export function empty() { let selected = "before"; for ([selected] in \`\`) {} return selected; }
|
|
export function ordinaryTemplate() { let selected = "before"; for (selected in \`abc\`) {} return selected; }
|
|
for (const [templateKey] in \`abc\`) { void templateKey; }
|
|
for (const [quotedKey] in "abc") { void quotedKey; }
|
|
`,
|
|
);
|
|
const output = await cli.run({
|
|
arguments: ["analyze-javascript-application", root, "--json"],
|
|
});
|
|
expect(output.exitCode).toBe(0);
|
|
const envelope = z
|
|
.object({ normalized_result: javascriptApplicationAnalysisResultSchema })
|
|
.parse(output.json);
|
|
const result = envelope.normalized_result;
|
|
const shapeSchema = z.object({
|
|
semantic_role: z.literal("export-return-shapes"),
|
|
exported_name: z.string(),
|
|
static_return_shapes: z.array(
|
|
z.object({
|
|
fields: z.array(z.object({ state: z.string(), value: z.unknown() })),
|
|
}),
|
|
),
|
|
});
|
|
const shapes = result.graph.nodes.flatMap(({ observations }) =>
|
|
observations.flatMap(({ properties }) => {
|
|
const parsed = shapeSchema.safeParse(properties);
|
|
return parsed.success ? [parsed.data] : [];
|
|
}),
|
|
);
|
|
for (const name of ["template", "quoted", "ordinaryTemplate"]) {
|
|
const shape = shapes.find(({ exported_name }) => exported_name === name);
|
|
expect(shape?.static_return_shapes[0]?.fields[0]).toMatchObject({
|
|
state: "unknown",
|
|
value: null,
|
|
});
|
|
}
|
|
expect(
|
|
shapes.find(({ exported_name }) => exported_name === "empty")
|
|
?.static_return_shapes[0]?.fields[0],
|
|
).toMatchObject({ state: "literal", value: "before" });
|
|
const graph = result.semantic_graph;
|
|
for (const label of ["templateKey", "quotedKey"]) {
|
|
const binding = graph.nodes.find(
|
|
(node) => node.kind === "binding" && node.label === label,
|
|
);
|
|
expect(binding).toBeDefined();
|
|
const assignment = graph.relations.find(
|
|
(relation) =>
|
|
relation.relation === "defines" &&
|
|
relation.target_node_id === binding?.node_id &&
|
|
graph.nodes.find((node) => node.node_id === relation.source_node_id)
|
|
?.label === "assignment",
|
|
);
|
|
expect(assignment).toBeDefined();
|
|
}
|
|
},
|
|
);
|