# core/analyzers/dependency.analyzer.ts

> 77 lines of code and 16 definitions.

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

Listed in [core/analyzers](https://banes-lab.com/api/source/stats/core/analyzers.md), before [core/analyzers/rule.analyzer.ts](https://banes-lab.com/source/stats/core/analyzers/rule.analyzer.ts.md).

## Definitions

- `topRows` (lexical_declaration, line 50)
- `declaredDependencies` (lexical_declaration, line 11)
- `buildNodes` (lexical_declaration, line 20)
- `collectDependencyGraph` (lexical_declaration, line 70, exported)
- `block` (lexical_declaration, line 14)
- `pkg` (lexical_declaration, line 23)
- `name` (lexical_declaration, line 24)
- `computeFans` (lexical_declaration, line 32)
- `nodeList` (lexical_declaration, line 33)
- `internal` (lexical_declaration, line 34)
- `fanOut` (lexical_declaration, line 35)
- `fanIn` (lexical_declaration, line 36)
- `linkedRows` (lexical_declaration, line 58)
- `nodes` (lexical_declaration, line 71, exported)
- `fans` (lexical_declaration, line 72, exported)
- `links` (lexical_declaration, line 73, exported)

## Contained in

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

## Uses

- [core/predicates/record.predicate.ts](https://banes-lab.com/source/stats/core/predicates/record.predicate.ts.md)
- [core/selectors/source.selector.ts](https://banes-lab.com/source/stats/core/selectors/source.selector.ts.md)

## Linked from

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

## Source

```typescript
import { DEPENDENCY_FIELDS, PACKAGE_MANIFEST } from "#configuration/constants/package.constants";
import type { DependencyGraph, Fans, GraphLink, GraphNode, GraphRow } from "#types/dependency.types";
import { field, stringField } from "#core/selectors/field.selector";
import { TOP_PACKAGES } from "#configuration/constants/metric.constants";
import { isRecord } from "#core/predicates/record.predicate";
import path from "node:path";
import { posixOf } from "#core/selectors/source.selector";
import { readJson } from "#core/loaders/data.loader";
import { workspaceMembers } from "#core/resolvers/package.resolver";

const declaredDependencies = function declaredDependencies(pkg: unknown): Set<string> {
    return new Set(
        DEPENDENCY_FIELDS.flatMap((fieldName) => {
            const block = field(pkg, fieldName);
            return isRecord(block) ? Object.keys(block) : [];
        }),
    );
};

const buildNodes = function buildNodes(root: string, dirs: readonly string[]): Map<string, GraphNode> {
    const nodes = new Map<string, GraphNode>();
    for (const dir of dirs) {
        const pkg = readJson(path.join(dir, PACKAGE_MANIFEST));
        const name = stringField(pkg, "name");
        if (name.length > 0) {
            nodes.set(name, { deps: declaredDependencies(pkg), name, rel: posixOf(path.relative(root, dir)) });
        }
    }
    return nodes;
};

const computeFans = function computeFans(nodes: Map<string, GraphNode>): Fans {
    const nodeList = [...nodes.values()];
    const internal = new Map(nodeList.map((node) => [node.name, [...node.deps].filter((dep) => nodes.has(dep))]));
    const fanOut = new Map(nodeList.map((node) => [node.name, internal.get(node.name)?.length ?? 0]));
    const fanIn = new Map(
        nodeList.map((node) => [
            node.name,
            nodeList.filter((other) => (internal.get(other.name) ?? []).includes(node.name)).length,
        ]),
    );
    return {
        edges: [...internal.values()].reduce((sum, deps) => sum + deps.length, 0),
        fanIn,
        fanOut,
        leaves: nodeList.filter((node) => (internal.get(node.name) ?? []).length === 0).length,
    };
};

const topRows = function topRows(counts: Map<string, number>, nodes: Map<string, GraphNode>): GraphRow[] {
    return [...counts.entries()]
        .filter(([, count]) => count > 0)
        .toSorted((a, b) => b[1] - a[1])
        .slice(0, TOP_PACKAGES)
        .map(([name, count]) => ({ count, name, rel: nodes.get(name)?.rel ?? "" }));
};

const linkedRows = function linkedRows(fans: Fans, nodes: Map<string, GraphNode>): GraphLink[] {
    return [...nodes.values()]
        .map((node) => ({
            fanIn: fans.fanIn.get(node.name) ?? 0,
            fanOut: fans.fanOut.get(node.name) ?? 0,
            name: node.name,
            rel: node.rel,
        }))
        .filter((row) => row.fanIn > 0 || row.fanOut > 0)
        .toSorted((a, b) => b.fanIn - a.fanIn || b.fanOut - a.fanOut);
};

export const collectDependencyGraph = function collectDependencyGraph(root: string): DependencyGraph {
    const nodes = buildNodes(root, workspaceMembers(root));
    const fans = computeFans(nodes);
    const links = linkedRows(fans, nodes);
    return {
        edges: fans.edges,
        isolated: nodes.size - links.length,
        leaves: fans.leaves,
        links,
        nodeCount: nodes.size,
        topFanIn: topRows(fans.fanIn, nodes),
        topFanOut: topRows(fans.fanOut, nodes),
    };
};
```
