mirror of
https://github.com/morluto/rea
synced 2026-10-26 14:25:49 -04:00
7436ac8e20
The semantic collectors admitted Node timers, fetch, WebSocket, JSON.parse and global coercions from the identifier spelling alone, so a source that replaced those globals (`setTimeout = () => null`) still produced resolved built-in effects. Require the existing isUnmodifiedGlobalReference guard, which the static analyzer already uses for fetch and WebSocket, before admitting these effects. The guard treats any visible possible write as a replacement without evaluating order or reachability, so computed global writes and guarded polyfills also remove the resolved effect. The workflow docs now say so. Fixes #3367
71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
import { expect } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
import { analyzeJavaScriptDirectory } from "../../support/cli/javascriptApplication.js";
|
|
import { writeFixtureFiles } from "../../support/javascriptApplicationFixture.js";
|
|
|
|
const EFFECT_RELATIONS = [
|
|
"schedules-timer",
|
|
"constructs-request",
|
|
"parses",
|
|
"coerces",
|
|
];
|
|
|
|
const SOURCE = `function work() {}
|
|
setTimeout(work, 100);
|
|
fetch('https://example.invalid');
|
|
new WebSocket('wss://example.invalid');
|
|
JSON.parse('null');
|
|
String(work);
|
|
`;
|
|
|
|
const REPLACEMENTS = `setTimeout = () => null;
|
|
fetch = () => null;
|
|
WebSocket = class {};
|
|
JSON.parse = () => null;
|
|
String = () => '';
|
|
`;
|
|
|
|
cliTest.for(["CLI", "stdio MCP"] as const)(
|
|
"does not resolve built-in effects after visible global replacements through %s",
|
|
async (surface, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-replaced-globals-");
|
|
await writeFixtureFiles(root, {
|
|
"replaced/main.mjs": REPLACEMENTS + SOURCE,
|
|
"control/main.mjs": SOURCE,
|
|
});
|
|
const effects = async (directory: string) => {
|
|
const graph = (
|
|
await analyzeJavaScriptDirectory(surface, cli, `${root}/${directory}`)
|
|
).semantic_graph;
|
|
if (graph === null) throw new Error("Expected a semantic graph");
|
|
return {
|
|
relations: graph.relations
|
|
.filter(({ relation }) => EFFECT_RELATIONS.includes(relation))
|
|
.map(({ relation, resolution }) => `${relation}:${resolution}`)
|
|
.sort(),
|
|
boundaryUnknowns: graph.unknowns
|
|
.filter(({ family }) => family === "boundary")
|
|
.map(({ detail }) => detail),
|
|
};
|
|
};
|
|
|
|
// The replaced JSON.parse remains only the generic `.parse` candidate.
|
|
await expect(effects("replaced")).resolves.toEqual({
|
|
relations: ["parses:candidate"],
|
|
boundaryUnknowns: ["Static parse boundary kind is a candidate."],
|
|
});
|
|
await expect(effects("control")).resolves.toEqual({
|
|
relations: [
|
|
"coerces:resolved",
|
|
"constructs-request:resolved",
|
|
"constructs-request:resolved",
|
|
"parses:resolved",
|
|
"schedules-timer:resolved",
|
|
],
|
|
boundaryUnknowns: [],
|
|
});
|
|
},
|
|
);
|