definitionPage

tcmts.cli.pages.definitionPage · #3habskgp · term · runs on the server

uses Buffer

Everything a definition's page shows.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { renderDoc } from "#codebase/tcmts/cli/docs";
import { formatOf, isBytes, linkedName, markdownLinks } from "#codebase/tcmts/cli/files";
import { esc, highlight, IMAGE_TYPES, importRefs } from "#codebase/tcmts/cli/pages";
import type { PageLinks } from "#codebase/tcmts/cli/pages";
import type { Placement } from "#codebase/tcmts/cli/placement";
import { isTestPath } from "#codebase/tcmts/cli/testing";
import { keysForResults } from "#codebase/tcmts/cli/vitest";
import { shortHash } from "#codebase/tcmts/core/hash";
import { tcmModule } from "#codebase/tcmts/core/types";
import { render } from "#codebase/tcmts/loader";

export function definitionPage(cb: Codebase, hash: string, placement: Placement, links: PageLinks) {
  const rec = cb.store.getDef(hash);
  if (!rec) return undefined;
  const names = cb.store.pathsOf(hash);
  const nameOf = (h: string) => cb.store.pathsOf(h)[0] ?? shortHash(h);
  // the source without its doc (the doc is rendered above it), references as links
  const r = render([{ ...rec, doc: undefined }], cb.source(), cb.lay, {
    style: "scratch",
    exportDeclarations: true,
  });
  const deps = [
    ...new Set(
      rec.deps
        .filter((d) => d.ref.startsWith("#") && d.ref !== `#${hash}`)
        .map((d) => d.ref.slice(1)),
    ),
  ];
  const typeHashes = new Set([...deps, hash].filter((h) => cb.store.getDef(h)?.kind === "type"));
  const doc = renderDoc(cb, rec, {
    evaluate: links.evaluate ?? true,
    link: (h) => `tcm-page:${h}`,
  });
  const where = rec.kind === "term" ? placement.placeOf(hash) : undefined;
  const isTest = rec.kind !== "type" && names.some(isTestPath);
  const test = isTest
    ? cb.store.testResult(hash, keysForResults(cb, [hash]).get(hash)!)
    : undefined;
  const item = (h: string) => ({
    hash: h,
    name: nameOf(h),
    kind: cb.store.getDef(h)?.kind,
    signature: ((r) => r && cb.store.shownSignature(r))(cb.store.getDef(h)),
    uri: links.viewUri(h),
  });
  // a file kept as written: its content (Markdown rendered, an image shown, other text as it is)
  if (rec.kind === "file") {
    const format = formatOf(rec);
    const page = {
      hash,
      short: shortHash(hash),
      names,
      kind: rec.kind,
      format,
      signature: rec.signature,
      docProblems: [] as string[],
      externals: [] as string[],
    };
    const related = {
      uses: deps.map(item).sort((a, b) => (a.name < b.name ? -1 : 1)),
      usedBy: cb.store
        .namedDependents(hash)
        .map(item)
        .sort((a, b) => (a.name < b.name ? -1 : 1)),
      uri: links.viewUri(hash),
    };
    if (isBytes(rec)) {
      const mime = IMAGE_TYPES[format];
      return {
        ...page,
        ...related,
        ...(mime
          ? { image: `data:${mime};base64,${rec.sourceText}` }
          : {
              sourceHtml: esc(
                `(${format || "bytes"}, ${Buffer.from(rec.sourceText, "base64").length} bytes)`,
              ),
            }),
      };
    }
    const text = rec.sourceText;
    if (format === "svg")
      return {
        ...page,
        ...related,
        image: `data:image/svg+xml;base64,${Buffer.from(text).toString("base64")}`,
        sourceHtml: esc(text),
      };
    if (format !== "md")
      return {
        ...page,
        ...related,
        sourceHtml: ["ts", "tsx", "js", "json"].includes(format)
          ? highlight(text, [], new Set(), format === "tsx")
          : esc(text),
      };
    // Markdown: its links to code and to other pages open their pages
    // (a span is the name in `{@link name}`: the whole of it becomes the link)
    const edits: { start: number; end: number; text: string }[] = rec.spans.flatMap((sp) => {
      if (!sp.hash) return [];
      const open = /\{@link\s+$/.exec(text.slice(Math.max(0, sp.start - 20), sp.start));
      const close = /^\s*\}/.exec(text.slice(sp.end));
      return [
        {
          start: open ? sp.start - open[0].length : sp.start,
          end: close ? sp.end + close[0].length : sp.end,
          text: `[\`${nameOf(sp.hash)}\`](tcm-page:${sp.hash})`,
        },
      ];
    });
    const name = names[0] ?? "";
    for (const l of markdownLinks(text)) {
      if (l.code) continue;
      const to = linkedName(name, l.target);
      const h = to && cb.store.hashesAt(to.name).find((x) => cb.store.getDef(x)?.kind === "file");
      if (h) edits.push({ start: l.start, end: l.end, text: `tcm-page:${h}` });
    }
    let md = text;
    for (const e of edits.sort((x, y) => y.start - x.start))
      md = md.slice(0, e.start) + e.text + md.slice(e.end);
    return { ...page, ...related, docMarkdown: md };
  }
  return {
    hash,
    short: shortHash(hash),
    names,
    kind: rec.kind,
    signature: cb.store.shownSignature(rec),
    ...(where && !isTest ? { place: where.place, why: where.why } : {}),
    ...(where?.place === "server" && placement.crossable(hash)
      ? { endpoint: true, streaming: placement.streams(hash) }
      : {}),
    ...(isTest ? { test: test ? (test.ok ? "passed" : "failed") : "not run" } : {}),
    docMarkdown: doc?.markdown,
    docProblems: doc?.problems ?? [],
    sourceHtml: highlight(
      r.text.trimEnd(),
      [
        ...r.refs.filter((x) => x.hash !== hash || x.from !== hash),
        ...importRefs(cb, r.text.trimEnd(), rec),
      ],
      typeHashes,
      !!rec.jsx,
    ),
    uses: deps.map(item).sort((a, b) => (a.name < b.name ? -1 : 1)),
    // what it imports from outside the codebase, as written in code: a package, a Node builtin, #tcm/host
    externals: [
      ...new Set(
        rec.spans.flatMap((s) =>
          s.ext ? [tcmModule(s.ext.module)?.spec ?? s.ext.module.replace(/^npm:/, "")] : [],
        ),
      ),
    ],
    usedBy: cb.store
      .namedDependents(hash)
      .map(item)
      .sort((a, b) => (a.name < b.name ? -1 : 1)),
    uri: links.viewUri(hash),
  };
}

uses

used by