Files
scriptc/packages/compiler/test/fs-write-options.test.ts
T
Malte UblandClaude Opus 5.5 5d8d6b35a1 Address the child-lifecycle, stream-alias and fs write-options reviews
- child_process: the exit/close/error dispatch loops read the live
  listener registry in their loop condition, so a listener that called
  off/removeListener on a sibling cut the in-flight emit short. Node
  copies the listener array before it emits; detach the registry
  (scr_child_detach_exit/_close/_err), deliver that snapshot, release
  each closure, and free the array in both platform arms.
- process: a first-class alias of stdout/stderr (`const out =
  process.stdout`) keeps the stream's type but read `writableLength`
  through the keyed dynamic get, which answered undefined. Fold that
  read to the same synchronous-write 0 the direct lowering answers,
  leaving the runtime untouched.
- fs: a `const` write-options record was inlined at the write site even
  when it was mutable, so `OPTS.mode = 0o644` before the call diverged
  from Node, which reads the record when the call runs. Accept only a
  record whose properties are all readonly (`as const`, `Readonly<>`);
  a mutable one keeps the SC2020 fence.
- Refresh the node-types-fenced snapshot: `path.win32` sub-namespaces
  lower now, so the PlatformPath.sep fence is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:15:37 -07:00

59 lines
2.2 KiB
TypeScript

/* The three-argument writeFileSync forms whose options record is a `const`
* binding. The lowering inlines the record's literal values at the write
* site, so it accepts the binding only when the record is readonly: Node
* reads the record when the call runs, and a mutable record may have
* changed since its initializer (`OPTS.mode = 0o644` must reach the
* write). */
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, test } from "vitest";
import { compile } from "../src/index.js";
async function compileSource(source: string) {
const directory = mkdtempSync(join(tmpdir(), "scriptc-fs-write-options-"));
try {
const entry = join(directory, "main.ts");
writeFileSync(entry, source);
return await compile(entry, {
outDir: directory,
outPath: join(directory, "program.ir"),
outputKind: "ir",
});
} finally {
rmSync(directory, { recursive: true, force: true });
}
}
test.each([
["as const", 'const OPTS = { encoding: "utf-8", mode: 0o600 } as const;'],
[
"a Readonly<> annotation",
'const OPTS: Readonly<{ encoding: "utf-8"; mode: number }> = { encoding: "utf-8", mode: 0o600 };',
],
])("a readonly const record (%s) keeps lowering", async (_what, declaration) => {
const result = await compileSource(`import * as fs from "node:fs";
${declaration}
fs.writeFileSync("scratch.json", "{}", OPTS);
`);
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
});
test("a MUTABLE const record fences: its mode may change before the call", async () => {
const result = await compileSource(`import * as fs from "node:fs";
const OPTS: { encoding: "utf-8"; mode: number } = { encoding: "utf-8", mode: 0o600 };
OPTS.mode = 0o644;
fs.writeFileSync("scratch.json", "{}", OPTS);
`);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected the write options fence");
expect(result.diagnostics).toEqual(
expect.arrayContaining([
expect.objectContaining({
message: expect.stringContaining("writeFileSync with 3 arguments"),
}),
]),
);
expect(result.diagnostics.some((diagnostic) => diagnostic.code === "SC9001")).toBe(false);
});