core/quality/eslint/require-canonical-concept.eslint.rule.ts

core/quality/eslint/require-canonical-concept.eslint.rule.ts is a file in GovLab Quality. 72 lines of code and 14 definitions.

import type { Rule } from "eslint";

const MESSAGE =
    "govlabMeta() is missing '{{field}}'. Every @govlab/* rule must declare meta.docs.description and a non-empty " +
    "meta.docs.canonical (its shared quality concept) so the quality-relations ingest registers it with a concept — " +
    "a concept-less rule breaks the SSOT completeness gate. [require_canonical_concept]";

const SELECTORS = ["CallExpression"];

interface PropValue {
    type?: string;
    elements?: unknown[];
}

interface PropNode {
    type: string;
    key?: { type?: string; name?: string };
    value?: PropValue;
}

interface CallNode {
    type: string;
    callee?: { type?: string; name?: string };
    arguments?: { type?: string; properties?: PropNode[] }[];
}

const isCallNode = function isCallNode(node: unknown): node is CallNode {
    return node !== null && typeof node === "object" && "type" in node;
};

const propNamed = function propNamed(properties: PropNode[], name: string): PropNode | null {
    for (const prop of properties) {
        if (prop.type === "Property" && prop.key?.type === "Identifier" && prop.key.name === name) {
            return prop;
        }
    }
    return null;
};

const hasNonEmptyArray = function hasNonEmptyArray(prop: PropNode | null): boolean {
    return prop?.value?.type === "ArrayExpression" && (prop.value.elements?.length ?? 0) > 0;
};

const reportMissing = function reportMissing(context: Rule.RuleContext, node: Rule.Node, properties: PropNode[]): void {
    if (propNamed(properties, "description") === null) {
        context.report({ data: { field: "description" }, messageId: "missing", node });
    }
    if (!hasNonEmptyArray(propNamed(properties, "canonical"))) {
        context.report({ data: { field: "canonical" }, messageId: "missing", node });
    }
};

const onCall = function onCall(context: Rule.RuleContext, node: Rule.Node): void {
    const call: CallNode | null = isCallNode(node) ? node : null;
    if (call?.callee?.type !== "Identifier" || call.callee.name !== "govlabMeta") {
        return;
    }
    const arg = call.arguments?.[0];
    if (arg?.type !== "ObjectExpression") {
        return;
    }
    reportMissing(context, node, arg.properties ?? []);
};

export default {
    create(context): Rule.RuleListener {
        const listeners: Rule.RuleListener = {};
        for (const selector of SELECTORS) {
            listeners[selector] = (node: Rule.Node): void => {
                onCall(context, node);
            };
        }
        return listeners;
    },
    meta: {
        docs: {
            description: "Require meta.docs.description and a non-empty meta.docs.canonical on every govlabMeta rule",
        },
        messages: { missing: MESSAGE },
        schema: [],
        type: "problem",
    },
} satisfies Rule.RuleModule;