# core/validators/definition.validator.ts

> 93 lines of code and 30 definitions.

Tree: Bane's Lab Build Scripts
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/build-scripts#file-build-scripts-core-validators-definition-validator-ts
Source text: https://banes-lab.com/source/build-scripts/core/validators/definition.validator.ts.txt

Listed in [core/validators](https://banes-lab.com/api/source/build-scripts/core/validators.md), after [core/validators/catalog.walk.validator.ts](https://banes-lab.com/source/build-scripts/core/validators/catalog.walk.validator.ts.md) and before [core/validators/element.validator.ts](https://banes-lab.com/source/build-scripts/core/validators/element.validator.ts.md).

## Definitions

- `citationFindings` (lexical_declaration, line 90, exported)
- `KIND_KEY` (lexical_declaration, line 7)
- `DEFINITION_KIND` (lexical_declaration, line 8)
- `DEFINITION_NAME_KEY` (lexical_declaration, line 9)
- `DEFINITION_FILE_KEY` (lexical_declaration, line 10)
- `DEFINITION_LINE_KEY` (lexical_declaration, line 11)
- `DEFINITION_TITLE_KEY` (lexical_declaration, line 12)
- `FRAGMENT_MARK` (lexical_declaration, line 13)
- `FRAGMENT_ENDS` (lexical_declaration, line 14)
- `Citation` (interface_declaration, line 16)
- `citationsIn` (lexical_declaration, line 23)
- `name` (lexical_declaration, line 30)
- `file` (lexical_declaration, line 31)
- `line` (lexical_declaration, line 32)
- `title` (lexical_declaration, line 33)
- `own` (lexical_declaration, line 34)
- `fragmentEnd` (lexical_declaration, line 48)
- `fragmentsIn` (lexical_declaration, line 56)
- `at` (lexical_declaration, line 58)
- `end` (lexical_declaration, line 60)
- `stringsIn` (lexical_declaration, line 67)
- `checkCitations` (lexical_declaration, line 77)
- `parsed` (lexical_declaration, line 78)
- `loader` (lexical_declaration, line 79)
- `anchors` (lexical_declaration, line 80)
- `panels` (lexical_declaration, line 81)
- `links` (lexical_declaration, line 84)
- `found` (lexical_declaration, line 93, exported)
- `json` (lexical_declaration, line 95, exported)
- `raw` (lexical_declaration, line 96, exported)

## Contained in

- [core/validators](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-validators.md)

## Used by

- [core/coordinators/validation.coordinator.ts](https://banes-lab.com/source/build-scripts/core/coordinators/validation.coordinator.ts.md)

## Linked from

- [core/coordinators](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-coordinators.md)

## Source

```typescript
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();
};
```
