# core/converters/specifier.converter.ts

> 37 lines of code and 14 definitions.

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

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

## Definitions

- `edgeOf` (lexical_declaration, line 24)
- `importEdgesOf` (lexical_declaration, line 29, exported)
- `KEY_SEPARATOR` (lexical_declaration, line 6)
- `PATH_SEPARATOR` (lexical_declaration, line 7)
- `containerOfPath` (lexical_declaration, line 9)
- `fileEdges` (lexical_declaration, line 14)
- `from` (lexical_declaration, line 15)
- `bindings` (lexical_declaration, line 16)
- `target` (lexical_declaration, line 18)
- `to` (lexical_declaration, line 19)
- `cut` (lexical_declaration, line 25)
- `resolve` (lexical_declaration, line 33, exported)
- `counts` (lexical_declaration, line 34, exported)
- `keys` (lexical_declaration, line 35, exported)

## Contained in

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

## Linked from

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

## Source

```typescript
import type { DiskFile } from "#types/structure.types";
import type { ImportEdge } from "@banes-lab/web/types/anatomy.types.js";
import type { SpecifierResolver } from "#types/anatomy.types";
import { ingestImports } from "@govlab/patterns";

const KEY_SEPARATOR = "::";
const PATH_SEPARATOR = "/";

const containerOfPath = function containerOfPath(path: string): string {
    const cut = path.indexOf(PATH_SEPARATOR);
    return cut === -1 ? "" : path.slice(0, cut);
};

const fileEdges = async function fileEdges(file: DiskFile, resolve: SpecifierResolver): Promise<string[]> {
    const from = containerOfPath(file.path);
    const bindings = await ingestImports(file.text, file.path);
    return bindings.flatMap((binding) => {
        const target = resolve(binding.source, file.path);
        const to = target === null ? "" : containerOfPath(target);
        return from !== "" && to !== "" && to !== from ? [from + KEY_SEPARATOR + to] : [];
    });
};

const edgeOf = function edgeOf(key: string, count: number): ImportEdge {
    const cut = key.indexOf(KEY_SEPARATOR);
    return { count, from: key.slice(0, cut), to: key.slice(cut + KEY_SEPARATOR.length) };
};

export const importEdgesOf = async function importEdgesOf(
    files: readonly DiskFile[],
    resolverOf: (files: ReadonlySet<string>) => SpecifierResolver,
): Promise<ImportEdge[]> {
    const resolve = resolverOf(new Set(files.map((file) => file.path)));
    const counts = new Map<string, number>();
    const keys = await Promise.all(files.map(async (file) => fileEdges(file, resolve)));
    for (const key of keys.flat()) {
        counts.set(key, (counts.get(key) ?? 0) + 1);
    }
    return [...counts.entries()]
        .map(([key, count]) => edgeOf(key, count))
        .sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
};
```
