definitionPage
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
- tcmts.cli.codebase.Codebase
- tcmts.cli.docs.renderDoc
- tcmts.cli.files.formatOf
- tcmts.cli.files.isBytes
- tcmts.cli.files.linkedName
- tcmts.cli.files.markdownLinks
- tcmts.cli.pages.IMAGE_TYPES
- tcmts.cli.pages.PageLinks
- tcmts.cli.pages.esc
- tcmts.cli.pages.highlight
- tcmts.cli.pages.importRefs
- tcmts.cli.placement.Placement
- tcmts.cli.testing.isTestPath
- tcmts.cli.vitest.keysForResults
- tcmts.core.hash.shortHash
- tcmts.core.types.tcmModule
- tcmts.loader.render