testSetupStatementsJoinTheirConstant
A top-level statement that only writes into one constant declared above it (filling a table, freezing it) goes with that constant: one definition, its statements run and hashed together, in a scratch file as in an import. Anything else stays refused: a statement writing into nothing (a call), into two constants, or into something not the file's; and what a function in it does later doesn't count.
import { analyzeProject } from "#codebase/tcmts/core/analyze"; import { dir, hashes, project } from "#codebase/tcmts/core/test/hash"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; export const testSetupStatementsJoinTheirConstant = () => { const lib = `const alphabet = "abc"; const codes = new Uint8Array(128); // fill the table for (let i = 0; i < alphabet.length; i++) codes[alphabet.charCodeAt(i)] = i; export const limits = { max: 3 }; Object.freeze(limits); const Attributes = function () {} as unknown as new () => Record<string, string>; Attributes.prototype = Object.freeze(Object.create(null)); export const code = (c: string): number => codes[c.charCodeAt(0)]!; `; const p = project({ "tables.ts": lib }); assert.deepEqual(Object.keys(p).sort(), ["Attributes", "alphabet", "code", "codes", "limits"]); assert.match( p.Attributes!.sourceText, /\nAttributes\.prototype = /, "freezing a new object writes into nothing else", ); assert.match(p.codes!.sourceText, /new Uint8Array\(128\)\n\/\/ fill the table\nfor \(let i = 0/); assert.ok(JSON.stringify(p.codes!.deps).includes(p.alphabet!.hash), "the loop reads alphabet"); const scratch = hashes(lib); for (const name of ["codes", "limits"]) assert.equal(scratch[name], p[name]!.hash, `${name} in a scratch file`); assert.equal( hashes(p.limits!.sourceText).limits, p.limits!.hash, "the stored code reads back as itself", ); const sub = fs.mkdtempSync(path.join(dir, "setup-")); const refused = (name: string, text: string) => { const file = path.join(sub, `${name}.ts`); fs.writeFileSync(file, text); const a = analyzeProject([file], { cacheDir: path.join(sub, ".tcm", "cache"), resolveName: () => "", }); assert.match(a.errors.join("\n"), /not module side effects/, name); }; refused("call", `const x = { n: 1 };\nconsole.log(x);\n`); refused("two", `const a = [0];\nconst b = [0];\na[0] = b[0] = 1;\n`); refused("global", `const a = 1;\n(globalThis as any).a = a;\n`); refused("later", `const a: { n?: number } = {};\nsetTimeout(() => { a.n = 1; });\n`); refused("unknown", `const a = { n: 1 };\nconst get = () => a;\nObject.freeze(get());\n`); }