at
uses node:path
tcm at <file>[:<line>]: the definition at a line of a file, or every definition in it with its lines. For
scratch files, definitions' documents, generated modules (what stack traces name) and mirrored files. A module
named by its component's hash (.tcm/cache/<hash>.ts, a packed package's modules/<hash>.js, from a stack trace
or a profile, here or elsewhere) is its component's: the member at the line, or every member. A view reads as its
branch has it now (rendered: one only read, --branch, has no files written); another branch's view is that
branch's to say (--branch it), not this one's.
import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { cutSignature, nameAt } from "#codebase/tcmts/cli/main"; import { declarations } from "#codebase/tcmts/cli/mirror"; import { bold, dim } from "#codebase/tcmts/cli/style"; import { UsageError } from "#codebase/tcmts/cli/ui"; import { hashesOfViewFile, srcDir, viewsBranchOf, viewText } from "#codebase/tcmts/cli/views"; import { shortHash } from "#codebase/tcmts/core/hash"; import fs from "node:fs"; import path from "node:path"; export function at(cb: Codebase, arg: string) { const m = /^(.*?)(?::(\d+))?(?::\d+)?$/.exec(arg)!; const file = path.resolve(process.cwd(), m[1]!); const component = /^([a-z2-7]{52})\.(?:[mc]?js|tsx?)$/.exec(path.basename(m[1]!))?.[1]; if (component && cb.store.componentMembers(component).length) { const members = cb.store.componentMembers(component); const show = (h: string) => { const rec = cb.store.getDef(h)!; console.log( `${bold(cb.store.pathsOf(h)[0] ?? "(unnamed)")} ${dim(shortHash(h))} ${dim(cutSignature(rec.kind === "type" ? rec.signature : `: ${cb.store.shownSignature(rec)}`, 80))}`, ); }; const line = m[2] ? Number(m[2]) : undefined; // which member: the declaration at the line, and the hash the module exports it as if (line !== undefined && members.length > 1 && fs.existsSync(file)) { const text = fs.readFileSync(file, "utf8"); const lineOf = (offset: number) => text.slice(0, offset).split("\n").length; const exported = new Map( [...text.matchAll(/export (?:type )?\{ ([\w$]+) as h_([a-z2-7]{52}) \}/g)].map((x) => [ x[1]!, x[2]!, ]), ); const d = [...declarations(file, text)].find( ([, d]) => lineOf(d.start) <= line && line <= lineOf(d.end), ); const h = d && exported.get(d[0]); if (h) return show(h); } for (const r of members) show(r.hash); return; } const owner = viewsBranchOf(cb, file); const ours = owner !== undefined && !path.relative(srcDir(cb), file).startsWith(".."); if (owner !== undefined && !ours) throw new UsageError( owner === null ? `${m[1]} is a view of a session whose branch is gone` : `${m[1]} is a view of ${owner}'s definitions, not ${cb.store.branch().name}'s: \`tcm at ${arg} --branch ${owner}\``, ); const text = ours ? viewText(cb, hashesOfViewFile(cb, file))?.text : fs.existsSync(file) ? fs.readFileSync(file, "utf8") : undefined; if (text === undefined) throw new UsageError( ours ? `${m[1]} isn't a view of anything on ${cb.store.branch().name}` : `no file ${m[1]}`, ); const line = m[2] ? Number(m[2]) : undefined; const lineOf = (offset: number) => text.slice(0, offset).split("\n").length; const name = nameAt(cb, file, text); const decls = [...declarations(file, text)].map(([leaf, d]) => ({ leaf, from: lineOf(d.doc?.start ?? d.start), to: lineOf(d.end), at: lineOf(d.start), })); const shown = line === undefined ? decls : decls.filter((d) => d.from <= line && line <= d.to); if (!shown.length) return console.log( dim( line === undefined ? `no definitions in ${m[1]}` : `line ${line} of ${m[1]} isn't in a definition`, ), ); for (const d of shown.sort((a, b) => a.from - b.from)) { const full = name(d.leaf, d.at); let hashes: string[] = []; try { hashes = full ? cb.lookup(full) : []; } catch { hashes = []; } const rec = hashes[0] ? cb.store.getDef(hashes[0]) : undefined; const what = rec ? `${dim(shortHash(rec.hash))} ${dim(cutSignature(rec.kind === "type" ? rec.signature : `: ${cb.store.shownSignature(rec)}`, 80))}` : dim("(not in the codebase)"); console.log(`${bold(full ?? d.leaf)} ${what} ${dim(`lines ${d.from}–${d.to}`)}`); } }
uses
- tcmts.cli.codebase.Codebase
- tcmts.cli.main.cutSignature
- tcmts.cli.main.nameAt
- tcmts.cli.mirror.declarations
- tcmts.cli.style.bold
- tcmts.cli.style.dim
- tcmts.cli.ui.UsageError
- tcmts.cli.views.hashesOfViewFile
- tcmts.cli.views.srcDir
- tcmts.cli.views.viewText
- tcmts.cli.views.viewsBranchOf
- tcmts.core.hash.shortHash