core/formatters/report.formatter.ts

core/formatters/report.formatter.ts is a file in GovLab Patterns. 52 lines of code and 14 definitions.

import type { AnalysisModel, ModuleFindings } from "#types/report.types";
import { sortedEdges, unresolvedNames } from "#core/selectors/code.selector";
import type { CodeFinding } from "#types/code.types";
import { definitionRecords } from "#core/converters/definition.converter";
import { resolutionRate } from "#core/counters/report.counter";
import { tally } from "#core/counters/base.counter";

const ROW_JOIN = ",\n";

const section = function section(key: string, items: readonly unknown[]): string {
    const rows = items.map((item) => JSON.stringify(item)).join(ROW_JOIN);
    return items.length === 0 ? `"${key}": []` : `"${key}": [\n${rows}\n]`;
};

const byKind = function byKind(findings: readonly CodeFinding[]): Record<string, number> {
    return tally(findings, (finding) => finding.kind);
};

const byModuleName = function byModuleName(left: { module: string }, right: { module: string }): number {
    return left.module.localeCompare(right.module);
};

export const buildFindings = function buildFindings(title: string, findings: readonly CodeFinding[]): string {
    const summary = { byKind: byKind(findings), total: findings.length };
    return `{\n"module": ${JSON.stringify(title)},\n"summary": ${JSON.stringify(summary)},\n${section("findings", findings)}\n}\n`;
};

export const buildMasterFindings = function buildMasterFindings(modules: readonly ModuleFindings[]): string {
    const sorted = modules.toSorted(byModuleName);
    const all = sorted.flatMap((entry) => entry.findings.map((finding) => ({ module: entry.module, ...finding })));
    const perModule = sorted
        .map((entry) => ({ byKind: byKind(entry.findings), module: entry.module, total: entry.findings.length }))
        .filter((entry) => entry.total > 0)
        .sort((left, right) => right.total - left.total || byModuleName(left, right));
    const summary = {
        byKind: tally(all, (finding) => finding.kind),
        bySeverity: tally(all, (finding) => finding.severity),
        findings: all.length,
        modules: modules.length,
    };
    return `{\n"summary": ${JSON.stringify(summary)},\n${section("modules", perModule)},\n${section("findings", all)}\n}\n`;
};

export const buildAnalysis = function buildAnalysis(model: AnalysisModel): string {
    const { insight, graph, findingsCount, title } = model;
    const summary = {
        definitions: insight.definitions,
        edges: graph.edges.length,
        findings: findingsCount,
        resolutionRate: resolutionRate(graph.edges.length, graph.external.length),
        unresolvedCalls: graph.external.length,
    };
    const body = [
        section("unresolvedCalls", unresolvedNames(graph)),
        section("definitions", definitionRecords(insight, graph)),
        section("edges", sortedEdges(graph)),
    ].join(",\n");
    return `{\n"module": ${JSON.stringify(title)},\n"summary": ${JSON.stringify(summary)},\n${body}\n}\n`;
};