core/validators/definition.validator.ts
core/validators/definition.validator.ts is a file in Bane's Lab Build Scripts. 93 lines of code and 30 definitions.
import type { Finding, ValidationRoute } from "#types/validation.types";
import { unresolvedCitation, unresolvedPanel } from "#configuration/strings/definition.strings";
import { isRecord } from "#core/selectors/base.selector";
import { jsonFileOf } from "#core/resolvers/page.resolver";
import { readOrNull } from "#core/loaders/build.loader";
const KIND_KEY = "kind";
const DEFINITION_KIND = "definition" as const;
const DEFINITION_NAME_KEY = "name";
const DEFINITION_FILE_KEY = "file";
const DEFINITION_LINE_KEY = "line";
const DEFINITION_TITLE_KEY = "title";
const FRAGMENT_MARK = "#";
const FRAGMENT_ENDS = new Set([")", '"', "'", " ", "<", ">", "\n"]);
interface Citation {
readonly file: string | null;
readonly line: number | null;
readonly name: string;
readonly title: string;
}
const citationsIn = function citationsIn(value: unknown): Citation[] {
if (Array.isArray(value)) {
return value.flatMap(citationsIn);
}
if (!isRecord(value)) {
return [];
}
const name = value[DEFINITION_NAME_KEY];
const file = value[DEFINITION_FILE_KEY];
const line = value[DEFINITION_LINE_KEY];
const title = value[DEFINITION_TITLE_KEY];
const own =
value[KIND_KEY] === DEFINITION_KIND && typeof name === "string" && typeof title === "string"
? [
{
file: typeof file === "string" ? file : null,
line: typeof line === "number" ? line : null,
name,
title,
},
]
: [];
return [...own, ...Object.values(value).flatMap(citationsIn)];
};
const fragmentEnd = function fragmentEnd(text: string, from: number): number {
let at = from;
while (at < text.length && !FRAGMENT_ENDS.has(text.charAt(at))) {
at += 1;
}
return at;
};
const fragmentsIn = function fragmentsIn(text: string, anchor: string): string[] {
const found: string[] = [];
let at = text.indexOf(FRAGMENT_MARK + anchor);
while (at !== -1) {
const end = fragmentEnd(text, at + FRAGMENT_MARK.length);
found.push(text.slice(at + FRAGMENT_MARK.length, end));
at = text.indexOf(FRAGMENT_MARK + anchor, end);
}
return found;
};
const stringsIn = function stringsIn(value: unknown): string[] {
if (typeof value === "string") {
return [value];
}
if (Array.isArray(value)) {
return value.flatMap(stringsIn);
}
return isRecord(value) ? Object.values(value).flatMap(stringsIn) : [];
};
const checkCitations = async function checkCitations(file: string, raw: string): Promise<Finding[]> {
const parsed: unknown = JSON.parse(raw);
const loader = await import("@banes-lab/web/domain/loaders/anatomy.loader.ts");
const anchors = await import("@banes-lab/web/core/ids/anatomy.ids.ts");
const panels = citationsIn(parsed)
.filter((citation) => loader.citedSource({ ...citation, kind: DEFINITION_KIND }) === null)
.map((citation) => ({ file, message: unresolvedPanel(citation.title, citation.name) }));
const links = stringsIn(parsed)
.flatMap((text) => fragmentsIn(text, anchors.DEFINITION_ANCHOR))
.map((fragment) => ({ file, message: unresolvedCitation(fragment) }));
return [...panels, ...links];
};
export const citationFindings = async function citationFindings(
routes: readonly ValidationRoute[],
): Promise<Finding[]> {
const found = await Promise.all(
routes.map(async (route) => {
const json = jsonFileOf(route.page, route.tab);
const raw = readOrNull(json);
return raw === null ? Promise.resolve([]) : checkCitations(json, raw);
}),
);
return found.flat();
};