testSetupStatementsJoinTheirConstant

tcmts.core.test.hash.testSetupStatementsJoinTheirConstant · #yqb7rnsv · term

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`);
}

uses