testAnalyzedAndStoredDefinitionsReadAlike

tcmts.cli.test.shapes.testAnalyzedAndStoredDefinitionsReadAlike · #idsxa3dn · term

Code reading a definition's IR reads it the same analyzed from source (as tests and scratch checks analyze it, with no lineage salt) as stored (salted, and read back from the store): a read by position, which finds the declaration in one and the salt or JSX runtime in the other, disagrees here.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { holdsState } from "#codebase/tcmts/cli/kept";
import { isMutableState } from "#codebase/tcmts/cli/placement";
import { evaluatedOnLoad, isLiteralValue, memberCode, quietLoad } from "#codebase/tcmts/cli/suites";
import { codebaseOf } from "#codebase/tcmts/cli/test/shapes";
import { analyzeFile } from "#codebase/tcmts/core/analyze";
import type { AnalyzeHost } from "#codebase/tcmts/core/analyze";
import { IR_VERSION } from "#codebase/tcmts/core/hash";
import type { IR } from "#codebase/tcmts/core/hash";
import { childOf, declarationOf, defParts, initializerOf } from "#codebase/tcmts/core/ir";
import { fakeReact } from "#codebase/tcmts/core/test/hash";
import type { DefRecord } from "#codebase/tcmts/core/types";
import { open } from "#codebase/tcmts/store/test/history";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";

export const testAnalyzedAndStoredDefinitionsReadAlike = () => {
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "tcm shapes "));
  fakeReact(dir);
  fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({ type: "module" }));
  const file = path.join(dir, "shapes.tsx");
  fs.writeFileSync(
    file,
    `export const n = 3;
export const negative = -1;
export const greeting = \`hello \${"world"}\`;
export const table = new Map<string, number>();
export const frozen = [1, 2] as const;
export const config = { retries: 3, run: (x: number) => x + 1 };
export const computed = Math.max(n, 2);
export const built = [n].map((x) => x * 2);
export const double = (x: number) => x * 2;
export const { left, right } = { left: 1, right: 2 };
export let counter = 0;
export function bump() { counter++; }
export function over(x: string): string;
export function over(x: number): number;
export function over(x: unknown) { return x; }
export class Point { x = 0; y: number; constructor(y: number) { this.y = y; } norm() { return Math.hypot(this.x, this.y); } }
export class Hello { render() { return <p>hi {n}</p>; } size() { return 1; } }
export const Greet = () => <p>{greeting}</p>;
export interface Shape { area(): number }
export type Id = string;
`,
  );
  const host = (saltFor?: AnalyzeHost["saltFor"]): AnalyzeHost => ({
    cacheDir: path.join(dir, ".tcm", "cache"),
    resolveName: (name) => {
      throw new Error(`unknown ${name}`);
    },
    saltFor,
  });
  const analyzed = analyzeFile(file, host());
  assert.deepEqual([...analyzed.errors, ...analyzed.diagnostics.map((d) => d.message)], []);
  // salted as the CLI salts a definition added at lib.<name>, stored, and read back
  const salted = analyzeFile(
    file,
    host((_f, name) => `lib.${name}`),
  ).defs;
  const store = open();
  store.putDefs(salted, "test", IR_VERSION);
  const stored = salted.map((d) => store.getDef(d.hash)!);

  const byName = (defs: DefRecord[]) => new Map(defs.map((d) => [d.nameHint, d]));
  const a = byName(analyzed.defs);
  const s = byName(stored);
  assert.deepEqual([...a.keys()].sort(), [...s.keys()].sort());
  const cbA = codebaseOf(analyzed.defs);
  const cbS = codebaseOf(stored);
  // what code finds out about a definition from its IR, its hashes by name (the two differ by their salts)
  const named = (cb: typeof cbA, hs: string[] | undefined) =>
    hs?.map((h) => cb.store.pathsOf(h)[0] ?? h);
  const read = (rec: DefRecord, cb: typeof cbA) => {
    const parts = defParts(rec.ir);
    const members =
      rec.kind === "class"
        ? ((childOf(declarationOf(rec.ir), "members") as IR[] | undefined) ?? [])
            .slice(1)
            .flatMap((m) => {
              const name = childOf(m, "name");
              return Array.isArray(name) && name[0] === "N" ? [String(name[1])] : [];
            })
        : [];
    return {
      tag: parts.tag,
      jsx: parts.jsx,
      body: parts.body.length,
      declaration: declarationOf(rec.ir)?.[0],
      initializer: initializerOf(rec.ir)?.[0],
      evaluatedOnLoad: evaluatedOnLoad(rec),
      isLiteralValue: isLiteralValue(rec),
      holdsState: holdsState(rec),
      isMutableState: isMutableState(JSON.parse(rec.ir) as IR),
      quietLoad: named(cb, quietLoad(cb, rec)),
      // a method's code is the method's alone; the rest of a class's has its identity (its salt) in it
      members: Object.fromEntries(members.map((m) => [m, memberCode(cb as Codebase, rec, m)])),
      hasOwnCode:
        rec.kind === "class" ? memberCode(cb as Codebase, rec, "") !== undefined : undefined,
    };
  };
  for (const [name, rec] of s) {
    const was = a.get(name)!;
    const salt = rec.kind === "type" ? undefined : `lib.${name}`;
    assert.equal(defParts(rec.ir).salt, salt, `${name} is stored ${salt ? "salted" : "unsalted"}`);
    assert.equal(defParts(was.ir).salt, undefined, `${name} is analyzed unsalted`);
    assert.deepEqual(read(was, cbA), read(rec, cbS), `${name} reads alike analyzed and stored`);
  }
  // and what they read is right
  const facts = (name: string) => read(s.get(name)!, cbS);
  assert.equal(facts("n").holdsState, "fixed");
  assert.equal(facts("n").isLiteralValue, true);
  assert.equal(facts("greeting").isLiteralValue, true);
  assert.equal(facts("config").isLiteralValue, true);
  assert.equal(facts("computed").isLiteralValue, false);
  assert.equal(facts("double").evaluatedOnLoad, false);
  assert.equal(facts("table").isMutableState, true);
  assert.equal(facts("frozen").isMutableState, false);
  assert.equal(facts("counter").holdsState, "state");
  assert.equal(facts("over").declaration, "FunctionDeclaration");
  assert.equal(facts("over").body, 3);
  assert.equal(facts("Hello").jsx, "npm:react@19");
  assert.deepEqual(Object.keys(facts("Hello").members), ["render", "size"]);
  assert.ok(
    Object.values(facts("Hello").members).every((c) => c !== undefined),
    "a class with JSX has its methods",
  );
  assert.equal(facts("Hello").hasOwnCode, true);
  assert.equal(facts("Point").hasOwnCode, true);
}

uses