mirror of
https://github.com/morluto/rea
synced 2026-10-26 22:28:27 -04:00
6183544e8b
Use the shared binding-at-read owner instead of grouping schedules across the whole binding lifetime. Match selected initializer nodes, retain uncertain origins as candidate links, and preserve immutable closure captures. Adapt the diagnosis in PR #3794 by @Xinglan233, whose source commit was authored by Codex. Verify stale writes, later schedules, replacements and CLI graph links. Refs #3358, #3794 Co-authored-by: Codex <codex@openai.com>
90 lines
2.3 KiB
TypeScript
90 lines
2.3 KiB
TypeScript
import { expect, it } from "vitest";
|
|
|
|
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
|
|
|
|
const timerOperations = (source: string) =>
|
|
analyzeJavaScriptSemantics(source).timerOperations;
|
|
|
|
it("does not link cancellation to a timer after its handle is cleared", () => {
|
|
const operations = timerOperations(`
|
|
let handle = setTimeout(work, 10);
|
|
handle = null;
|
|
clearTimeout(handle);
|
|
`);
|
|
|
|
expect(operations.at(-1)).toMatchObject({
|
|
kind: "cancel",
|
|
linkedTimerId: null,
|
|
resolution: "unresolved",
|
|
});
|
|
});
|
|
|
|
it("links cancellation to the latest preceding timer assignment", () => {
|
|
const operations = timerOperations(`
|
|
let handle = setTimeout(first, 10);
|
|
handle = setTimeout(second, 20);
|
|
clearTimeout(handle);
|
|
`);
|
|
|
|
expect(operations[2]).toMatchObject({
|
|
kind: "cancel",
|
|
linkedTimerId: operations[1]?.timerId,
|
|
resolution: "complete",
|
|
});
|
|
});
|
|
|
|
it("does not link cancellation to a timer scheduled later", () => {
|
|
const operations = timerOperations(`
|
|
let handle;
|
|
clearTimeout(handle);
|
|
handle = setTimeout(work, 10);
|
|
`);
|
|
|
|
expect(operations[0]).toMatchObject({
|
|
kind: "cancel",
|
|
linkedTimerId: null,
|
|
resolution: "unresolved",
|
|
});
|
|
});
|
|
|
|
it("preserves an immutable timer handle captured by a closure", () => {
|
|
const operations = timerOperations(`
|
|
const handle = setTimeout(work, 10);
|
|
const cancel = () => clearTimeout(handle);
|
|
`);
|
|
|
|
expect(operations[1]).toMatchObject({
|
|
kind: "cancel",
|
|
linkedTimerId: operations[0]?.timerId,
|
|
resolution: "complete",
|
|
});
|
|
});
|
|
|
|
it("keeps cancellation uncertain after a conditional handle replacement", () => {
|
|
const operations = timerOperations(`
|
|
let handle = setTimeout(work, 10);
|
|
if (condition) handle = null;
|
|
clearTimeout(handle);
|
|
`);
|
|
|
|
expect(operations.at(-1)).toMatchObject({
|
|
kind: "cancel",
|
|
linkedTimerId: operations[0]?.timerId,
|
|
resolution: "partial",
|
|
});
|
|
});
|
|
|
|
it("keeps cancellation uncertain across a mutable closure capture", () => {
|
|
const operations = timerOperations(`
|
|
let handle = setTimeout(work, 10);
|
|
const cancel = () => clearTimeout(handle);
|
|
handle = null;
|
|
`);
|
|
|
|
expect(operations[1]).toMatchObject({
|
|
kind: "cancel",
|
|
linkedTimerId: operations[0]?.timerId,
|
|
resolution: "partial",
|
|
});
|
|
});
|