99 lines
5.4 KiB
TypeScript
99 lines
5.4 KiB
TypeScript
import { expect } from "vitest";
|
|
|
|
import {
|
|
verifyJavaScriptReturnShapes,
|
|
type JavaScriptReturnFields,
|
|
} from "../../fixtures/javascriptReturnShapes.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
const stores = {
|
|
object: "const o = {a: {x: 0}}; o.a = shared; o.a.x = 2;",
|
|
array: "const arr = [{x: 0}]; arr[0] = shared; arr[0].x = 2;",
|
|
captured: "const o = {a: {x: 0}}; o.a = shared; const t = o.a; t.x = 2;",
|
|
nested: "const o = {a: {b: {x: 0}}}; o.a.b = shared; o.a.b.x = 2;",
|
|
quoted: 'const o = {}; o["a/b~c"] = shared; o["a/b~c"].x = 2;',
|
|
dynamic: 'const o = {}; const key = "entry"; o[key] = shared; o[key].x = 2;',
|
|
receiverAlias: "const o = {}; const t = o; t.a = shared; o.a.x = 2;",
|
|
transitive: "const p = {}; const o = {}; o.a = p; p.b = shared; o.a.b.x = 2;",
|
|
overwritten: "const o = {}; o.a = {x: 0}; o.a = shared; o.a.x = 2;",
|
|
replacedReference:
|
|
"const o = {}; const other = {x: 0}; o.a = other; o.a = shared; o.a.x = 2;",
|
|
copiedDestination:
|
|
"const o = {}; o.a = shared; const copy = {...o}; copy.a.x = 2;",
|
|
iteration: "const arr = []; arr[0] = shared; for (const t of arr) t.x = 2;",
|
|
nestedArray: "const o = {}; o.a = [shared]; o.a[0].x = 2;",
|
|
orAssignment: "const o = {a: null}; o.a ||= shared; o.a.x = 2;",
|
|
nullishAssignment: "const o = {a: null}; o.a ??= shared; o.a.x = 2;",
|
|
andAssignment: "const o = {a: {x: 0}}; o.a &&= shared; o.a.x = 2;",
|
|
objectPattern: "const o = {}; ({child: o.a} = {child: shared}); o.a.x = 2;",
|
|
arrayPattern: "const o = {}; [o.a] = [shared]; o.a.x = 2;",
|
|
defaultPattern: "const o = {}; ({child: o.a = shared} = {}); o.a.x = 2;",
|
|
objectRestMember:
|
|
"const o = {}; ({...o.a} = {child: shared}); o.a.child.x = 2;",
|
|
arrayRestMember: "const o = {}; [...o.a] = [shared]; o.a[0].x = 2;",
|
|
iteratedObjectRest:
|
|
"const o = {}; for ({...o.a} of [{child: shared}]) o.a.child.x = 2;",
|
|
iteratedArrayRest: "const o = {}; for ([...o.a] of [[shared]]) o.a[0].x = 2;",
|
|
classField:
|
|
"class Holder {} const o = new Holder(); o.a = shared; o.a.x = 2;",
|
|
};
|
|
const source = [
|
|
...Object.entries(stores).map(
|
|
([name, body]) =>
|
|
`export function ${name}() { const shared = {x: 1}; ${body} return shared.x; }`,
|
|
),
|
|
"export function primitive() { const n = 1; const o = {}; o.a = n; o.a = 2; return n; }",
|
|
"export function nestedChild() { const shared = {inner: {x: 1}}; const o = {}; o.a = shared; o.a.inner.x = 2; return shared.inner.x; }",
|
|
"export function readOnlyStore() { const shared = {x: 1}; const o = {}; o.a = shared; const captured = o.a.x; return shared.x; }",
|
|
"export function copiedScalar() { const shared = {x: 1}; const o = {}; o.a = {...shared}; o.a.x = 2; return shared.x; }",
|
|
"export function copiedArrayScalar() { const shared = {x: 1}; const o = {}; o.a = [shared.x]; o.a[0] = 2; return shared.x; }",
|
|
"export function unrelated() { const shared = {x: 1}; const other = {x: 9}; const o = {}; o.a = shared; o.a.x = 2; return other.x; }",
|
|
"export function copiedChild() { const child = {x: 1}; const parent = {child, keep: 7}; const o = {}; o.a = {...parent}; o.a.child.x = 2; return {child: child.x, keep: parent.keep}; }",
|
|
"export function objectRestExclusion() { const child = {x: 1}; const skip = {x: 3}; const parent = {child, skip, keep: 7}; const o = {}; let ignored; ({skip: ignored, ...o.a} = parent); o.a.child.x = 2; return {child: child.x, skip: skip.x, keep: parent.keep}; }",
|
|
"export function arrayRestOffset() { const child = {x: 1}; const skip = {x: 3}; const items = [skip, child]; const o = {}; let ignored; [ignored, ...o.a] = items; o.a[0].x = 2; return {child: child.x, skip: items[0].x}; }",
|
|
].join("\n");
|
|
|
|
const assertReturns = (fields: JavaScriptReturnFields): void => {
|
|
for (const name of [...Object.keys(stores), "nestedChild"]) {
|
|
const field = fields(name).find(({ path }) => path === "");
|
|
expect(field, name).toBeDefined();
|
|
expect(field?.state, name).toMatch(/^(unknown|literal)$/);
|
|
if (field?.state === "literal") expect(field.value, name).toBe(2);
|
|
}
|
|
const readOnly = fields("readOnlyStore").find(({ path }) => path === "");
|
|
expect(readOnly?.state).toMatch(/^(unknown|literal)$/);
|
|
if (readOnly?.state === "literal") expect(readOnly.value).toBe(1);
|
|
for (const [name, value] of Object.entries({
|
|
primitive: 1,
|
|
copiedScalar: 1,
|
|
copiedArrayScalar: 1,
|
|
unrelated: 9,
|
|
}))
|
|
expect(fields(name), name).toContainEqual(
|
|
expect.objectContaining({ path: "", state: "literal", value }),
|
|
);
|
|
const child = fields("copiedChild").find(({ path }) => path === "/child");
|
|
expect(child?.state).toMatch(/^(unknown|literal)$/);
|
|
if (child?.state === "literal") expect(child.value).toBe(2);
|
|
expect(fields("copiedChild")).toContainEqual(
|
|
expect.objectContaining({ path: "/keep", state: "literal", value: 7 }),
|
|
);
|
|
for (const name of ["objectRestExclusion", "arrayRestOffset"]) {
|
|
const child = fields(name).find(({ path }) => path === "/child");
|
|
expect(child?.state, name).toMatch(/^(unknown|literal)$/);
|
|
if (child?.state === "literal") expect(child.value, name).toBe(2);
|
|
expect(fields(name), name).toContainEqual(
|
|
expect.objectContaining({ path: "/skip", state: "literal", value: 3 }),
|
|
);
|
|
}
|
|
expect(fields("objectRestExclusion")).toContainEqual(
|
|
expect.objectContaining({ path: "/keep", state: "literal", value: 7 }),
|
|
);
|
|
};
|
|
|
|
cliTest(
|
|
"does not report stale literals after storing references in member slots",
|
|
({ cli }) => verifyJavaScriptReturnShapes(cli, source, assertReturns),
|
|
120_000,
|
|
);
|