import { existsSync } from "node:fs"; import { join } from "node:path"; import { LinkGraph } from "../link-graph.js"; import type { CoverageTracker } from "../coverage.js"; import type { CommentDocs } from "../yaml-comments.js"; import { esc } from "../badges.js"; import { fmtAttrValue } from "./object-data.js"; // Rendered specifically (own fields/badges); everything else a template authors // (e.g. a consumer's @dataflow) is rendered generically as extraAttrsHtml. const OUTPUT_KNOWN_ATTRS = new Set(["payloadRef", "textRef", "format", "kind", "description"]); export interface OutputPageData { name: string; pkg: string; href: string; breadcrumbHtml: string; format: string; kind: string; textRef: string; textRefResolves: boolean; payloadName: string; payloadHref: string; desc: string; extraAttrsHtml: string; fields: { name: string; type: string; isArray: boolean; wire: string; note: string; refHtml: string }[]; } export function buildOutputPage(fqn: string, g: LinkGraph, cov: CoverageTracker, docs: CommentDocs, sourceDirs: string[] = []): OutputPageData { const dn = g.byFqn(fqn)!; const t = dn.node; cov.consumeNode(t); cov.consumeAttr(t, "payloadRef"); cov.consumeAttr(t, "textRef"); cov.consumeAttr(t, "format"); cov.consumeAttr(t, "kind"); const format = String(t.attr("format") ?? "text"); // any other authored template attrs (e.g. @maxChars, @requiredTags, a consumer's @dataflow) const extraAttrsHtml = [...t.ownAttrs()] .filter(([n]) => !OUTPUT_KNOWN_ATTRS.has(n)) .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)) .map(([n, v]) => { cov.consumeAttr(t, n); return `@${esc(n)}=${esc(fmtAttrValue(v))}`; }) .join(" "); const pRef = g.refsFrom(fqn).find((r) => r.kind === "payload"); const payload = pRef ? g.byFqn(pRef.to) : undefined; const fields = (payload?.node.childrenOfType("field") ?? []).map((f) => { const ref = f.attr("objectRef"); const target = typeof ref === "string" ? (g.byFqn(ref.includes("::") ? ref : `${dn.pkg}::${ref}`) ?? g.byFqn(ref)) : undefined; const hasXmlText = f.attr("xmlText") !== undefined; const wire = hasXmlText ? "@xmlText body" : target ? "nested" : format === "xml" ? (f.resolvedIsArray() ? "element" : "attr") : "property"; const noteAttr = f.attr("description"); const note = esc(noteAttr ?? docs.fieldNote.get(`${payload!.name}.${f.name}`) ?? ""); if (noteAttr !== undefined) cov.consumeAttr(f, "description"); return { name: f.name, type: f.subType, isArray: f.resolvedIsArray(), wire, note, refHtml: target ? `${esc(target.name)}` : "" }; }); const textRef = String(t.attr("textRef") ?? ""); const textRefResolves = sourceDirs.some((d) => existsSync(join(d, ...textRef.split("/")) + ".mustache")); return { name: dn.name, pkg: dn.pkg, href: dn.href, breadcrumbHtml: `index / ${esc(dn.pkg)} / ${esc(dn.name)}`, format, kind: String(t.attr("kind") ?? "document"), textRef, textRefResolves, extraAttrsHtml, payloadName: payload?.name ?? "", payloadHref: payload ? esc(g.relHref(dn.href, payload.href)) : "", desc: payload ? esc(payload.node.attr("description") ?? docs.objectDesc.get(payload.name) ?? "") : "", fields }; }