# core/analyzers/definition.analyzer.ts

> 146 lines of code and 35 definitions.

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

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

## Definitions

- `declName` (lexical_declaration, line 36)
- `declId` (lexical_declaration, line 40)
- `exportedTypeDecls` (lexical_declaration, line 117)
- `kindOfDecl` (lexical_declaration, line 12)
- `memberNames` (lexical_declaration, line 44)
- `clauseEdges` (lexical_declaration, line 66)
- `nodeForDecl` (lexical_declaration, line 105)
- `collectTypeDecls` (lexical_declaration, line 133)
- `heritageEdges` (lexical_declaration, line 74)
- `EMPTY` (lexical_declaration, line 10)
- `isTypeDecl` (lexical_declaration, line 22)
- `typeDeclOf` (lexical_declaration, line 31)
- `target` (lexical_declaration, line 32)
- `collectRefs` (lexical_declaration, line 52)
- `out` (lexical_declaration, line 53)
- `targetIdFor` (lexical_declaration, line 60)
- `symbol` (lexical_declaration, line 61)
- `to` (lexical_declaration, line 69)
- `referenceRoots` (lexical_declaration, line 81)
- `members` (lexical_declaration, line 88)
- `referenceEdges` (lexical_declaration, line 94)
- `kind` (lexical_declaration, line 95)
- `targets` (lexical_declaration, line 96)
- `position` (lexical_declaration, line 107)
- `file` (lexical_declaration, line 122)
- `moduleSymbol` (lexical_declaration, line 123)
- `decl` (lexical_declaration, line 128)
- `deriveTypeGraph` (lexical_declaration, line 149, exported)
- `barrels` (lexical_declaration, line 150, exported)
- `program` (lexical_declaration, line 154, exported)
- `checker` (lexical_declaration, line 155, exported)
- `decls` (lexical_declaration, line 156, exported)
- `nodes` (lexical_declaration, line 157, exported)
- `edges` (lexical_declaration, line 158, exported)
- `context` (lexical_declaration, line 159, exported)

## Contained in

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

## Uses

- [core/normalizers/diagram.normalizer.ts](https://banes-lab.com/source/docs/core/normalizers/diagram.normalizer.ts.md)
- [core/resolvers/source.resolver.ts](https://banes-lab.com/source/docs/core/resolvers/source.resolver.ts.md)

## Linked from

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

## Source

```typescript
import type { AxisBarrel, PackageJsonLike, TypeDecl, TypeEdgeContext } from "#types/code.types";
import type { TypeEdge, TypeEdgeKind, TypeGraph, TypeNode, TypeNodeKind } from "#types/graph.types";
import { PROGRAM_OPTIONS } from "#configuration/constants/program.constants";
import { hasSymbolFlag } from "#core/predicates/program.predicate";
import { nodeId } from "#core/normalizers/diagram.normalizer";
import { relFromModules } from "#core/resolvers/source.resolver";
import { resolveSourceBarrels } from "#core/resolvers/barrel.resolver";
import ts from "typescript";

const EMPTY: TypeGraph = { edges: [], nodes: [] };

const kindOfDecl = function kindOfDecl(decl: TypeDecl): TypeNodeKind {
    if (ts.isClassDeclaration(decl)) {
        return "class";
    }
    if (ts.isEnumDeclaration(decl)) {
        return "enum";
    }
    return ts.isInterfaceDeclaration(decl) ? "interface" : "type-alias";
};

const isTypeDecl = function isTypeDecl(decl: ts.Declaration): decl is TypeDecl {
    return (
        ts.isInterfaceDeclaration(decl) ||
        ts.isClassDeclaration(decl) ||
        ts.isTypeAliasDeclaration(decl) ||
        ts.isEnumDeclaration(decl)
    );
};

const typeDeclOf = function typeDeclOf(symbol: ts.Symbol, checker: ts.TypeChecker): TypeDecl | null {
    const target = hasSymbolFlag(symbol.getFlags(), ts.SymbolFlags.Alias) ? checker.getAliasedSymbol(symbol) : symbol;
    return (target.getDeclarations() ?? []).find(isTypeDecl) ?? null;
};

const declName = function declName(decl: TypeDecl): string {
    return decl.name ? decl.name.text : "";
};

const declId = function declId(decl: TypeDecl): string {
    return nodeId(`type:${decl.getSourceFile().fileName}:${declName(decl)}`);
};

const memberNames = function memberNames(decl: TypeDecl): string[] {
    if (ts.isTypeAliasDeclaration(decl)) {
        return [];
    }
    const members: readonly ts.NamedDeclaration[] = decl.members;
    return members.flatMap((member) => (member.name && ts.isIdentifier(member.name) ? [member.name.text] : []));
};

const collectRefs = function collectRefs(node: ts.Node): ts.Identifier[] {
    const out: ts.Identifier[] = ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) ? [node.typeName] : [];
    ts.forEachChild(node, (child) => {
        out.push(...collectRefs(child));
    });
    return out;
};

const targetIdFor = function targetIdFor(ref: ts.Identifier, context: TypeEdgeContext): string | null {
    const symbol = context.checker.getSymbolAtLocation(ref);
    const decl = symbol ? typeDeclOf(symbol, context.checker) : null;
    return decl === null ? null : (context.ids.get(decl) ?? null);
};

const clauseEdges = function clauseEdges(clause: ts.HeritageClause, context: TypeEdgeContext): TypeEdge[] {
    const kind: TypeEdgeKind = clause.token === ts.SyntaxKind.ImplementsKeyword ? "implements" : "extends";
    return clause.types.flatMap((type) => {
        const to = ts.isIdentifier(type.expression) ? targetIdFor(type.expression, context) : null;
        return to !== null && to !== context.fromId ? [{ from: context.fromId, kind, to }] : [];
    });
};

const heritageEdges = function heritageEdges(decl: TypeDecl, context: TypeEdgeContext): TypeEdge[] {
    if (ts.isTypeAliasDeclaration(decl) || ts.isEnumDeclaration(decl)) {
        return [];
    }
    return (decl.heritageClauses ?? []).flatMap((clause) => clauseEdges(clause, context));
};

const referenceRoots = function referenceRoots(decl: TypeDecl): ts.Node[] {
    if (ts.isTypeAliasDeclaration(decl)) {
        return [decl.type];
    }
    if (ts.isEnumDeclaration(decl)) {
        return [];
    }
    const members: readonly ts.Node[] = decl.members;
    return members.filter(
        (member) => (ts.isPropertySignature(member) || ts.isPropertyDeclaration(member)) && Boolean(member.type),
    );
};

const referenceEdges = function referenceEdges(decl: TypeDecl, context: TypeEdgeContext): TypeEdge[] {
    const kind: TypeEdgeKind = ts.isTypeAliasDeclaration(decl) ? "uses" : "has";
    const targets = new Set(
        referenceRoots(decl)
            .flatMap(collectRefs)
            .map((ref) => targetIdFor(ref, context))
            .filter((to): to is string => to !== null && to !== context.fromId),
    );
    return [...targets].map((to) => ({ from: context.fromId, kind, to }));
};

const nodeForDecl = function nodeForDecl(decl: TypeDecl, id: string): TypeNode {
    const file = decl.getSourceFile();
    const position = file.getLineAndCharacterOfPosition(decl.getStart());
    return {
        id,
        kind: kindOfDecl(decl),
        label: declName(decl),
        members: memberNames(decl),
        source: { file: relFromModules(file.fileName), line: position.line + 1 },
    };
};

const exportedTypeDecls = function exportedTypeDecls(
    program: ts.Program,
    checker: ts.TypeChecker,
    barrel: string,
): TypeDecl[] {
    const file = program.getSourceFile(barrel);
    const moduleSymbol = file ? checker.getSymbolAtLocation(file) : undefined;
    if (!moduleSymbol) {
        return [];
    }
    return checker.getExportsOfModule(moduleSymbol).flatMap((symbol) => {
        const decl = typeDeclOf(symbol, checker);
        return decl !== null && declName(decl) !== "" ? [decl] : [];
    });
};

const collectTypeDecls = function collectTypeDecls(
    program: ts.Program,
    barrels: readonly AxisBarrel[],
): Map<TypeDecl, string> {
    const checker = program.getTypeChecker();
    const decls = new Map<TypeDecl, string>();
    for (const { barrel } of barrels) {
        for (const decl of exportedTypeDecls(program, checker, barrel)) {
            if (!decls.has(decl)) {
                decls.set(decl, declId(decl));
            }
        }
    }
    return decls;
};

export const deriveTypeGraph = function deriveTypeGraph(moduleDir: string, pkg: PackageJsonLike): TypeGraph {
    const barrels = resolveSourceBarrels(moduleDir, pkg);
    if (barrels.length === 0) {
        return EMPTY;
    }
    const program = ts.createProgram({ options: PROGRAM_OPTIONS, rootNames: barrels.map((barrel) => barrel.barrel) });
    const checker = program.getTypeChecker();
    const decls = collectTypeDecls(program, barrels);
    const nodes = [...decls].map(([decl, id]) => nodeForDecl(decl, id));
    const edges = [...decls].flatMap(([decl, id]) => {
        const context: TypeEdgeContext = { checker, fromId: id, ids: decls };
        return [...heritageEdges(decl, context), ...referenceEdges(decl, context)];
    });
    return { edges, nodes };
};
```
