# codemods/analyzers/binding.analyzer.ts

> 128 lines of code and 21 definitions.

Tree: Governance tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/governance#file-governance-codemods-analyzers-binding-analyzer-ts
Source text: https://banes-lab.com/source/governance/codemods/analyzers/binding.analyzer.ts.txt

Listed in [codemods/analyzers](https://banes-lab.com/api/source/governance/codemods/analyzers.md), before [codemods/analyzers/code-point.analyzer.ts](https://banes-lab.com/source/governance/codemods/analyzers/code-point.analyzer.ts.md).

## Definitions

- `findingsForImplements` (lexical_declaration, line 60)
- `findingForMember` (lexical_declaration, line 30)
- `overridesOf` (lexical_declaration, line 76)
- `visit` (lexical_declaration, line 125, exported)
- `callableFieldDecl` (lexical_declaration, line 7)
- `methodImplementing` (lexical_declaration, line 18)
- `decl` (lexical_declaration, line 35)
- `name` (lexical_declaration, line 39)
- `implementation` (lexical_declaration, line 40)
- `memberSource` (lexical_declaration, line 44)
- `ifaceType` (lexical_declaration, line 64)
- `iface` (lexical_declaration, line 65)
- `finding` (lexical_declaration, line 68)
- `symbol` (lexical_declaration, line 82)
- `classesIn` (lexical_declaration, line 90)
- `collectOverrides` (lexical_declaration, line 102, exported)
- `pairs` (lexical_declaration, line 107, exported)
- `scanSourceFile` (lexical_declaration, line 116, exported)
- `out` (lexical_declaration, line 121, exported)
- `contextFor` (lexical_declaration, line 122, exported)
- `ctx` (lexical_declaration, line 127, exported)

## Uses

- [codemods/predicates/binding.predicate.ts](https://banes-lab.com/source/governance/codemods/predicates/binding.predicate.ts.md)
- [codemods/selectors/heritage.selector.ts](https://banes-lab.com/source/governance/codemods/selectors/heritage.selector.ts.md)
- [codemods/selectors/program.selector.ts](https://banes-lab.com/source/governance/codemods/selectors/program.selector.ts.md)

## Source

```typescript
import type { BindingContext, BindingFinding } from "../../types/analyzer.types.ts";
import { clausesOfToken, implementsClauses, memberNamesOf } from "../selectors/heritage.selector.ts";
import { lineOf, relPath } from "../selectors/program.selector.ts";
import ts from "typescript";
import { unsafeReason } from "../predicates/binding.predicate.ts";

const callableFieldDecl = function callableFieldDecl(
    checker: ts.TypeChecker,
    member: ts.Symbol,
): ts.PropertySignature | null {
    const decl = member.valueDeclaration ?? member.declarations?.[0] ?? null;
    if (!decl || !ts.isPropertySignature(decl)) {
        return null;
    }
    return checker.getTypeOfSymbolAtLocation(member, decl).getCallSignatures().length > 0 ? decl : null;
};

const methodImplementing = function methodImplementing(
    classNode: ts.ClassDeclaration,
    name: string,
): ts.MethodDeclaration | null {
    for (const classMember of classNode.members) {
        if (ts.isMethodDeclaration(classMember) && classMember.name.getText() === name) {
            return classMember;
        }
    }
    return null;
};

const findingForMember = function findingForMember(
    ctx: BindingContext,
    interfaceName: string,
    member: ts.Symbol,
): BindingFinding | null {
    const decl = callableFieldDecl(ctx.checker, member);
    if (!decl) {
        return null;
    }
    const name = member.getName();
    const implementation = methodImplementing(ctx.classNode, name);
    if (!implementation) {
        return null;
    }
    const memberSource = decl.getSourceFile();
    return {
        end: implementation.getEnd(),
        file: relPath(ctx.sourceFile.fileName),
        fileName: ctx.sourceFile.fileName,
        iface: interfaceName,
        ifaceFile: relPath(memberSource.fileName),
        ifaceLine: lineOf(memberSource, decl),
        line: lineOf(ctx.sourceFile, implementation),
        member: name,
        method: implementation,
        reason: unsafeReason(ctx, implementation, name),
        start: implementation.getStart(ctx.sourceFile),
    };
};

const findingsForImplements = function findingsForImplements(
    ctx: BindingContext,
    typeExpr: ts.ExpressionWithTypeArguments,
): BindingFinding[] {
    const ifaceType = ctx.checker.getTypeAtLocation(typeExpr.expression);
    const iface = ifaceType.getSymbol()?.getName() ?? "<interface>";
    const out: BindingFinding[] = [];
    for (const member of ifaceType.getProperties()) {
        const finding = findingForMember(ctx, iface, member);
        if (finding) {
            out.push(finding);
        }
    }
    return out;
};

const overridesOf = function overridesOf(
    checker: ts.TypeChecker,
    node: ts.ClassLikeDeclaration,
): [ts.Symbol, string[]][] {
    const out: [ts.Symbol, string[]][] = [];
    for (const typeExpr of clausesOfToken(node, ts.SyntaxKind.ExtendsKeyword)) {
        const symbol = checker.getSymbolAtLocation(typeExpr.expression);
        if (symbol) {
            out.push([symbol, memberNamesOf(node)]);
        }
    }
    return out;
};

const classesIn = function classesIn(sourceFile: ts.SourceFile): ts.ClassLikeDeclaration[] {
    const out: ts.ClassLikeDeclaration[] = [];
    const visit = function visit(node: ts.Node): void {
        if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
            out.push(node);
        }
        ts.forEachChild(node, visit);
    };
    ts.forEachChild(sourceFile, visit);
    return out;
};

export const collectOverrides = function collectOverrides(
    checker: ts.TypeChecker,
    sourceFiles: readonly ts.SourceFile[],
): Map<ts.Symbol, Set<string>> {
    const out = new Map<ts.Symbol, Set<string>>();
    const pairs = sourceFiles.flatMap((sourceFile) =>
        classesIn(sourceFile).flatMap((node) => overridesOf(checker, node)),
    );
    for (const [symbol, names] of pairs) {
        out.set(symbol, new Set([...(out.get(symbol) ?? []), ...names]));
    }
    return out;
};

export const scanSourceFile = function scanSourceFile(
    typeChecker: ts.TypeChecker,
    source: ts.SourceFile,
    overrideMap: ReadonlyMap<ts.Symbol, ReadonlySet<string>>,
): BindingFinding[] {
    const out: BindingFinding[] = [];
    const contextFor = function contextFor(declaration: ts.ClassDeclaration): BindingContext {
        return { checker: typeChecker, classNode: declaration, overrides: overrideMap, sourceFile: source };
    };
    const visit = function visit(node: ts.Node): void {
        if (ts.isClassDeclaration(node)) {
            const ctx = contextFor(node);
            for (const typeExpr of implementsClauses(typeChecker, node)) {
                out.push(...findingsForImplements(ctx, typeExpr));
            }
        }
        ts.forEachChild(node, visit);
    };
    ts.forEachChild(source, visit);
    return out;
};
```
