definitionsAt

tcmts.cli.main.definitionsAt · #pntxsa2f · term · runs on the server

uses node:path

The definitions at a line of a file (with no line, every one in it), as tcm at says them: for scratch files, definitions' documents (views), generated modules and mirrored files. A module named by its component's hash (.tcm/cache/<hash>.ts, a packed package's modules/<hash>.js) is its component's: the member at the line, or every member. Each with its hash (none: not in the codebase), and the name and lines it has in the file when it's read as text. A view of another branch's, or of none, is a UsageError saying so; a file that isn't there, too.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { nameAt } from "#codebase/tcmts/cli/main";
import { declarations } from "#codebase/tcmts/cli/mirror";
import { UsageError } from "#codebase/tcmts/cli/ui";
import { hashesOfViewFile, srcDir, viewsBranchOf, viewText } from "#codebase/tcmts/cli/views";
import fs from "node:fs";
import path from "node:path";

export function definitionsAt(
  cb: Codebase,
  file: string,
  line?: number,
  /** the file as it was asked for, for what's said */
  asked = path.relative(process.cwd(), file) || file,
): { hash?: string; name: string; from?: number; to?: number }[] {
  const component = /^([a-z2-7]{52})\.(?:[mc]?js|tsx?)$/.exec(path.basename(file))?.[1];
  if (component && cb.store.componentMembers(component).length) {
    const members = cb.store.componentMembers(component);
    const named = (h: string) => ({ hash: h, name: cb.store.pathsOf(h)[0] ?? "(unnamed)" });
    // 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 [named(h)];
    }
    return members.map((r) => named(r.hash));
  }
  const owner = viewsBranchOf(cb, file);
  const ours = owner !== undefined && !path.relative(srcDir(cb), file).startsWith("..");
  const shown = asked;
  if (owner !== undefined && !ours)
    throw new UsageError(
      owner === null
        ? `${shown} is a view of a session whose branch is gone`
        : `${shown} is a view of ${owner}'s definitions, not ${cb.store.branch().name}'s: \`tcm at ${shown}${line === undefined ? "" : `:${line}`} --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 ? `${shown} isn't a view of anything on ${cb.store.branch().name}` : `no file ${shown}`,
    );
  const lineOf = (offset: number) => text.slice(0, offset).split("\n").length;
  const name = nameAt(cb, file, text);
  return [...declarations(file, text)]
    .map(([leaf, d]) => ({
      leaf,
      from: lineOf(d.doc?.start ?? d.start),
      to: lineOf(d.end),
      at: lineOf(d.start),
    }))
    .filter((d) => line === undefined || (d.from <= line && line <= d.to))
    .sort((a, b) => a.from - b.from)
    .map((d) => {
      const full = name(d.leaf, d.at);
      let hash: string | undefined;
      try {
        hash = full ? cb.lookup(full)[0] : undefined;
      } catch {
        hash = undefined;
      }
      return { hash, name: full ?? d.leaf, from: d.from, to: d.to };
    });
}

uses

used by