import type { AnatomyFile, AnatomyFolder, DefinitionLocation, DefinitionRef, DefinitionView, } from "#types/anatomy.types"; import { CALLED_BY_RELATION, CALLS_RELATION, DEFINITIONS_RELATION, DEFINITION_KIND, FILES_RELATION, FILE_KIND, FOLDERS_RELATION, FOLDER_INTRO_DEFINITIONS, FOLDER_INTRO_EDGES, FOLDER_INTRO_FILES, FOLDER_INTRO_LINES, FOLDER_RELATION, LINE_MARK, LOCATION_RELATION, ROOT_LABEL, } from "#configuration/strings/folder.strings"; import { DEFINITION_ANCHOR, TREE_TAB } from "#core/ids/anatomy.ids"; import type { DefinitionBlock, SourceBlock } from "#types/block.types"; import { LINE_INFIX, PATH_SEPARATOR } from "#configuration/constants/anatomy.constants"; import type { ReferenceRecord, ReferenceRelation } from "#types/reference.types"; import { fileId, folderId, languageOf } from "#domain/converters/source.converter"; import { ANATOMY } from "#assets/anatomy.generated"; import { ANATOMY_PAGE } from "#core/ids/page.ids"; import { CHAPTER_FACE } from "#configuration/constants/vocabulary.constants"; import type { EdgeRef } from "#types/link.types"; import { FACE_SEPARATOR } from "@govlab/constants"; import type { NodeTarget } from "#types/folder.types"; import { definitionsNamed } from "#core/loaders/definition.loader"; import { sourceNameOf } from "#core/loaders/walk.loader"; import { tabLink } from "#assets/link.assets"; import { targetOf } from "#core/converters/folder.converter"; type Node = { readonly file: AnatomyFile; readonly kind: "file" } | { readonly folder: AnatomyFolder; readonly kind: "folder" }; let nodes: ReadonlyMap | null = null; const inFile = function inFile(location: DefinitionLocation, file: string | null): boolean { return file === null || location.file === file || location.file.endsWith(PATH_SEPARATOR + file); }; export const citedDefinition = function citedDefinition( name: string, file: string | null = null, ): DefinitionLocation | null { const found = definitionsNamed(name).filter((location) => inFile(location, file)); const [only] = found; return found.length === 1 && only !== undefined ? only : null; }; export const resolveTarget = function resolveTarget(target: NodeTarget): NodeTarget { if (!target.id.startsWith(DEFINITION_ANCHOR)) { return target; } const cited = citedDefinition(target.id.slice(DEFINITION_ANCHOR.length), target.text); return cited === null ? target : { id: fileId(cited.file), line: cited.line, text: null }; }; export const citedSource = function citedSource( block: DefinitionBlock, ): { readonly block: SourceBlock; readonly location: DefinitionLocation } | null { const location = citedDefinition(block.name, block.file); const source = location === null ? null : sourceNameOf(location.file); if (location === null || source === null) { return null; } return { block: { kind: "source", language: languageOf(location.file), path: location.file, source, title: block.title }, location, }; }; const collect = function collect(folder: AnatomyFolder, into: Map): void { into.set(folderId(folder.path), { folder, kind: "folder" }); for (const file of folder.files) { into.set(fileId(file.path), { file, kind: "file" }); } for (const child of folder.folders) { collect(child, into); } }; const index = function index(): ReadonlyMap { if (nodes === null) { const found = new Map(); collect(ANATOMY.tree, found); nodes = found; } return nodes; }; const nodeRef = function nodeRef(id: string, line: number | null): string { const suffix = line === null ? "" : LINE_INFIX + String(line); return CHAPTER_FACE + FACE_SEPARATOR + tabLink(ANATOMY_PAGE, TREE_TAB, id + suffix); }; const edge = function edge(label: string, id: string, line: number | null): EdgeRef { return { label, ref: nodeRef(id, line) }; }; const lineOf = function lineOf(ref: DefinitionRef): number | null { const node = index().get(fileId(ref.file)); const definition = node?.kind === "file" ? node.file.definitions.find((candidate) => candidate.name === ref.name) : undefined; return definition === undefined ? null : definition.line; }; const definitionEdge = function definitionEdge(ref: DefinitionRef): EdgeRef { return edge(ref.name, fileId(ref.file), lineOf(ref)); }; const relation = function relation(name: string, edges: readonly EdgeRef[]): ReferenceRelation[] { return edges.length === 0 ? [] : [{ edges, relation: name }]; }; const parentOf = function parentOf(path: string): string { const cut = path.lastIndexOf(PATH_SEPARATOR); return cut === -1 ? "" : path.slice(0, cut); }; const folderEdge = function folderEdge(path: string): EdgeRef { return edge(path === "" ? ROOT_LABEL : path, folderId(path), null); }; const countsOf = function countsOf(node: AnatomyFile | AnatomyFolder): string { return ( String(node.stats.lines.code) + FOLDER_INTRO_LINES + String(node.stats.definitions) + FOLDER_INTRO_DEFINITIONS + String(node.stats.edges) + FOLDER_INTRO_EDGES ); }; const definitionRecord = function definitionRecord(file: AnatomyFile, definition: DefinitionView): ReferenceRecord { return { code: definition.flow, kind: definition.kind, layer: null, name: definition.name, relations: [ ...relation(LOCATION_RELATION, [ edge(file.path + LINE_MARK + String(definition.line), fileId(file.path), definition.line), ]), ...relation(CALLS_RELATION, definition.callees.map(definitionEdge)), ...relation(CALLED_BY_RELATION, definition.callers.map(definitionEdge)), ], summary: DEFINITION_KIND, }; }; const fileRecord = function fileRecord(file: AnatomyFile): ReferenceRecord { return { code: file.layer, kind: file.slots === null ? FILE_KIND : file.slots.concern, layer: null, name: file.name, relations: [ ...relation(FOLDER_RELATION, [folderEdge(parentOf(file.path))]), ...relation( DEFINITIONS_RELATION, file.definitions.map((definition) => edge(definition.name, fileId(file.path), definition.line)), ), ], summary: countsOf(file), }; }; const folderRecord = function folderRecord(folder: AnatomyFolder): ReferenceRecord { return { code: folder.layer, kind: folder.role, layer: null, name: folder.path === "" ? ROOT_LABEL : folder.name, relations: [ ...relation( FOLDERS_RELATION, folder.folders.map((child) => edge(child.name, folderId(child.path), null)), ), ...relation( FILES_RELATION, folder.files.map((file) => edge(file.name, fileId(file.path), null)), ), ], summary: String(folder.stats.files) + FOLDER_INTRO_FILES + countsOf(folder), }; }; export const anatomyReference = function anatomyReference(fragment: string): ReferenceRecord | null { const requested = targetOf(`#${fragment}`); const target = requested === null ? null : resolveTarget(requested); const node = target === null ? undefined : index().get(target.id); if (node === undefined || target === null) { return null; } if (node.kind === "folder") { return folderRecord(node.folder); } const definition = target.line === null ? undefined : node.file.definitions.find((candidate) => candidate.line === target.line); return definition === undefined ? fileRecord(node.file) : definitionRecord(node.file, definition); };