testTodoIsEditedAgainstWhatReplacedItsUses
A definition in todo is edited against what replaced the versions it used, imported by name, even where that's a
version from before (a change undone: Ctx given jobs, then not, so Tool hashes as it first did): tcm edit
and tcm patch say so, and a scratch file in a directory of its own (a session's) checks. A version imported by
its hash from a module that isn't there is said plainly, without what follows from it.
import { okIn, tcmIn, tempDir } from "#codebase/tcmts/cli/test/e2e/harness"; import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; export const testTodoIsEditedAgainstWhatReplacedItsUses = () => { const dir = tempDir("todo edited by name"); const ok = (...args: string[]) => okIn(dir, ...args); ok("init"); const write = (f: string, text: string) => { fs.mkdirSync(path.dirname(path.join(dir, f)), { recursive: true }); fs.writeFileSync(path.join(dir, f), text); }; write( "lib.ts", `// tcm: namespace lib\n` + `export interface Ctx { root: string }\n` + `export interface Tool { name: string; run: (ctx: Ctx) => string }\n`, ); ok("add", "lib.ts"); ok("patch", "lib.Ctx", "--old", "root: string;", "--new", "root: string;\n jobs: number[];"); write( "tools.ts", `// tcm: namespace lib\n` + `import type { Tool } from "#codebase/lib";\n` + `export const TOOLS: Tool[] = [{ name: "jobs", run: (ctx) => ctx.jobs.join(",") }];\n`, ); ok("add", "tools.ts"); assert.match( ok("patch", "lib.Ctx", "--old", "\n jobs: number[];", "--new", ""), /no longer typecheck/, ); fs.mkdirSync(path.join(dir, "sub"), { recursive: true }); const edited = ok("edit", "lib.TOOLS", "--file", "sub/t.ts"); const replaced = /Tool: imported as lib\.Tool \(the version it used, #(\w+), is replaced\)/.exec( edited, ); assert.ok(replaced, edited); const text = fs.readFileSync(path.join(dir, "sub", "t.ts"), "utf8"); assert.match(text, /import type \{ Tool \} from "#codebase\/lib"/, text); assert.doesNotMatch(text, /\.tcm\/cache/, text); // checked against the current Tool: the real problem const checked = tcmIn(dir, "check", "sub/t.ts"); assert.notEqual(checked.status, 0); assert.match(checked.out, /Property 'jobs' does not exist on type 'Ctx'/, checked.out); const patched = ok("patch", "lib.TOOLS", "--old", `ctx.jobs.join(",")`, "--new", "ctx.root"); assert.match(patched, /Tool: imported as lib\.Tool/, patched); assert.match(ok("todo"), /nothing to do/); // the old version by its hash, from where its module isn't (the project's cache, written from another directory) const cache = path.join(dir, ".tcm", "cache"); const old = fs .readdirSync(cache) .find((f) => f.startsWith(replaced[1]!))! .replace(/\.tsx?$/, ""); write( "sub/m.ts", `// tcm: namespace lib\n` + `import type { h_${old} as Tool } from "./.tcm/cache/${old}.ts";\n` + `export const MORE: Tool[] = [{ name: "more", run: (ctx) => ctx.root }];\n`, ); const missing = tcmIn(dir, "check", "sub/m.ts"); assert.notEqual(missing.status, 0); assert.match( missing.out, new RegExp( `uses #${replaced[1]}, a version of lib\\.Tool nothing names now: import the current one by its name, from "#codebase/lib"`, ), missing.out, ); assert.doesNotMatch(missing.out, /Cannot find module|implicitly has an 'any' type/, missing.out); }