97 lines
2.9 KiB
TypeScript
97 lines
2.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
|
|
|
|
const observe = (source: string) => {
|
|
const ir = analyzeJavaScriptSemantics(source);
|
|
return {
|
|
binding: ir.bindings.find((item) => item.name === "x")?.value,
|
|
returned: ir.callables.find((item) => item.name === "read")?.returnSites[0]
|
|
?.value,
|
|
};
|
|
};
|
|
|
|
describe("JavaScript semantic analysis: logical assignment", () => {
|
|
it.each(["||=", "??=", "&&="])(
|
|
"keeps a parameter uncertain after %s",
|
|
(operator) => {
|
|
const { binding, returned } = observe(
|
|
`function read(x) { x ${operator} 2; return x; }`,
|
|
);
|
|
expect(binding?.status).toBe("unknown");
|
|
expect(returned?.status).toBe("unknown");
|
|
},
|
|
);
|
|
|
|
it.each(["||=", "??=", "&&="])(
|
|
"keeps a destructured parameter uncertain after %s",
|
|
(operator) => {
|
|
const { binding, returned } = observe(
|
|
`function read({ x }) { x ${operator} 2; return x; }`,
|
|
);
|
|
expect(binding?.status).toBe("unknown");
|
|
expect(returned?.status).toBe("unknown");
|
|
},
|
|
);
|
|
|
|
it.each(["||=", "??=", "&&="])(
|
|
"keeps a catch binding uncertain after %s",
|
|
(operator) => {
|
|
const { binding, returned } = observe(
|
|
`function read() { try { run(); } catch (x) { x ${operator} 2; return x; } }`,
|
|
);
|
|
expect(binding?.status).toBe("unknown");
|
|
expect(returned?.status).toBe("unknown");
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
["let", "||="],
|
|
["let", "??="],
|
|
["var", "||="],
|
|
["var", "??="],
|
|
])(
|
|
"keeps an uninitialized %s local exact after %s",
|
|
(declaration, operator) => {
|
|
const { binding, returned } = observe(
|
|
`function read() { ${declaration} x; x ${operator} 2; return x; }`,
|
|
);
|
|
expect(binding).toEqual({ status: "literal", value: 2 });
|
|
expect(returned).toEqual({ status: "literal", value: 2 });
|
|
},
|
|
);
|
|
|
|
it("keeps an unconditional parameter assignment exact", () => {
|
|
const { binding, returned } = observe(
|
|
"function read(x) { x = 2; return x; }",
|
|
);
|
|
expect(binding).toEqual({ status: "literal", value: 2 });
|
|
expect(returned).toEqual({ status: "literal", value: 2 });
|
|
});
|
|
|
|
it("keeps an unchanged parameter unknown", () => {
|
|
expect(observe("function read(x) { return x; }").returned?.status).toBe(
|
|
"unknown",
|
|
);
|
|
});
|
|
|
|
it("reproduces the issue #2646 inputs without an exact literal", () => {
|
|
for (const operator of ["||=", "??="]) {
|
|
const { binding, returned } = observe(
|
|
`function read(x) { x ${operator} 2; return x; }`,
|
|
);
|
|
expect(binding).not.toEqual({ status: "literal", value: 2 });
|
|
expect(returned).not.toEqual({ status: "literal", value: 2 });
|
|
}
|
|
const readOr = (x: number) => {
|
|
x ||= 2;
|
|
return x;
|
|
};
|
|
const readNullish = (x: number) => {
|
|
x ??= 2;
|
|
return x;
|
|
};
|
|
expect(readOr(5)).toBe(5);
|
|
expect(readNullish(5)).toBe(5);
|
|
});
|
|
});
|