# core/analyzers/search.analyzer.ts

> 113 lines of code and 19 definitions.

Tree: Site tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/tree#file-core-analyzers-search-analyzer-ts
Source text: https://banes-lab.com/assets/sources/source.b24f6396b97f809fa197bd948ea8407610a15ab2459247a0c443a04b8c6b3812.generated.txt

## Definitions

- `earliest` (lexical_declaration, line 37)
- `sectionPath` (lexical_declaration, line 29, exported)
- `anchorsLinkedFrom` (lexical_declaration, line 54, exported)
- `anchorPath` (lexical_declaration, line 33, exported)
- `textsOf` (lexical_declaration, line 14, exported)
- `anchorsOf` (lexical_declaration, line 116, exported)
- `placeOf` (lexical_declaration, line 125, exported)
- `linksOf` (lexical_declaration, line 63, exported)
- `routeOf` (lexical_declaration, line 41, exported)
- `evidenceOf` (lexical_declaration, line 82, exported)
- `UNPLACED` (lexical_declaration, line 10, exported)
- `FOLLOWS` (lexical_declaration, line 12, exported)
- `anchorOf` (lexical_declaration, line 49)
- `cut` (lexical_declaration, line 50)
- `anchor` (lexical_declaration, line 58, exported)
- `positions` (lexical_declaration, line 87, exported)
- `at` (lexical_declaration, line 89, exported)
- `isGlossary` (lexical_declaration, line 112, exported)
- `blocks` (lexical_declaration, line 117, exported)

## Uses

- [core/assets/link.assets.ts](https://banes-lab.com/source/tree/core/assets/link.assets.ts.md)
- [core/converters/markup.converter.ts](https://banes-lab.com/source/tree/core/converters/markup.converter.ts.md)

## Used by

- [domain/converters/search.index.converter.ts](https://banes-lab.com/source/tree/domain/converters/search.index.converter.ts.md)

## Source

```typescript
import type { Block, GlossaryBlock } from "#types/block.types";
import { pagePath, tabLink } from "#assets/link.assets";
import { ANCHOR_PREFIX } from "#configuration/constants/document.constants";
import type { Evidence } from "#types/evidence.types";
import { SEARCH_IGNORED_KEYS } from "#configuration/constants/search.constants";
import type { SearchSource } from "#types/search.types";
import type { Section } from "#types/document.types";
import { convertMarkup } from "#core/converters/markup.converter";

export const UNPLACED = Number.POSITIVE_INFINITY;

export const FOLLOWS = 0.5;

export const textsOf = function textsOf(value: unknown): readonly string[] {
    if (typeof value === "string") {
        return [value];
    }
    if (Array.isArray(value)) {
        return value.flatMap((item: unknown) => textsOf(item));
    }
    if (typeof value === "object" && value !== null) {
        return Object.entries(value).flatMap(([key, inner]: readonly [string, unknown]) =>
            SEARCH_IGNORED_KEYS.has(key) ? [] : textsOf(inner),
        );
    }
    return [];
};

export const sectionPath = function sectionPath(page: string, tab: string, first: boolean, section: string): string {
    return first ? pagePath(page) + ANCHOR_PREFIX + section : tabLink(page, tab, section);
};

export const anchorPath = function anchorPath(source: SearchSource, section: string): string {
    return sectionPath(source.page, source.tab.id, source.first || source.layout === "document", section);
};

const earliest = function earliest(positions: Map<string, number>, key: string, at: number): void {
    positions.set(key, Math.min(positions.get(key) ?? UNPLACED, at));
};

export const routeOf = function routeOf(route: readonly string[]): ReadonlyMap<string, number> {
    const positions = new Map<string, number>();
    for (const [at, path] of route.entries()) {
        earliest(positions, path, at);
    }
    return positions;
};

const anchorOf = function anchorOf(href: string): string | null {
    const cut = href.indexOf(ANCHOR_PREFIX);
    return cut === -1 ? null : href.slice(cut + ANCHOR_PREFIX.length);
};

export const anchorsLinkedFrom = function anchorsLinkedFrom(section: Section): readonly string[] {
    return textsOf(section)
        .flatMap((text) => convertMarkup(text))
        .flatMap((run) => {
            const anchor = run.href === undefined ? null : anchorOf(run.href);
            return anchor === null ? [] : [anchor];
        });
};

export const linksOf = function linksOf(
    sources: readonly SearchSource[],
    route: ReadonlyMap<string, number>,
): ReadonlyMap<string, number> {
    const positions = new Map<string, number>();
    for (const source of sources) {
        for (const section of source.tab.sections) {
            const at = route.get(anchorPath(source, section.id));
            if (at === undefined) {
                continue;
            }
            for (const anchor of anchorsLinkedFrom(section)) {
                earliest(positions, anchor, at + FOLLOWS);
            }
        }
    }
    return positions;
};

export const evidenceOf = function evidenceOf(
    evidence: readonly Evidence[],
    route: ReadonlyMap<string, number>,
    firstTabs: ReadonlyMap<string, string>,
): ReadonlyMap<string, number> {
    const positions = new Map<string, number>();
    for (const { nodes, subject } of evidence) {
        const at =
            subject.kind === "chapter"
                ? route.get(
                      sectionPath(
                          subject.page,
                          subject.tab,
                          firstTabs.get(subject.page) === subject.tab,
                          subject.section,
                      ),
                  )
                : undefined;
        if (at === undefined) {
            continue;
        }
        for (const node of nodes) {
            if (node.kind === "definition") {
                earliest(positions, node.name, at + FOLLOWS);
            }
        }
    }
    return positions;
};

export const isGlossary = function isGlossary(block: Block): block is GlossaryBlock {
    return block.kind === "glossary";
};

export const anchorsOf = function anchorsOf(section: Section): readonly string[] {
    const blocks = [...(section.blocks ?? []), ...section.subsections.flatMap((sub) => sub.blocks ?? [])];
    return [
        section.id,
        ...section.subsections.flatMap((sub) => (sub.id === undefined ? [] : [sub.id])),
        ...blocks.filter(isGlossary).flatMap((block) => block.entries.flatMap((entry) => entry.id ?? [])),
    ];
};

export const placeOf = function placeOf(anchors: readonly string[], links: ReadonlyMap<string, number>): number {
    return Math.min(UNPLACED, ...anchors.map((anchor) => links.get(anchor) ?? UNPLACED));
};
```
