fileDefinition
uses node:crypto
A file kept in the codebase as written, as a definition named name. Markdown's {@link}s to code are
references by hash, so they read with current names and follow updates; its links to other files are kept as
written, and checked: one to a file of the codebase that isn't there (and isn't among alsoNew) is a
warning. A binary format's bytes are kept as base64. format is a hidden file's after a dot (.gitignore is
".", .mcp.json ".json"), kept so in its IR, as it's written back.
import type { CodebaseCore } from "#codebase/tcmts/cli/codebase"; import { BINARY_FORMATS, isText, linkedCode, linkedName, markdownLinks } from "#codebase/tcmts/cli/files"; import { hashIR, serializeIR } from "#codebase/tcmts/core/hash"; import type { IR } from "#codebase/tcmts/core/hash"; import type { DefRecord, Span } from "#codebase/tcmts/core/types"; import { createHash } from "node:crypto"; export function fileDefinition( cb: CodebaseCore, name: string, format: string, content: Buffer, alsoNew = new Set<string>(), ): { def: DefRecord; problems: string[]; warnings: string[] } { const leaf = name.slice(name.lastIndexOf(".") + 1); const ext = format.replace(/^\./, ""); const ns = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : ""; const problems: string[] = []; const warnings: string[] = []; const record = (ir: IR, sourceText: string, spans: Span[]): DefRecord => { const hash = hashIR(ir); return { hash, kind: "file", componentHash: hash, componentIndex: 0, componentSize: 1, ir: serializeIR(ir), sourceText, spans, reservedNames: [], initKind: "hoisted", nameHint: leaf, nominal: false, deps: [...new Set(spans.map((s) => s.hash!))].map((h) => ({ ref: `#${h}`, pos: "value" as const, strict: false, })), // (a file with no extension: text, or bytes) signature: ext || ((ir as unknown[][])[2]?.[0] === "Bytes" ? "bytes" : "text"), }; }; // kept as bytes by its format, or because it isn't text (UTF-8) if (BINARY_FORMATS.has(ext) || !isText(content)) return { def: record( ["File", format, ["Bytes", createHash("sha256").update(content).digest("hex")]], content.toString("base64"), [], ), problems, warnings, }; const text = content.toString("utf8"); const ir: IR[] = ["File", format]; const spans: Span[] = []; let at = 0; if (ext === "md") { for (const l of markdownLinks(text)) { if (l.code) { const h = linkedCode(cb, ns, l.target); if (!h) { problems.push(`{@link ${l.target}}: there's no ${l.target} in the codebase`); continue; } ir.push(text.slice(at, l.start), ["Ref", h]); spans.push({ start: l.start, end: l.end, pos: "value", hash: h }); at = l.end; continue; } const to = linkedName(name, l.target); if ( to && !alsoNew.has(to.name) && !cb.store.hashesAt(to.name).some((h) => cb.store.getDef(h)?.kind === "file") ) warnings.push(`${name} links to ${l.target}, and there's no ${to.name} in the codebase`); } } ir.push(text.slice(at)); return { def: record(ir, text, spans), problems, warnings }; }
uses
- tcmts.cli.codebase.CodebaseCore
- tcmts.cli.files.BINARY_FORMATS
- tcmts.cli.files.isText
- tcmts.cli.files.linkedCode
- tcmts.cli.files.linkedName
- tcmts.cli.files.markdownLinks
- tcmts.core.hash.IR
- tcmts.core.hash.hashIR
- tcmts.core.hash.serializeIR
- tcmts.core.types.DefRecord
- tcmts.core.types.Span