core/plugins/diagram.dependency.plugin.ts

core/plugins/diagram.dependency.plugin.ts is a file in GovLab Docs. 98 lines of code and 22 definitions.

import { CLASS_DEFS, EDGE_BUDGET } from "#configuration/constants/graph.constants";
import type { CappedEdges, CappedGraph, CodeEdge, CodeGraph, CodeNode } from "#types/graph.types";
import {
    DIAGRAM_DESCRIPTIONS,
    DIAGRAM_LEGENDS,
    DIAGRAM_TITLES,
    KIND_LEGEND,
    dataFlowTitle,
    truncationNote,
} from "#configuration/strings/figure.strings";
import type { DiagramContext, DiagramKind, DiagramSource } from "#types/figure.types";
import type { GraphEdge, GraphModel, GraphNode } from "#types/diagram.types";
import { classForKind, dedupEdges, shapeForKind, weightForKind } from "#core/converters/diagram.converter";
import { collapseByLabel } from "#core/converters/graph.converter";
import { emitGraph } from "#core/formatters/diagram.formatter";

const MIN_DATA_EDGES = 2;
const DATA_KINDS: ReadonlySet<CodeEdge["kind"]> = new Set<CodeEdge["kind"]>(["data-flow", "dependency"]);

const edgeKey = function edgeKey(edge: CodeEdge): string {
    return `${edge.from} ${edge.to} ${edge.label ?? ""}`;
};

const dataEdges = function dataEdges(graph: CodeGraph): CodeEdge[] {
    return graph.edges.filter((edge) => DATA_KINDS.has(edge.kind));
};

const capEdges = function capEdges(edges: readonly CodeEdge[]): CappedEdges {
    if (edges.length <= EDGE_BUDGET) {
        return { edges: [...edges], truncated: false };
    }
    const sorted = edges.toSorted((left, right) => edgeKey(left).localeCompare(edgeKey(right)));
    return { edges: sorted.slice(0, EDGE_BUDGET), truncated: true };
};

const involvedNodes = function involvedNodes(
    edges: readonly CodeEdge[],
    nodeById: ReadonlyMap<string, CodeNode>,
): CodeNode[] {
    const ids = new Set(edges.flatMap((edge) => [edge.from, edge.to]));
    return [...ids]
        .flatMap((id) => {
            const node = nodeById.get(id);
            return node ? [node] : [];
        })
        .toSorted((left, right) => left.id.localeCompare(right.id));
};

const modelNodes = function modelNodes(nodes: readonly CodeNode[]): GraphNode[] {
    return nodes.map((node) => ({
        class: classForKind(node.kind),
        id: node.id,
        label: node.label,
        shape: shapeForKind(node.kind),
    }));
};

const modelEdges = function modelEdges(edges: readonly CodeEdge[]): GraphEdge[] {
    return dedupEdges(
        edges.map((edge) => ({ from: edge.from, label: edge.label, to: edge.to, weight: weightForKind(edge.kind) })),
    );
};

const sourcesOf = function sourcesOf(nodes: readonly CodeNode[]): DiagramSource[] {
    return nodes.map((node) => ({ file: node.source.file, label: node.label, line: node.source.line }));
};

const cappedDataNodes = function cappedDataNodes(graph: CodeGraph): CappedGraph {
    const allEdges = dataEdges(graph);
    const { edges, truncated } = capEdges(allEdges);
    const nodes = involvedNodes(edges, new Map(graph.nodes.map((node) => [node.id, node])));
    return { allCount: allEdges.length, edges, nodes, truncated };
};

const buildModel = function buildModel(context: DiagramContext, capped: CappedGraph): GraphModel {
    return {
        accDescr: DIAGRAM_DESCRIPTIONS.dataFlow,
        accTitle: dataFlowTitle(context.moduleName),
        classDefs: [...CLASS_DEFS],
        direction: context.layout.direction,
        edges: modelEdges(capped.edges),
        kind: "flowchart",
        nodes: modelNodes(capped.nodes),
    };
};

export const diagram: DiagramKind = {
    appliesTo(context) {
        return context.codeGraph !== null && dataEdges(context.codeGraph).length >= MIN_DATA_EDGES;
    },
    id: "data-flow",
    order: 4,
    render(context) {
        if (context.codeGraph === null) {
            return null;
        }
        const capped = cappedDataNodes(collapseByLabel(context.codeGraph));
        if (capped.allCount < MIN_DATA_EDGES || capped.nodes.length === 0) {
            return null;
        }
        const note = capped.truncated ? [truncationNote(EDGE_BUDGET, capped.allCount)] : [];
        return {
            legend: [DIAGRAM_LEGENDS.dataFlow, ...KIND_LEGEND, ...note],
            mermaid: emitGraph(buildModel(context, capped)),
            sources: sourcesOf(capped.nodes),
        };
    },
    title: DIAGRAM_TITLES.dataFlow,
};