Files
rea/src/domain/javascript/javascriptSemanticAnalysis.getter_expansion.test.ts
T
morluto 0c7b6d834a fix(javascript): bound local getter-result expansion
Charge inner alias and branch work to the existing getter query budget before allocating queued states. Widen local suffixes during fallback so a single getter cannot reintroduce path permutations.

Validation: two new class/object controls fail on the baseline; 411 tests across eight affected files pass. Public CLI baseline/candidate controls preserve unrelated observations and expose intended partial resource coverage. Typecheck/lint, docs and formatting pass. Diagnostic costs retained; no speed or token reduction claim. This fixes a separate getter gap and does not resolve the getter-free Qodo input. Refs #3839.
2026-10-17 06:58:19 +08:00

125 lines
3.9 KiB
TypeScript

import { expect, it } from "vitest";
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
import { onlyBinding } from "./javascriptSemanticAnalysis.fixture.js";
const permutationSource = (count: number, isStatic: boolean) => {
const getterPrefix = isStatic ? "static " : "";
const access = isStatic ? "Request.field0" : "new Request().field0";
const getters = Array.from(
{ length: count },
(_, index) =>
`${getterPrefix}get field${index}() { return this[key].field${index}; }`,
).join("\n");
return `
import imported from "dep";
const shared = { value: 1 };
class Request {
${getters}
${getterPrefix}get state() { return shared; }
}
consume(${access});
const result = shared.value;
const unrelated = 42;
`;
};
const expectRetainedFacts = (
ir: ReturnType<typeof analyzeJavaScriptSemantics>,
) => {
expect(onlyBinding(ir, "unrelated").value).toEqual({
status: "literal",
value: 42,
});
expect(onlyBinding(ir, "imported").provenance.status).toBe("module");
expect(ir.moduleLinks).toContainEqual(
expect.objectContaining({ kind: "import", specifier: "dep" }),
);
};
it.each([false, true])(
"bounds unknown-key %s getter permutations and preserves unrelated facts",
(isStatic) => {
const ir = analyzeJavaScriptSemantics(permutationSource(10, isStatic));
expect(ir.coverage).toMatchObject({
status: "partial",
omittedCount: null,
resourceLimits: ["getter-expansion"],
});
expect(onlyBinding(ir, "result").value.status).toBe("unknown");
expect(onlyBinding(ir, "result").provenance).toMatchObject({
status: "local",
origins: [],
});
expect(ir.limitations.join(" ")).toMatch(
/Getter expansion was conservatively bounded/,
);
expectRetainedFacts(ir);
},
);
it("keeps direct field writes through an escaped growing getter result", () => {
const ir = analyzeJavaScriptSemantics(`
import imported from "dep";
const shared = { x: 1 };
class Request {
slot = shared;
get grow() { return this.grow.more; }
}
new Request()[key].x = 2;
const result = shared.x;
const unrelated = 42;
`);
expect(onlyBinding(ir, "result").value.status).toBe("unknown");
expect(ir.coverage.resourceLimits).toEqual(["getter-expansion"]);
expectRetainedFacts(ir);
});
it("escapes constructed fields when a getter query widens its suffix", () => {
const ir = analyzeJavaScriptSemantics(`
import imported from "dep";
const shared = { value: 1 };
class Inner { alias = shared; }
class Outer {
get result() { return new Inner(); }
get grow() { return this.grow.more; }
}
new Outer()[key].alias.value = 2;
const result = shared.value;
const unrelated = 42;
`);
expect(onlyBinding(ir, "result").value.status).toBe("unknown");
expect(ir.coverage.resourceLimits).toEqual(["getter-expansion"]);
expectRetainedFacts(ir);
});
it.each(["class", "object"] as const)(
"bounds branching local aliases inside one %s getter",
(receiver) => {
const aliases = Array.from(
{ length: 10 },
(_, index) =>
`const a${index + 1} = flag ? a${index}.left : a${index}.right;`,
).join("\n");
const getter = `get state() { const a0 = this; ${aliases} return a10; }`;
const definition =
receiver === "class"
? `class Request { slot = shared; ${getter} } const request = new Request();`
: `const request = { slot: shared, ${getter} };`;
const ir = analyzeJavaScriptSemantics(`
import imported from "dep";
const shared = { value: 1 };
${definition}
consume(request.state);
const result = shared.value;
const unrelated = 42;
`);
expect(ir.coverage).toMatchObject({
status: "partial",
resourceLimits: ["getter-expansion"],
});
expect(onlyBinding(ir, "result").value.status).toBe("unknown");
expectRetainedFacts(ir);
},
);