core/validators/constants.validator.ts

core/validators/constants.validator.ts is a file in GovLab Quality. 162 lines of code and 34 definitions.

import { CONSTANT_EXTENSIONS, RUNTIME_RELATIVE_MARKERS } from "#configuration/constants/validation.constants";
import {
    constantAlias,
    constantAliasFix,
    constantDuplicate,
    constantDuplicateFix,
} from "#configuration/strings/validation.strings";
import type { FileInput } from "#types/validation.types";
import type { Finding } from "#types/finding.types";
import { captureValue } from "#core/parsers/constants.parser";
import { defineValidator } from "#core/registries/validation.registry";
import { everyChar } from "@govlab/constants";
import { isIdentChar } from "#core/predicates/code-point.predicate";
import { isUpperSnake } from "#core/predicates/constants.predicate";
import { projectFinding } from "#core/factories/finding.factory";

const EXPORT_PREFIX = "export ";
const CONST_PREFIX = "const ";

const skipSpaces = function skipSpaces(text: string, from: number): number {
    let i = from;
    while (i < text.length && text[i] === " ") {
        i += 1;
    }
    return i;
};

const identEnd = function identEnd(text: string, from: number): number {
    let i = from;
    while (i < text.length && isIdentChar(text.codePointAt(i))) {
        i += 1;
    }
    return i;
};

const constantName = function constantName(line: string): string | null {
    const trimmed = line.trim();
    const body = trimmed.startsWith(EXPORT_PREFIX) ? trimmed.slice(EXPORT_PREFIX.length).trimStart() : trimmed;
    if (!body.startsWith(CONST_PREFIX)) {
        return null;
    }
    const start = skipSpaces(body, CONST_PREFIX.length);
    const end = identEnd(body, start);
    const name = body.slice(start, end);
    return name.length > 0 && body[skipSpaces(body, end)] === "=" ? name : null;
};

const upperConstantName = function upperConstantName(line: string): string | null {
    const name = constantName(line);
    return name !== null && isUpperSnake(name) ? name : null;
};

const assignedValue = function assignedValue(line: string): string | null {
    const eq = line.indexOf("=");
    if (eq === -1) {
        return null;
    }
    const value = line.slice(eq + 1).trim();
    const semi = value.indexOf(";");
    return semi === -1 ? value : value.slice(0, semi).trim();
};

interface ConstantSite {
    name: string;
    path: string;
    value: string;
}

const fileSites = function fileSites(file: FileInput): ConstantSite[] {
    const seen = new Set<string>();
    const sites: ConstantSite[] = [];
    const lines = file.content.split("\n");
    let index = 0;
    while (index < lines.length) {
        const name = upperConstantName(lines[index] ?? "");
        if (name === null || seen.has(name)) {
            index += 1;
        } else {
            seen.add(name);
            const captured = captureValue(lines, index);
            if (!RUNTIME_RELATIVE_MARKERS.some((marker) => captured.value.includes(marker))) {
                sites.push({ name, path: file.path, value: captured.value });
            }
            index = captured.endIndex + 1;
        }
    }
    return sites;
};

const duplicateGroups = function duplicateGroups(files: readonly FileInput[]): { name: string; paths: string[] }[] {
    const sites = files.flatMap(fileSites);
    const valuesByName = new Map<string, Set<string>>();
    const groups = new Map<string, { name: string; paths: string[] }>();
    for (const site of sites) {
        valuesByName.set(site.name, (valuesByName.get(site.name) ?? new Set()).add(site.value));
        const key = `${site.name}\n${site.value}`;
        const group = groups.get(key) ?? { name: site.name, paths: [] };
        group.paths.push(site.path);
        groups.set(key, group);
    }
    return [...groups.values()].filter((group) => group.paths.length > 1 && valuesByName.get(group.name)?.size === 1);
};

const soleIdentifier = function soleIdentifier(value: string): string | null {
    return value.length > 0 && everyChar(value, (ch) => isIdentChar(ch.codePointAt(0))) ? value : null;
};

const aliasTarget = function aliasTarget(
    name: string,
    value: string | null,
    declared: ReadonlySet<string>,
): string | null {
    const target = value === null ? null : soleIdentifier(value);
    if (target === null || target === name) {
        return null;
    }
    return isUpperSnake(target) && declared.has(target) ? target : null;
};

const aliasFinding = function aliasFinding(file: FileInput, line: string, declared: ReadonlySet<string>): Finding[] {
    const trimmed = line.trim();
    const name = upperConstantName(trimmed);
    const target = name === null ? null : aliasTarget(name, assignedValue(trimmed), declared);
    if (name === null || target === null) {
        return [];
    }
    return [
        projectFinding({
            file: file.path,
            message: constantAlias(name, target),
            ruleId: "constant-aliases",
            suggestion: constantAliasFix(name, target),
        }),
    ];
};

defineValidator({
    appliesTo: CONSTANT_EXTENSIONS,
    id: "constant-aliases",
    meta: {
        canonical: ["duplicate-code", "central-config"],
        description: "Flag a constant that is a bare alias of another constant; reference it directly",
    },
    validate(files) {
        const declared = new Set(
            files.flatMap((file) =>
                file.content.split("\n").flatMap((line) => {
                    const name = upperConstantName(line);
                    return name === null ? [] : [name];
                }),
            ),
        );
        return files.flatMap((file) => file.content.split("\n").flatMap((line) => aliasFinding(file, line, declared)));
    },
});

defineValidator({
    appliesTo: CONSTANT_EXTENSIONS,
    id: "constant-duplicates",
    meta: {
        canonical: ["duplicate-code", "central-config"],
        description: "Flag a constant name defined in multiple files; centralize it",
    },
    validate(files) {
        return duplicateGroups(files).flatMap(({ name, paths }) =>
            paths.map((path) =>
                projectFinding({
                    file: path,
                    message: constantDuplicate(name, paths.length),
                    ruleId: "constant-duplicates",
                    suggestion: constantDuplicateFix(name, paths.length),
                }),
            ),
        );
    },
});