testAnalyzedAndStoredDefinitionsReadAlike
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
- tcmts.cli.codebase.Codebase
- tcmts.cli.kept.holdsState
- tcmts.cli.placement.isMutableState
- tcmts.cli.suites.evaluatedOnLoad
- tcmts.cli.suites.isLiteralValue
- tcmts.cli.suites.memberCode
- tcmts.cli.suites.quietLoad
- tcmts.cli.test.shapes.codebaseOf
- tcmts.core.analyze.AnalyzeHost
- tcmts.core.analyze.analyzeFile
- tcmts.core.hash.IR
- tcmts.core.hash.IR_VERSION
- tcmts.core.ir.childOf
- tcmts.core.ir.declarationOf
- tcmts.core.ir.defParts
- tcmts.core.ir.initializerOf
- tcmts.core.test.hash.fakeReact
- tcmts.core.types.DefRecord
- tcmts.store.test.history.open