# core/converters/source.converter.ts

> 160 lines of code and 31 definitions.

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

Listed in [core/converters](https://banes-lab.com/api/source/build-scripts/core/converters.md), after [core/converters/snapshot.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/snapshot.converter.ts.md) and before [core/converters/source.route.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/source.route.converter.ts.md).

## Definitions

- `fileLinks` (lexical_declaration, line 79)
- `filesOf` (lexical_declaration, line 50)
- `summaryOf` (lexical_declaration, line 54, exported)
- `signatureAt` (lexical_declaration, line 90)
- `sourceLeaves` (lexical_declaration, line 135, exported)
- `sourceFiles` (lexical_declaration, line 59, exported)
- `relationsOf` (lexical_declaration, line 118)
- `definitionsOf` (lexical_declaration, line 95)
- `LINE_END` (lexical_declaration, line 18)
- `FILE_KIND` (lexical_declaration, line 19)
- `FRAGMENT` (lexical_declaration, line 20)
- `toolsOf` (lexical_declaration, line 22, exported)
- `canonicalOf` (lexical_declaration, line 35, exported)
- `local` (lexical_declaration, line 37, exported)
- `cut` (lexical_declaration, line 38, exported)
- `infix` (lexical_declaration, line 39, exported)
- `anchor` (lexical_declaration, line 43, exported)
- `definitions` (lexical_declaration, line 55, exported)
- `files` (lexical_declaration, line 84)
- `held` (lexical_declaration, line 91)
- `named` (lexical_declaration, line 100)
- `callees` (lexical_declaration, line 122)
- `callers` (lexical_declaration, line 123)
- `containedIn` (lexical_declaration, line 124)
- `{ file, identity, tree }` (lexical_declaration, line 141, exported)
- `path` (lexical_declaration, line 142, exported)
- `text` (lexical_declaration, line 143, exported)
- `textAddress` (lexical_declaration, line 144, exported)
- `relations` (lexical_declaration, line 145, exported)
- `data` (lexical_declaration, line 146, exported)
- `markdown` (lexical_declaration, line 165, exported)

## Contained in

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

## Uses

- [core/converters/link.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/link.converter.ts.md)
- [core/resolvers/catalog.resolver.ts](https://banes-lab.com/source/build-scripts/core/resolvers/catalog.resolver.ts.md)

## Used by

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

## Linked from

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

## Source

```typescript
import type { AnatomyFile, AnatomyFolder, DefinitionRef } from "@banes-lab/web/types/anatomy.types.js";
import {
    CONTAINED_IN_RELATION,
    EVIDENCE_FOR_RELATION,
    LINKED_FROM_RELATION,
    USED_BY_RELATION,
    USES_RELATION,
} from "@banes-lab/web/constants/graph.constants";
import type { DefinitionData, SourceFile, SourceSources, SourceTools, SourceTree } from "#types/source.types";
import type { Leaf, Link } from "#types/catalog.types";
import { sourceLeaf, sourceText } from "#core/resolvers/catalog.resolver";
import { ANATOMY_PREFIX } from "#configuration/constants/graph.constants";
import type { WebModules } from "#types/loader.types";
import { placedOf } from "#core/converters/location.converter";
import { relationGroups } from "#core/converters/link.converter";
import { renderSourceLeaf } from "#core/formatters/source.formatter";

const LINE_END = "\n";
const FILE_KIND = "file";
const FRAGMENT = "#";

export const toolsOf = function toolsOf(
    web: Pick<WebModules, "anatomyConstants" | "anatomyIds" | "folder" | "source">,
): SourceTools {
    return {
        definitionAnchor: web.anatomyIds.DEFINITION_ANCHOR,
        fileId: web.source.fileId,
        languageOf: web.source.languageOf,
        lineInfix: web.anatomyConstants.LINE_INFIX,
        localPath: web.folder.localPath,
        nodeHref: web.source.nodeHref,
    };
};

export const canonicalOf = function canonicalOf(site: string, tools: SourceTools): (href: string) => string {
    return (href) => {
        const local = href.startsWith(site) ? href.slice(site.length) : href;
        const cut = local.indexOf(FRAGMENT);
        const infix = cut === -1 ? -1 : local.indexOf(tools.lineInfix, cut);
        if (infix === -1) {
            return local;
        }
        const anchor = local.slice(cut + 1, infix);
        return anchor.startsWith(tools.definitionAnchor)
            ? tools.nodeHref(local.slice(infix + tools.lineInfix.length), null)
            : local.slice(0, infix);
    };
};

const filesOf = function filesOf(folder: AnatomyFolder): readonly AnatomyFile[] {
    return [...folder.files, ...folder.folders.flatMap(filesOf)];
};

export const summaryOf = function summaryOf(file: Pick<AnatomyFile, "definitions" | "stats">): string {
    const definitions = file.definitions.length;
    return `${String(file.stats.lines.code)} lines of code and ${String(definitions)} definition${definitions === 1 ? "" : "s"}.`;
};

export const sourceFiles = function sourceFiles(
    trees: readonly SourceTree[],
    tools: SourceTools,
): readonly SourceFile[] {
    return trees.flatMap((tree) =>
        filesOf(tree.snapshot.tree).map((file) => ({
            file,
            identity: {
                address: sourceLeaf(tree.tab, tools.localPath(file.path)),
                href: tools.nodeHref(file.path, null),
                kind: FILE_KIND,
                ref: ANATOMY_PREFIX + tools.fileId(file.path),
                summary: summaryOf(file),
                title: tools.localPath(file.path),
            },
            tree,
        })),
    );
};

const fileLinks = function fileLinks(
    refs: readonly DefinitionRef[],
    own: string,
    sources: SourceSources,
): readonly Link[] {
    const files = [...new Set(refs.map((ref) => ref.file))].filter((file) => file !== own);
    return files
        .toSorted((a, b) => a.localeCompare(b))
        .map((file) => sources.linker.link(sources.tools.localPath(file), ANATOMY_PREFIX + sources.tools.fileId(file)));
};

const signatureAt = function signatureAt(lines: readonly string[] | null, line: number): string | null {
    const held = line < 1 || lines === null ? undefined : lines.at(line - 1);
    return held === undefined || held.trim().length === 0 ? null : held.trim();
};

const definitionsOf = function definitionsOf(
    file: AnatomyFile,
    sources: SourceSources,
    lines: readonly string[] | null,
): readonly DefinitionData[] {
    const named = (ref: DefinitionRef): object => ({
        file: sources.tools.localPath(ref.file),
        name: ref.name,
        source: sources.linker.link(sources.tools.localPath(ref.file), ANATOMY_PREFIX + sources.tools.fileId(ref.file)),
    });
    return file.definitions.map((definition) => ({
        callable: definition.callable,
        callees: definition.callees.map(named),
        callers: definition.callers.map(named),
        exported: definition.exported,
        flow: definition.flow,
        kind: definition.kind,
        line: definition.line,
        name: definition.name,
        signature: signatureAt(lines, definition.line),
    }));
};

const relationsOf = async function relationsOf(
    { file, identity }: SourceFile,
    sources: SourceSources,
): Promise<ReturnType<typeof relationGroups>> {
    const callees = file.definitions.flatMap((definition) => definition.callees);
    const callers = file.definitions.flatMap((definition) => definition.callers);
    const containedIn = sources.folder(identity.ref)?.containedIn ?? null;
    return relationGroups([
        { links: containedIn === null ? [] : [containedIn], relation: CONTAINED_IN_RELATION },
        { links: fileLinks(callees, file.path, sources), relation: USES_RELATION },
        { links: fileLinks(callers, file.path, sources), relation: USED_BY_RELATION },
        { links: await sources.grounds(identity.ref), relation: EVIDENCE_FOR_RELATION },
        ...sources.checks(identity.ref),
        { links: sources.linkedBy(identity.ref), relation: LINKED_FROM_RELATION },
    ]);
};

export const sourceLeaves = async function sourceLeaves(
    files: readonly SourceFile[],
    sources: SourceSources,
): Promise<readonly Leaf[]> {
    return Promise.all(
        files.map(async (source) => {
            const { file, identity, tree } = source;
            const path = sources.tools.localPath(file.path);
            const text = file.source === null || file.generated ? null : sources.textOf(file.source);
            const textAddress = text === null ? null : sourceText(tree.tab, path);
            const relations = await relationsOf(source, sources);
            const data = {
                definitions: definitionsOf(file, sources, text === null ? null : text.split(LINE_END)),
                document: file.document,
                findings: file.findings.map((finding) => ({ ...finding, file: sources.tools.localPath(finding.file) })),
                generated: file.generated,
                href: identity.href === null ? null : sources.linker.site + identity.href,
                language: sources.tools.languageOf(file.name),
                layer: file.layer,
                name: file.name,
                path,
                ref: identity.ref,
                relations,
                slots: file.slots,
                stats: file.stats,
                summary: identity.summary,
                text: textAddress === null ? null : sources.linker.site + textAddress,
                tree: tree.tab,
                ...placedOf(sources.placement(identity.ref)),
            };
            const markdown = renderSourceLeaf(data, tree.label, text);
            return text === null || textAddress === null
                ? { data, identity, markdown }
                : { data, identity, markdown, text: { address: textAddress, body: text } };
        }),
    );
};
```
