mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
182 lines
5.2 KiB
TypeScript
182 lines
5.2 KiB
TypeScript
import { expect, it } from "vitest";
|
|
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
|
|
import { onlyCallable } from "./javascriptSemanticAnalysis.fixture.js";
|
|
|
|
const returned = (body: string) =>
|
|
onlyCallable(
|
|
analyzeJavaScriptSemantics(`export function observe() { ${body} }`),
|
|
"observe",
|
|
).returnSites[0]?.value;
|
|
|
|
it.each([
|
|
["ordinary read", "box.trigger;"],
|
|
["optional read", "box?.trigger;"],
|
|
["compound assignment", "box.trigger += 1;"],
|
|
["update", "box.trigger++;"],
|
|
])("invalidates a receiver after a mutating getter's %s", (_name, read) => {
|
|
expect(
|
|
returned(`
|
|
const box = { value: 1, get trigger() { this.value = 2; return 0; } };
|
|
${read}
|
|
return box.value;
|
|
`),
|
|
).toMatchObject({ status: "unknown" });
|
|
});
|
|
|
|
it("follows inherited getter selection while preserving own-property shadowing", () => {
|
|
const prototype = "{ get trigger() { this.value = 2; return 0; } }";
|
|
expect(
|
|
returned(`
|
|
const prototype = ${prototype};
|
|
const box = { __proto__: prototype, value: 1 };
|
|
box.trigger;
|
|
return box.value;
|
|
`),
|
|
).toMatchObject({ status: "unknown" });
|
|
expect(
|
|
returned(`
|
|
const box = { __proto__: ${prototype}, value: 1, trigger: 0 };
|
|
box.trigger;
|
|
return box.value;
|
|
`),
|
|
).toEqual({ status: "literal", value: 1 });
|
|
});
|
|
|
|
it.each([
|
|
["simple store", "box.trigger = 2;"],
|
|
["destructuring store", "({ trigger: box.trigger } = { trigger: 2 });"],
|
|
["delete", "delete box.trigger;"],
|
|
["later data property", "box.trigger;", ", trigger: 0"],
|
|
])("does not invoke a getter for %s", (_name, operation, later = "") => {
|
|
expect(
|
|
returned(`
|
|
const box = { value: 1, get trigger() { this.value = 2; return 0; } ${later} };
|
|
${operation}
|
|
return box.value;
|
|
`),
|
|
).toEqual({ status: "literal", value: 1 });
|
|
});
|
|
|
|
it("preserves an inert getter and a primitive sibling outside the receiver", () => {
|
|
expect(
|
|
returned(`
|
|
const box = { value: 1, get trigger() { return this.value; } };
|
|
box.trigger;
|
|
return box.value;
|
|
`),
|
|
).toEqual({ status: "literal", value: 1 });
|
|
expect(
|
|
returned(`
|
|
const outer = { kept: "stable", rows: [{ value: 1, get trigger() { this.value = 2; } }] };
|
|
outer.rows[0].trigger;
|
|
return outer.kept;
|
|
`),
|
|
).toEqual({ status: "literal", value: "stable" });
|
|
});
|
|
|
|
it.each([
|
|
[
|
|
"array element",
|
|
"const rows = [{ value: 1, set trigger(value) { this.value = value; } }];",
|
|
"rows[0]",
|
|
],
|
|
[
|
|
"inherited setter",
|
|
"const box = { __proto__: { set trigger(value) { this.value = value; } }, value: 1 };",
|
|
"box",
|
|
],
|
|
[
|
|
"aliased nested receiver",
|
|
"const box = { value: 1, set trigger(value) { this.value = value; } }; const outer = { box };",
|
|
"outer.box",
|
|
],
|
|
])(
|
|
"invalidates the selected %s receiver on assignment",
|
|
(_name, setup, receiver) => {
|
|
expect(
|
|
returned(`${setup} ${receiver}.trigger = 2; return ${receiver}.value;`),
|
|
).toMatchObject({ status: "unknown" });
|
|
},
|
|
);
|
|
|
|
it("does not inherit a setter through an own data property or copied descriptor", () => {
|
|
for (const box of [
|
|
"{ __proto__: { set trigger(value) { this.value = value; } }, value: 1, trigger: 0 }",
|
|
"{ ...{ set trigger(value) { this.value = value; } }, value: 1 }",
|
|
]) {
|
|
expect(
|
|
returned(`const box = ${box}; box.trigger = 2; return box.value;`),
|
|
).toEqual({ status: "literal", value: 1 });
|
|
}
|
|
});
|
|
|
|
it("follows getter-to-getter invocation effects with the selected receiver", () => {
|
|
expect(
|
|
returned(`
|
|
const box = {
|
|
value: 1,
|
|
get trigger() { return this.trigger2; },
|
|
get trigger2() { this.value = 2; return 0; },
|
|
};
|
|
box.trigger;
|
|
return box.value;
|
|
`),
|
|
).toMatchObject({ status: "unknown" });
|
|
});
|
|
|
|
it.each([
|
|
"const other = {}; other.x = 2; return this.value;",
|
|
"const unused = () => { this.value = 2; }; return 0;",
|
|
"const outer = this; const unused = () => outer.value++; return 0;",
|
|
])(
|
|
"keeps the receiver exact when getter effects do not reach it: %s",
|
|
(body) => {
|
|
expect(
|
|
returned(`
|
|
const box = { value: 1, get trigger() { ${body} } };
|
|
box.trigger;
|
|
return box.value;
|
|
`),
|
|
).toEqual({ status: "literal", value: 1 });
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
"(() => { this.value = 2; })(); return 0;",
|
|
"const change = () => { this.value = 2; }; change(); return 0;",
|
|
"const outer = this; function change() { outer.value = 2; } change(); return 0;",
|
|
"const a = { box: this }; a.box.value = 2; return 0;",
|
|
"const outer = this; function Change() { outer.value = 2; } new Change(); return 0;",
|
|
"const tag = () => { this.value = 2; }; tag``; return 0;",
|
|
])(
|
|
"retains effects through invoked local callables and containers: %s",
|
|
(body) => {
|
|
expect(
|
|
returned(`
|
|
const box = { value: 1, get trigger() { ${body} } };
|
|
box.trigger;
|
|
return box.value;
|
|
`),
|
|
).toMatchObject({ status: "unknown" });
|
|
},
|
|
);
|
|
|
|
it("retains references returned from tagged invocation through the ordinary callee owner", () => {
|
|
expect(
|
|
returned(`
|
|
const shared = { value: 1 };
|
|
const tag = () => shared;
|
|
tag\`\`;
|
|
return shared.value;
|
|
`),
|
|
).toMatchObject({ status: "unknown" });
|
|
expect(
|
|
returned(`
|
|
const shared = { value: 1 };
|
|
const tag = () => 0;
|
|
tag\`\`;
|
|
return shared.value;
|
|
`),
|
|
).toEqual({ status: "literal", value: 1 });
|
|
});
|