# core/converters/definition.converter.ts

> 195 lines of code and 45 definitions.

Tree: Site tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/tree#file-core-converters-definition-converter-ts
Source text: https://banes-lab.com/source/tree/core/converters/definition.converter.ts.txt

Listed in [core/converters](https://banes-lab.com/api/source/tree/core/converters.md), after [core/converters/code.converter.ts](https://banes-lab.com/source/tree/core/converters/code.converter.ts.md) and before [core/converters/folder.converter.ts](https://banes-lab.com/source/tree/core/converters/folder.converter.ts.md).

## Definitions

- `bySuffix` (lexical_declaration, line 148, exported)
- `existingIn` (lexical_declaration, line 92)
- `bySuffixIn` (lexical_declaration, line 123, exported)
- `definitionIndexOf` (lexical_declaration, line 143, exported)
- `resolvePath` (lexical_declaration, line 149, exported)
- `treeFrom` (lexical_declaration, line 72)
- `collect` (lexical_declaration, line 23)
- `containsInOrder` (lexical_declaration, line 76)
- `resolveFolder` (lexical_declaration, line 160, exported)
- `citedDefinition` (lexical_declaration, line 178, exported)
- `specifierResolverOf` (lexical_declaration, line 120, exported)
- `heldOf` (lexical_declaration, line 40)
- `resolveOwnPath` (lexical_declaration, line 168, exported)
- `RELATIVE_HERE` (lexical_declaration, line 13)
- `RELATIVE_UP` (lexical_declaration, line 14)
- `Held` (interface_declaration, line 16)
- `entry` (lexical_declaration, line 28)
- `bare` (lexical_declaration, line 48)
- `trimmed` (lexical_declaration, line 49)
- `unrooted` (lexical_declaration, line 50)
- `step` (lexical_declaration, line 56)
- `normalize` (lexical_declaration, line 63)
- `endsWithTail` (lexical_declaration, line 67)
- `at` (lexical_declaration, line 77)
- `inFile` (lexical_declaration, line 88)
- `typed` (lexical_declaration, line 96)
- `SuffixLookup` (type_alias_declaration, line 100)
- `lastSegment` (lexical_declaration, line 102)
- `suffixLookupOf` (lexical_declaration, line 106)
- `byName` (lexical_declaration, line 107)
- `name` (lexical_declaration, line 109)
- `matches` (lexical_declaration, line 113)
- `tail` (lexical_declaration, line 127, exported)
- `base` (lexical_declaration, line 137, exported)
- `local` (lexical_declaration, line 138, exported)
- `held` (lexical_declaration, line 144, exported)
- `definitionsNamed` (lexical_declaration, line 145, exported)
- `candidate` (lexical_declaration, line 169, exported)
- `own` (lexical_declaration, line 171, exported)
- `resolveSpecifier` (lexical_declaration, line 177, exported)
- `found` (lexical_declaration, line 182, exported)
- `site` (lexical_declaration, line 183, exported)
- `[only]` (lexical_declaration, line 184, exported)
- `tree` (lexical_declaration, line 191, exported)
- `all` (lexical_declaration, line 204, exported)

## Uses

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

## Used by

- [core/loaders/definition.loader.ts](https://banes-lab.com/source/tree/core/loaders/definition.loader.ts.md)

## Source

```typescript
import type { AnatomyFolder, DefinitionLocation } from "#types/anatomy.types";
import {
    EXTENSION_SEPARATOR,
    PATH_SEPARATOR,
    SELF_IMPORT_MARK,
    TYPESCRIPT_EXTENSION,
} from "#configuration/constants/anatomy.constants";
import { localPath, qualifiedPath, treeOf } from "#core/converters/folder.converter";
import type { DefinitionIndex } from "#types/definition.types";
import type { EvidenceNode } from "#types/evidence.types";
import { TREE_TAB } from "#core/ids/anatomy.ids";

const RELATIVE_HERE = ".";
const RELATIVE_UP = "..";

interface Held {
    readonly files: Set<string>;
    readonly folders: Set<string>;
    readonly moduleNames: Map<string, DefinitionLocation[]>;
    readonly names: Map<string, DefinitionLocation[]>;
}

const collect = function collect(folder: AnatomyFolder, held: Held): void {
    held.folders.add(folder.path);
    for (const file of folder.files) {
        held.files.add(file.path);
        for (const definition of file.definitions) {
            const entry = { file: file.path, line: definition.line, name: definition.name };
            held.names.set(definition.name, [...(held.names.get(definition.name) ?? []), entry]);
            if (!definition.local) {
                held.moduleNames.set(definition.name, [...(held.moduleNames.get(definition.name) ?? []), entry]);
            }
        }
    }
    for (const child of folder.folders) {
        collect(child, held);
    }
};

const heldOf = function heldOf(roots: readonly AnatomyFolder[]): Held {
    const held: Held = { files: new Set(), folders: new Set(), moduleNames: new Map(), names: new Map() };
    for (const root of roots) {
        collect(root, held);
    }
    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;
};

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 normalize = function normalize(segments: readonly string[]): string {
    return segments.reduce<string[]>(step, []).join(PATH_SEPARATOR);
};

const endsWithTail = function endsWithTail(path: string, tail: string): boolean {
    const local = localPath(path);
    return local.endsWith(PATH_SEPARATOR + tail) || local === tail;
};

const treeFrom = function treeFrom(from: string | null): string {
    return from === null ? TREE_TAB : treeOf(from);
};

const containsInOrder = function containsInOrder(path: string, parts: readonly string[]): boolean {
    let at = 0;
    for (const part of parts) {
        const found = path.indexOf(part, at);
        if (found === -1) {
            return false;
        }
        at = found + part.length;
    }
    return true;
};

const inFile = function inFile(location: DefinitionLocation, file: string | null): boolean {
    return file === null || location.file === file || location.file.endsWith(PATH_SEPARATOR + file);
};

const existingIn = function existingIn(files: ReadonlySet<string>, candidate: string): string | null {
    if (files.has(candidate)) {
        return candidate;
    }
    const typed = candidate + EXTENSION_SEPARATOR + TYPESCRIPT_EXTENSION;
    return files.has(typed) ? typed : null;
};

type SuffixLookup = (tail: string, tree?: string | null) => string | null;

const lastSegment = function lastSegment(path: string): string {
    return path.slice(path.lastIndexOf(PATH_SEPARATOR) + 1);
};

const suffixLookupOf = function suffixLookupOf(files: ReadonlySet<string>): SuffixLookup {
    const byName = new Map<string, string[]>();
    for (const path of files) {
        const name = lastSegment(path);
        byName.set(name, [...(byName.get(name) ?? []), path]);
    }
    return (tail, tree = null) => {
        const matches = (byName.get(lastSegment(tail)) ?? []).filter(
            (path) => (tree === null || treeOf(path) === tree) && endsWithTail(path, tail),
        );
        return matches.length === 1 ? (matches[0] ?? null) : null;
    };
};

export const specifierResolverOf = function specifierResolverOf(
    files: ReadonlySet<string>,
): (specifier: string, fromPath: string) => string | null {
    const bySuffixIn = suffixLookupOf(files);
    return (specifier, fromPath) => {
        const tree = treeOf(fromPath);
        if (specifier.startsWith(SELF_IMPORT_MARK)) {
            const tail = specifier.slice(SELF_IMPORT_MARK.length);
            return (
                existingIn(files, qualifiedPath(tree, tail)) ??
                bySuffixIn(tail + EXTENSION_SEPARATOR + TYPESCRIPT_EXTENSION, tree) ??
                bySuffixIn(tail, tree)
            );
        }
        if (!specifier.startsWith(RELATIVE_HERE)) {
            return null;
        }
        const base = localPath(fromPath).split(PATH_SEPARATOR).slice(0, -1);
        const local = normalize([...base, ...specifier.split(PATH_SEPARATOR)]);
        return existingIn(files, qualifiedPath(tree, local));
    };
};

export const definitionIndexOf = function definitionIndexOf(roots: readonly AnatomyFolder[]): DefinitionIndex {
    const held = heldOf(roots);
    const definitionsNamed = function definitionsNamed(name: string): readonly DefinitionLocation[] {
        return held.names.get(name) ?? [];
    };
    const bySuffix = suffixLookupOf(held.files);
    const resolvePath = function resolvePath(text: string, from: string | null = null): string | null {
        const candidate = bare(text);
        if (candidate.length === 0) {
            return null;
        }
        const own = qualifiedPath(treeFrom(from), candidate);
        if (held.files.has(own)) {
            return own;
        }
        return (from === null ? null : bySuffix(candidate, treeFrom(from))) ?? bySuffix(candidate);
    };
    const resolveFolder = function resolveFolder(text: string, from: string | null = null): string | null {
        const candidate = bare(text);
        if (candidate.length === 0) {
            return null;
        }
        const own = qualifiedPath(treeFrom(from), candidate);
        return held.folders.has(own) ? own : null;
    };
    const resolveOwnPath = function resolveOwnPath(text: string, from: string): string | null {
        const candidate = bare(text);
        const tree = treeOf(from);
        const own = qualifiedPath(tree, candidate);
        if (candidate.length === 0 || held.files.has(own)) {
            return candidate.length === 0 ? null : own;
        }
        return bySuffix(candidate, tree);
    };
    const resolveSpecifier = specifierResolverOf(held.files);
    const citedDefinition = function citedDefinition(
        name: string,
        file: string | null = null,
    ): DefinitionLocation | null {
        const found = definitionsNamed(name).filter((location) => inFile(location, file));
        const site = found.filter((location) => treeOf(location.file) === TREE_TAB);
        const [only] = found.length === 1 ? found : site;
        return (found.length === 1 || site.length === 1) && only !== undefined ? only : null;
    };
    return {
        citedDefinition,
        definitionsNamed,
        filesMatching: function filesMatching(parts, from) {
            const tree = treeOf(from);
            return [...held.files].filter((path) => treeOf(path) === tree && containsInOrder(localPath(path), parts));
        },
        listDefinitions: function listDefinitions() {
            return [...held.names.values()].flat();
        },
        locateNode: function locateNode(node: EvidenceNode) {
            if (node.kind === "definition") {
                return citedDefinition(node.name, node.file)?.file ?? null;
            }
            return node.kind === "file" ? resolvePath(node.name) : resolveFolder(node.words.join(PATH_SEPARATOR));
        },
        moduleDefinitionsNamedFrom: function moduleDefinitionsNamedFrom(name, from) {
            const all = held.moduleNames.get(name) ?? [];
            return all.filter((location) => treeOf(location.file) === treeOf(from));
        },
        resolveFolder,
        resolveOwnPath,
        resolvePath,
        resolveSpecifier,
    };
};
```
