Files
reverse-engineering-anything/src/domain/javascript/javascriptSemanticAnalysis.language_invocations.test.ts
T

150 lines
4.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([
"const copy = { ...state };",
"const { trigger } = state;",
"let trigger; ({ trigger } = state);",
"const { ...copy } = state;",
"const outer = { state }; const { state: { trigger } } = outer;",
])(
"indexes implicit getter invocation through the shared pattern/copy owner: %s",
(read) => {
expect(
returned(`
const state = { value: 1, get trigger() { this.value = 2; return 0; } };
${read}
return state.value;
`)?.status,
).toBe("unknown");
},
);
it("keeps copied own data properties separate from inherited getters and excluded rest keys", () => {
expect(
returned(`
const state = { __proto__: { get trigger() { this.value = 2; return 0; } }, value: 1 };
const copy = { ...state };
return state.value;
`),
).toEqual({ status: "literal", value: 1 });
expect(
returned(`
const state = { value: 1, get trigger() { return 0; } };
const { trigger, ...copy } = state;
return state.value;
`),
).toEqual({ status: "literal", value: 1 });
});
it.each([
"const key = { [state]: 0 };",
"const result = state + '';",
"const result = +state;",
"const result = `${state}`;",
])(
"retains receiver uncertainty after implicit primitive conversion: %s",
(operation) => {
expect(
returned(`
const state = { [Symbol.toPrimitive]() { this.value = 2; return 'key'; }, value: 1 };
${operation}
return state.value;
`)?.status,
).toBe("unknown");
},
);
it("keeps noncoercing operations and primitive operands precise", () => {
expect(
returned(`
const state = { [Symbol.toPrimitive]() { this.value = 2; return 'key'; }, value: 1 };
const same = state === state;
const truthy = !state;
const primitive = 2 + 3;
return state.value;
`),
).toEqual({ status: "literal", value: 1 });
});
it.each(["using", "await using"])(
"retains %s disposal effects at lexical scope exit",
(kind) => {
const source = `${kind === "await using" ? "async " : ""}function observe() {
const state = { [Symbol.dispose]() { this.value = 2; }, value: 1 };
{ ${kind} owned = state; }
return state.value;
}`;
const ir = analyzeJavaScriptSemantics(source);
expect(onlyCallable(ir, "observe").returnSites[0]?.value.status).toBe(
"unknown",
);
},
);
it("preserves scalar snapshots selected before disposal", () => {
expect(
returned(`
const state = { [Symbol.dispose]() { this.value = 2; }, value: 1 };
const snapshot = state.value;
{ using owned = state; }
return snapshot;
`),
).toEqual({ status: "literal", value: 1 });
expect(
returned(`
const state = { [Symbol.dispose]() { this.value = 2; }, value: 1 };
using owned = state;
return state.value;
`),
).toEqual({ status: "literal", value: 1 });
});
it("keeps the disposer receiver captured before an outer binding replacement", () => {
expect(
returned(`
let state = { [Symbol.dispose]() { this.value = 2; }, value: 1 };
const original = state;
{ using owned = state; state = { value: 3 }; }
return original.value;
`)?.status,
).toBe("unknown");
expect(
returned(`
const state = { [Symbol.dispose]() { this.value = 2; }, value: 1 };
{ using nothing = null; }
return state.value;
`),
).toEqual({ status: "literal", value: 1 });
});
it("projects supported class getter effects through inherited receiver fields", () => {
expect(
returned(`
const shared = { value: 1 };
class Base { get trigger() { this.state.value = 2; return 0; } }
class Box extends Base { state = shared; }
new Box().trigger;
return shared.value;
`)?.status,
).toBe("unknown");
});
it("retains shared child effects from selected setters", () => {
expect(
returned(`
const shared = { value: 1 };
const box = { shared, set trigger(value) { this.shared.value = value; } };
box.trigger = 2;
return shared.value;
`)?.status,
).toBe("unknown");
});