# core/analyzers/orchestration.analyzer.ts

> 137 lines of code and 36 definitions.

Tree: GovLab Docs
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/docs#file-docs-core-analyzers-orchestration-analyzer-ts
Source text: https://banes-lab.com/source/docs/core/analyzers/orchestration.analyzer.ts.txt

Listed in [core/analyzers](https://banes-lab.com/api/source/docs/core/analyzers.md), after [core/analyzers/markdown.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/markdown.analyzer.ts.md) and before [core/analyzers/package.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/package.analyzer.ts.md).

## Definitions

- `detectProtocol` (lexical_declaration, line 148, exported)
- `isNamedBinding` (lexical_declaration, line 34)
- `boundFunction` (lexical_declaration, line 38)
- `isTopLevelVariable` (lexical_declaration, line 58)
- `isCallableDeclaration` (lexical_declaration, line 62)
- `functionBodyOf` (lexical_declaration, line 48)
- `#keepIdentifierCallee` (method_definition, line 138)
- `MAX_MESSAGES` (lexical_declaration, line 7)
- `MIN_PARTICIPANT_LEN` (lexical_declaration, line 8)
- `MIN_ORCH_PARTICIPANTS` (lexical_declaration, line 9)
- `MIN_ORCH_MESSAGES` (lexical_declaration, line 10)
- `THIS` (lexical_declaration, line 11)
- `foldTarget` (lexical_declaration, line 13)
- `key` (lexical_declaration, line 18)
- `skip` (lexical_declaration, line 19)
- `init` (lexical_declaration, line 41)
- `ProtocolDetector` (class_declaration, line 71)
- `#checker` (public_field_definition, line 72)
- `constructor` (method_definition, line 74)
- `bestIn` (method_definition, line 78)
- `current` (lexical_declaration, line 79)
- `#consider` (method_definition, line 86)
- `fn` (lexical_declaration, line 87)
- `found` (lexical_declaration, line 91)
- `#orchestrationOf` (method_definition, line 95)
- `seed` (lexical_declaration, line 96)
- `fold` (lexical_declaration, line 97)
- `#awaitTargets` (method_definition, line 103)
- `out` (lexical_declaration, line 104)
- `visit` (lexical_declaration, line 105)
- `target` (lexical_declaration, line 106)
- `#targetOf` (method_definition, line 116)
- `#awaitTarget` (method_definition, line 123)
- `symbol` (lexical_declaration, line 139)
- `decl` (lexical_declaration, line 140)
- `detector` (lexical_declaration, line 149, exported)

## Contained in

- [core/analyzers](https://banes-lab.com/anatomy/docs/folder-docs-core-analyzers.md)

## Uses

- [core/selectors/program.selector.ts](https://banes-lab.com/source/docs/core/selectors/program.selector.ts.md)

## Used by

- [core/plugins/code.typescript.plugin.ts](https://banes-lab.com/source/docs/core/plugins/code.typescript.plugin.ts.md)

## Linked from

- [core/analyzers](https://banes-lab.com/anatomy/docs/folder-docs-core-analyzers.md)
- [core/plugins](https://banes-lab.com/anatomy/docs/folder-docs-core-plugins.md)
- [core/selectors](https://banes-lab.com/anatomy/docs/folder-docs-core-selectors.md)

## Source

```typescript
import type { FunctionBody, OrchestrationFold, OrchestrationTarget, ProgramAnalysis } from "#types/code.types";
import type { DetectedProtocol } from "#types/graph.types";
import { firstDeclaration } from "#core/selectors/code.typescript.selector";
import { inPackageSources } from "#core/selectors/program.selector";
import ts from "typescript";

const MAX_MESSAGES = 50;
const MIN_PARTICIPANT_LEN = 3;
const MIN_ORCH_PARTICIPANTS = 2;
const MIN_ORCH_MESSAGES = 2;
const THIS = "this";

const foldTarget = function foldTarget(
    fold: OrchestrationFold,
    target: OrchestrationTarget,
    fnName: string,
): OrchestrationFold {
    const key = `${target.participant} ${target.method}`;
    const skip =
        target.participant === fnName ||
        target.participant.length < MIN_PARTICIPANT_LEN ||
        fold.msgKey.has(key) ||
        fold.messages.length >= MAX_MESSAGES;
    if (skip) {
        return fold;
    }
    return {
        messages: [...fold.messages, { async: true, text: target.method, to: target.participant }],
        msgKey: new Set([...fold.msgKey, key]),
        seen: new Set([...fold.seen, target.participant]),
    };
};

const isNamedBinding = function isNamedBinding(node: ts.Node): node is ts.PropertyAssignment | ts.VariableDeclaration {
    return (ts.isVariableDeclaration(node) || ts.isPropertyAssignment(node)) && ts.isIdentifier(node.name);
};

const boundFunction = function boundFunction(
    node: ts.PropertyAssignment | ts.VariableDeclaration,
): FunctionBody | null {
    const init = node.initializer;
    if (!init || !(ts.isArrowFunction(init) || ts.isFunctionExpression(init))) {
        return null;
    }
    return { body: init.body, name: node.name.getText() };
};

const functionBodyOf = function functionBodyOf(node: ts.Node): FunctionBody | null {
    if (ts.isFunctionDeclaration(node) && node.name) {
        return { body: node.body, name: node.name.text };
    }
    if (ts.isMethodDeclaration(node) && ts.isIdentifier(node.name)) {
        return { body: node.body, name: node.name.text };
    }
    return isNamedBinding(node) ? boundFunction(node) : null;
};

const isTopLevelVariable = function isTopLevelVariable(decl: ts.Declaration): boolean {
    return ts.isVariableDeclaration(decl) && ts.isSourceFile(decl.parent.parent.parent);
};

const isCallableDeclaration = function isCallableDeclaration(decl: ts.Declaration): boolean {
    return (
        ts.isFunctionDeclaration(decl) ||
        ts.isImportSpecifier(decl) ||
        ts.isImportClause(decl) ||
        ts.isImportEqualsDeclaration(decl)
    );
};

class ProtocolDetector {
    readonly #checker: ts.TypeChecker;

    public constructor(checker: ts.TypeChecker) {
        this.#checker = checker;
    }

    public bestIn(node: ts.Node, best: DetectedProtocol | null): DetectedProtocol | null {
        let current = this.#consider(node, best);
        ts.forEachChild(node, (child) => {
            current = this.bestIn(child, current);
        });
        return current;
    }

    #consider(node: ts.Node, best: DetectedProtocol | null): DetectedProtocol | null {
        const fn = functionBodyOf(node);
        if (!fn?.body || !ts.isBlock(fn.body)) {
            return best;
        }
        const found = this.#orchestrationOf(fn.name, fn.body);
        return found !== null && (best === null || found.messages.length > best.messages.length) ? found : best;
    }

    #orchestrationOf(fnName: string, body: ts.Block): DetectedProtocol | null {
        const seed: OrchestrationFold = { messages: [], msgKey: new Set<string>(), seen: new Set<string>() };
        const fold = this.#awaitTargets(body).reduce((acc, target) => foldTarget(acc, target, fnName), seed);
        return fold.seen.size >= MIN_ORCH_PARTICIPANTS && fold.messages.length >= MIN_ORCH_MESSAGES
            ? { messages: fold.messages, participants: [...fold.seen], self: fnName }
            : null;
    }

    #awaitTargets(body: ts.Block): OrchestrationTarget[] {
        const out: OrchestrationTarget[] = [];
        const visit = (child: ts.Node): void => {
            const target = this.#targetOf(child);
            if (target !== null) {
                out.push(target);
            }
            ts.forEachChild(child, visit);
        };
        ts.forEachChild(body, visit);
        return out;
    }

    #targetOf(child: ts.Node): OrchestrationTarget | null {
        if (!ts.isAwaitExpression(child) || !ts.isCallExpression(child.expression)) {
            return null;
        }
        return this.#awaitTarget(child.expression.expression);
    }

    #awaitTarget(callee: ts.Expression): OrchestrationTarget | null {
        if (ts.isIdentifier(callee)) {
            return this.#keepIdentifierCallee(callee) ? { method: callee.text, participant: callee.text } : null;
        }
        if (
            ts.isPropertyAccessExpression(callee) &&
            ts.isIdentifier(callee.expression) &&
            callee.expression.text !== THIS &&
            ts.isIdentifier(callee.name)
        ) {
            return { method: callee.name.text, participant: callee.expression.text };
        }
        return null;
    }

    #keepIdentifierCallee(idNode: ts.Node): boolean {
        const symbol = this.#checker.getSymbolAtLocation(idNode);
        const decl = symbol ? firstDeclaration(symbol, this.#checker) : null;
        if (decl === null || isCallableDeclaration(decl)) {
            return true;
        }
        return isTopLevelVariable(decl);
    }
}

export const detectProtocol = function detectProtocol(analysis: ProgramAnalysis): DetectedProtocol | null {
    const detector = new ProtocolDetector(analysis.checker);
    return inPackageSources(analysis.program, analysis.dirPosix).reduce<DetectedProtocol | null>(
        (best, file) => detector.bestIn(file, best),
        null,
    );
};
```
