shared/matchers/folder.matcher.ts

shared/matchers/folder.matcher.ts is a file in GovLab Extension Host. 99 lines of code and 23 definitions.

import {
    isConcernFolder,
    isDeclaredContainer,
    isLegalSubject,
    isMarkerFolder,
    vocabularyFor,
} from "../manifests/taxonomy.manifest.ts";
import { isKebab } from "./filename.matcher.ts";

const holdsRole = function holdsRole(root: string, name: string, role: string): boolean {
    if (role === "container") {
        return isDeclaredContainer(root, name);
    }
    if (role === "subject") {
        return isLegalSubject(name, root);
    }
    if (role === "concern") {
        return isConcernFolder(name, root) || isMarkerFolder(name, root);
    }
    return false;
};

const ranksAtDepth = function ranksAtDepth(root: string, name: string, depth: number, after: number): number[] {
    const { roleOrder, rolesAtDepth } = vocabularyFor(root).roles;
    const ranks: number[] = [];
    for (const role of rolesAtDepth[depth - 1] ?? []) {
        const rank = roleOrder.indexOf(role);
        if (rank > after && holdsRole(root, name, role)) {
            ranks.push(rank);
        }
    }
    return ranks;
};

export const roleAtDepth = function roleAtDepth(root: string, name: string, depth: number, after: number): number {
    return ranksAtDepth(root, name, depth, after)[0] ?? -1;
};

const terminalRankOf = function terminalRankOf(root: string): number {
    const { roleOrder, terminalRole } = vocabularyFor(root).roles;
    return roleOrder.indexOf(terminalRole);
};

const assignRoles = function assignRoles(
    root: string,
    segments: readonly string[],
    index: number,
    after: number,
): number[] | undefined {
    const name = segments[index];
    if (name === undefined) {
        return after === terminalRankOf(root) ? [] : undefined;
    }
    for (const rank of ranksAtDepth(root, name, index + 1, after)) {
        const rest = assignRoles(root, segments, index + 1, rank);
        if (rest !== undefined) {
            return [rank, ...rest];
        }
    }
    return undefined;
};

const deepestRank = function deepestRank(root: string, segments: readonly string[]): { at: number; after: number } {
    let after = -1;
    for (const [index, name] of segments.entries()) {
        const rank = roleAtDepth(root, name, index + 1, after);
        if (rank === -1) {
            return { after, at: index };
        }
        after = rank;
    }
    return { after, at: segments.length };
};

const labelError = function labelError(root: string, segments: readonly string[]): string | undefined {
    const unkebab = segments.find((name) => !isKebab(name, root));
    if (unkebab !== undefined) {
        return `folder '${unkebab}' is not ${vocabularyFor(root).case}-case`;
    }
    const repeated = segments.find((name, index) => segments.indexOf(name) !== index);
    if (repeated !== undefined) {
        return `folder '${repeated}' appears twice on one path. a label names one role once, so a word that fills two depths states the same axis twice; rename the outer folder or drop the inner one`;
    }
    return undefined;
};

export const folderPathError = function folderPathError(root: string, segments: readonly string[]): string | undefined {
    const vocabulary = vocabularyFor(root);
    const { roleOrder, rolesAtDepth, terminalRole } = vocabulary.roles;
    if (segments.length > vocabulary.maxDepth) {
        return `nests ${segments.length} folders below the governed root; the cap is ${vocabulary.maxDepth}. overflow relieves sideways — the filename variant slot for a collision, a sibling folder for breadth — never downward`;
    }
    const label = labelError(root, segments);
    if (label !== undefined) {
        return label;
    }
    if (assignRoles(root, segments, 0, -1) !== undefined) {
        return undefined;
    }
    const { at, after } = deepestRank(root, segments);
    if (at < segments.length) {
        const allowed = (rolesAtDepth[at] ?? []).join(" | ");
        const later = roleOrder.slice(after + 1).join(" | ");
        return `folder '${segments[at] ?? ""}' resolves to no role at depth ${at + 1}. that depth accepts ${allowed}, and the path has already consumed up to '${roleOrder[after] ?? "(root)"}', so only ${later} may follow`;
    }
    return `the file's parent folder resolves to '${roleOrder[after] ?? "(root)"}', not '${terminalRole}'. every governed file sits directly inside its concern folder`;
};