# core/resolvers/binding.resolver.ts

> 106 lines of code and 40 definitions.

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

Listed in [core/resolvers](https://banes-lab.com/api/source/build-scripts/core/resolvers.md), after [core/resolvers/asset.resolver.ts](https://banes-lab.com/source/build-scripts/core/resolvers/asset.resolver.ts.md) and before [core/resolvers/browser.resolver.ts](https://banes-lab.com/source/build-scripts/core/resolvers/browser.resolver.ts.md).

## Definitions

- `bindingsIn` (lexical_declaration, line 89)
- `visit` (lexical_declaration, line 91)
- `isModuleSpecifier` (lexical_declaration, line 45)
- `bindingResolverOf` (lexical_declaration, line 104, exported)
- `WINDOWS_SEPARATOR` (lexical_declaration, line 6)
- `PATH_SEPARATOR` (lexical_declaration, line 7)
- `EXTENSION_SEPARATOR` (lexical_declaration, line 8)
- `keyOf` (lexical_declaration, line 10)
- `slashed` (lexical_declaration, line 11)
- `isCode` (lexical_declaration, line 15)
- `optionsOf` (lexical_declaration, line 19)
- `read` (lexical_declaration, line 24)
- `parsed` (lexical_declaration, line 25)
- `isDynamicImport` (lexical_declaration, line 29)
- `isImportType` (lexical_declaration, line 33)
- `SPECIFIER_PARENTS` (lexical_declaration, line 37)
- `symbolAt` (lexical_declaration, line 49)
- `shorthand` (lexical_declaration, line 50)
- `Scope` (interface_declaration, line 58)
- `bindingAt` (lexical_declaration, line 63)
- `symbol` (lexical_declaration, line 64)
- `declaration` (lexical_declaration, line 65)
- `home` (lexical_declaration, line 69)
- `file` (lexical_declaration, line 70)
- `start` (lexical_declaration, line 74)
- `here` (lexical_declaration, line 75)
- `at` (lexical_declaration, line 76)
- `name` (lexical_declaration, line 80)
- `nameStart` (lexical_declaration, line 81)
- `target` (lexical_declaration, line 85)
- `found` (lexical_declaration, line 90)
- `binding` (lexical_declaration, line 93)
- `code` (lexical_declaration, line 108, exported)
- `program` (lexical_declaration, line 109, exported)
- `paths` (lexical_declaration, line 110, exported)
- `byPath` (lexical_declaration, line 111, exported)
- `sources` (lexical_declaration, line 112, exported)
- `scope` (lexical_declaration, line 113, exported)
- `key` (lexical_declaration, line 115, exported)
- `source` (lexical_declaration, line 116, exported)

## Contained in

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

## Linked from

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

## Source

```typescript
import type { Binding, CodeFile } from "@banes-lab/web/types/code.types.js";
import { CODE_EXTENSIONS } from "#configuration/constants/binding.constants";
import { dirname } from "node:path";
import ts from "typescript";

const WINDOWS_SEPARATOR = "\\";
const PATH_SEPARATOR = "/";
const EXTENSION_SEPARATOR = ".";

const keyOf = function keyOf(file: string): string {
    const slashed = file.split(WINDOWS_SEPARATOR).join(PATH_SEPARATOR);
    return ts.sys.useCaseSensitiveFileNames ? slashed : slashed.toLowerCase();
};

const isCode = function isCode(path: string): boolean {
    return CODE_EXTENSIONS.has(path.slice(path.lastIndexOf(EXTENSION_SEPARATOR) + 1));
};

const optionsOf = function optionsOf(configDir: string): ts.CompilerOptions {
    const found = ts.findConfigFile(configDir, (file) => ts.sys.fileExists(file));
    if (found === undefined) {
        return { allowJs: true, noEmit: true };
    }
    const read = ts.readConfigFile(found, (file) => ts.sys.readFile(file));
    const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, dirname(found));
    return { ...parsed.options, allowJs: true, noEmit: true };
};

const isDynamicImport = function isDynamicImport(parent: ts.Node): boolean {
    return ts.isCallExpression(parent) && parent.expression.kind === ts.SyntaxKind.ImportKeyword;
};

const isImportType = function isImportType(parent: ts.Node): boolean {
    return ts.isLiteralTypeNode(parent) && ts.isImportTypeNode(parent.parent);
};

const SPECIFIER_PARENTS: readonly ((parent: ts.Node) => boolean)[] = [
    ts.isImportDeclaration,
    ts.isExportDeclaration,
    ts.isExternalModuleReference,
    isDynamicImport,
    isImportType,
];

const isModuleSpecifier = function isModuleSpecifier(node: ts.Node): boolean {
    return ts.isStringLiteralLike(node) && SPECIFIER_PARENTS.some((test) => test(node.parent));
};

const symbolAt = function symbolAt(checker: ts.TypeChecker, node: ts.Node): ts.Symbol | undefined {
    const shorthand =
        ts.isShorthandPropertyAssignment(node.parent) && node.parent.name === node
            ? checker.getShorthandAssignmentValueSymbol(node.parent)
            : undefined;
    const found = shorthand ?? checker.getSymbolAtLocation(node);
    return found !== undefined && (found.flags & ts.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(found) : found;
};

interface Scope {
    readonly checker: ts.TypeChecker;
    readonly paths: ReadonlyMap<string, string>;
}

const bindingAt = function bindingAt(scope: Scope, source: ts.SourceFile, node: ts.Node): Binding | null {
    const symbol = symbolAt(scope.checker, node);
    const declaration = symbol?.valueDeclaration ?? symbol?.declarations?.[0];
    if (symbol === undefined || declaration === undefined) {
        return null;
    }
    const home = declaration.getSourceFile();
    const file = scope.paths.get(keyOf(home.fileName));
    if (file === undefined) {
        return null;
    }
    const start = node.getStart(source);
    const here = source.getLineAndCharacterOfPosition(start);
    const at = { column: here.character, line: here.line + 1 };
    if (ts.isSourceFile(declaration)) {
        return { ...at, file, module: true, name: symbol.getName(), target: 1 };
    }
    const name = ts.getNameOfDeclaration(declaration) ?? declaration;
    const nameStart = name.getStart(home);
    if (home === source && nameStart === start) {
        return null;
    }
    const target = home.getLineAndCharacterOfPosition(nameStart).line + 1;
    return { ...at, file, module: false, name: name.getText(home), target };
};

const bindingsIn = function bindingsIn(scope: Scope, source: ts.SourceFile): readonly Binding[] {
    const found: Binding[] = [];
    const visit = function visit(node: ts.Node): void {
        if (ts.isIdentifier(node) || ts.isPrivateIdentifier(node) || isModuleSpecifier(node)) {
            const binding = bindingAt(scope, source, node);
            if (binding !== null) {
                found.push(binding);
            }
        }
        ts.forEachChild(node, visit);
    };
    visit(source);
    return found;
};

export const bindingResolverOf = function bindingResolverOf(
    files: readonly CodeFile[],
    configDir: string,
): (path: string) => readonly Binding[] | null {
    const code = files.filter((file) => isCode(file.path));
    const program = ts.createProgram({ options: optionsOf(configDir), rootNames: code.map((file) => file.absolute) });
    const paths = new Map(code.map((file) => [keyOf(file.absolute), file.path] as const));
    const byPath = new Map(code.map((file) => [file.path, keyOf(file.absolute)] as const));
    const sources = new Map(program.getSourceFiles().map((source) => [keyOf(source.fileName), source] as const));
    const scope: Scope = { checker: program.getTypeChecker(), paths };
    return (path) => {
        const key = byPath.get(path);
        const source = key === undefined ? undefined : sources.get(key);
        return source === undefined ? null : bindingsIn(scope, source);
    };
};
```
