core/validators/filename.validator.ts

core/validators/filename.validator.ts is a file in GovLab Quality. 48 lines of code and 11 definitions.

import { NAMING_FIX, fileNameSpace, folderNotKebab } from "#configuration/strings/validation.strings";
import type { FileInput } from "#types/validation.types";
import type { Finding } from "#types/finding.types";
import { NAMING_EXTENSIONS } from "#configuration/constants/validation.constants";
import { defineValidator } from "#core/registries/validation.registry";
import { everyChar } from "@govlab/constants";
import { isAsciiUpper } from "#core/predicates/code-point.predicate";
import { projectFinding } from "#core/factories/finding.factory";

const RULE_ID = "naming";

const hasUpper = function hasUpper(text: string): boolean {
    return !everyChar(text, (ch) => !isAsciiUpper(ch.codePointAt(0)));
};

const segmentsOf = function segmentsOf(path: string): string[] {
    return path
        .split("\\")
        .join("/")
        .split("/")
        .filter((segment) => segment.length > 0);
};

const namingFinding = function namingFinding(file: string, message: string): Finding {
    return projectFinding({ file, message, ruleId: RULE_ID, suggestion: NAMING_FIX });
};

const fileFindings = function fileFindings(file: FileInput, flaggedFolders: Set<string>): Finding[] {
    const segments = segmentsOf(file.path);
    const fileName = segments.at(-1) ?? "";
    const out: Finding[] = fileName.includes(" ") ? [namingFinding(file.path, fileNameSpace(fileName))] : [];
    let accumulated = "";
    for (const folder of segments.slice(0, -1)) {
        accumulated = accumulated === "" ? folder : `${accumulated}/${folder}`;
        if ((folder.includes(" ") || hasUpper(folder)) && !flaggedFolders.has(accumulated)) {
            flaggedFolders.add(accumulated);
            out.push(namingFinding(file.path, folderNotKebab(folder)));
        }
    }
    return out;
};

defineValidator({
    appliesTo: NAMING_EXTENSIONS,
    id: RULE_ID,
    meta: {
        canonical: ["naming-convention"],
        description: "Require lowercase-kebab-case file and folder names (no spaces, no uppercase folders)",
    },
    validate(files): Finding[] {
        const flaggedFolders = new Set<string>();
        return files.flatMap((file) => fileFindings(file, flaggedFolders));
    },
});