import type { AnatomyFolder, DefinitionLocation } from "#types/anatomy.types"; import { EXTENSION_SEPARATOR, PATH_SEPARATOR, SELF_IMPORT_MARK, TYPESCRIPT_EXTENSION, } from "#configuration/constants/anatomy.constants"; import { ANATOMY } from "#assets/anatomy.generated"; const RELATIVE_HERE = "."; const RELATIVE_UP = ".."; interface Index { readonly files: ReadonlySet; readonly folders: ReadonlySet; readonly names: ReadonlyMap; } let held: Index | null = null; const collect = function collect( folder: AnatomyFolder, names: Map, files: Set, folders: Set, ): void { folders.add(folder.path); for (const file of folder.files) { files.add(file.path); for (const definition of file.definitions) { const entry = { file: file.path, line: definition.line, name: definition.name }; names.set(definition.name, [...(names.get(definition.name) ?? []), entry]); } } for (const child of folder.folders) { collect(child, names, files, folders); } }; const index = function index(): Index { if (held === null) { const names = new Map(); const files = new Set(); const folders = new Set(); collect(ANATOMY.tree, names, files, folders); held = { files, folders, names }; } return held; }; const bare = function bare(text: string): string { const trimmed = text.trim(); const unrooted = trimmed.startsWith(RELATIVE_HERE + PATH_SEPARATOR) ? trimmed.slice(RELATIVE_HERE.length + PATH_SEPARATOR.length) : trimmed; return unrooted.endsWith(PATH_SEPARATOR) ? unrooted.slice(0, -PATH_SEPARATOR.length) : unrooted; }; export const definitionsNamed = function definitionsNamed(name: string): readonly DefinitionLocation[] { return index().names.get(name) ?? []; }; const step = function step(out: string[], segment: string): string[] { if (segment === RELATIVE_UP) { return out.slice(0, -1); } return segment === RELATIVE_HERE || segment.length === 0 ? out : [...out, segment]; }; const normalise = function normalise(segments: readonly string[]): string { return segments.reduce(step, []).join(PATH_SEPARATOR); }; const existing = function existing(candidate: string): string | null { const { files } = index(); if (files.has(candidate)) { return candidate; } const typed = candidate + EXTENSION_SEPARATOR + TYPESCRIPT_EXTENSION; return files.has(typed) ? typed : null; }; const bySuffix = function bySuffix(tail: string): string | null { const matches = [...index().files].filter((path) => path.endsWith(PATH_SEPARATOR + tail) || path === tail); return matches.length === 1 ? (matches[0] ?? null) : null; }; export const resolvePath = function resolvePath(text: string): string | null { const candidate = bare(text); if (candidate.length === 0) { return null; } return index().files.has(candidate) ? candidate : bySuffix(candidate); }; export const resolveFolder = function resolveFolder(text: string): string | null { const candidate = bare(text); return candidate.length > 0 && index().folders.has(candidate) ? candidate : null; }; export const resolveSpecifier = function resolveSpecifier(specifier: string, fromPath: string): string | null { if (specifier.startsWith(SELF_IMPORT_MARK)) { const tail = specifier.slice(SELF_IMPORT_MARK.length); return existing(tail) ?? bySuffix(tail + EXTENSION_SEPARATOR + TYPESCRIPT_EXTENSION) ?? bySuffix(tail); } if (!specifier.startsWith(RELATIVE_HERE)) { return null; } const base = fromPath.split(PATH_SEPARATOR).slice(0, -1); return existing(normalise([...base, ...specifier.split(PATH_SEPARATOR)])); };