at

tcmts.cli.main.at · #7zxendmg · term · runs on the server

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

used by