# core/analyzers/machine.analyzer.ts

> 77 lines of code and 18 definitions.

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

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

## Definitions

- `detectState` (lexical_declaration, line 71, exported)
- `initialState` (lexical_declaration, line 34)
- `transitionsIn` (lexical_declaration, line 51)
- `compareCandidates` (lexical_declaration, line 8)
- `keyTextOf` (lexical_declaration, line 15)
- `transitionOf` (lexical_declaration, line 19)
- `from` (lexical_declaration, line 26)
- `value` (lexical_declaration, line 27)
- `stateTypes` (lexical_declaration, line 38)
- `members` (lexical_declaration, line 40)
- `name` (lexical_declaration, line 42)
- `out` (lexical_declaration, line 56)
- `props` (lexical_declaration, line 57)
- `transition` (lexical_declaration, line 59)
- `sources` (lexical_declaration, line 72, exported)
- `[first]` (lexical_declaration, line 73, exported)
- `states` (lexical_declaration, line 77, exported)
- `seen` (lexical_declaration, line 78, exported)

## Contained in

- [core/analyzers](https://banes-lab.com/anatomy/docs/folder-docs-core-analyzers.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)

## Source

```typescript
import type { DetectedState, DetectedTransition } from "#types/graph.types";
import { INITIAL_HINTS, STATE_NAME_HINTS } from "#configuration/constants/code.typescript.constants";
import type { KeyedTransition, ProgramAnalysis, StateCandidate } from "#types/code.types";
import { inPackageSources } from "#core/selectors/program.selector";
import ts from "typescript";
import { unionStringLiterals } from "#core/selectors/code.typescript.selector";

const compareCandidates = function compareCandidates(left: StateCandidate, right: StateCandidate): number {
    if (left.hinted !== right.hinted) {
        return left.hinted ? -1 : 1;
    }
    return right.members.length - left.members.length;
};

const keyTextOf = function keyTextOf(name: ts.PropertyName): string | null {
    return ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : null;
};

const transitionOf = function transitionOf(
    prop: ts.ObjectLiteralElementLike,
    states: ReadonlySet<string>,
): KeyedTransition | null {
    if (!ts.isPropertyAssignment(prop)) {
        return null;
    }
    const from = keyTextOf(prop.name);
    const value = prop.initializer;
    if (from === null || !states.has(from) || !ts.isStringLiteral(value) || !states.has(value.text)) {
        return null;
    }
    return { from, key: `${from} ${value.text}`, to: value.text };
};

const initialState = function initialState(members: readonly string[]): string | null {
    return members.find((member) => INITIAL_HINTS.has(member.toLowerCase())) ?? null;
};

const stateTypes = function stateTypes(node: ts.Node): StateCandidate[] {
    const out: StateCandidate[] = [];
    const members = ts.isTypeAliasDeclaration(node) ? unionStringLiterals(node.type) : null;
    if (ts.isTypeAliasDeclaration(node) && members !== null) {
        const name = node.name.text.toLowerCase();
        out.push({ hinted: STATE_NAME_HINTS.some((hint) => name.includes(hint)), members });
    }
    ts.forEachChild(node, (child) => {
        out.push(...stateTypes(child));
    });
    return out;
};

const transitionsIn = function transitionsIn(
    node: ts.Node,
    states: ReadonlySet<string>,
    seen: Set<string>,
): DetectedTransition[] {
    const out: DetectedTransition[] = [];
    const props = ts.isObjectLiteralExpression(node) ? node.properties : [];
    for (const prop of props) {
        const transition = transitionOf(prop, states);
        if (transition !== null && !seen.has(transition.key)) {
            seen.add(transition.key);
            out.push({ from: transition.from, to: transition.to });
        }
    }
    ts.forEachChild(node, (child) => {
        out.push(...transitionsIn(child, states, seen));
    });
    return out;
};

export const detectState = function detectState(analysis: ProgramAnalysis): DetectedState | null {
    const sources = inPackageSources(analysis.program, analysis.dirPosix);
    const [first] = sources.flatMap(stateTypes).toSorted(compareCandidates);
    if (!first) {
        return null;
    }
    const states = new Set(first.members);
    const seen = new Set<string>();
    return {
        initial: initialState(first.members),
        states: first.members,
        transitions: sources.flatMap((file) => transitionsIn(file, states, seen)),
    };
};
```
