core/quality/eslint/no-underscore-dead-code.eslint.rule.ts

core/quality/eslint/no-underscore-dead-code.eslint.rule.ts is a file in GovLab Quality. 78 lines of code and 18 definitions.

import type { Rule } from "eslint";
import { govlabMeta } from "#core/factories/eslint.factory";

interface AstNode {
    type: string;
    name?: string;
    id?: AstNode;
    parent?: AstNode;
    params?: AstNode[];
}

const NODE_BUILTINS = new Set(["__filename", "__dirname"]);
const FN_TYPES = new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]);
const PATTERN_TYPES = new Set(["ArrayPattern", "ObjectPattern"]);

const isAstNode = function isAstNode(value: unknown): value is AstNode {
    return value !== null && typeof value === "object" && "type" in value;
};

const asNode = function asNode(value: unknown): AstNode | null {
    return isAstNode(value) ? value : null;
};

const isRuleNode = function isRuleNode(value: unknown): value is Rule.Node {
    return value !== null && typeof value === "object" && "type" in value;
};

const isUnderscorePrefixed = function isUnderscorePrefixed(name: string | undefined): name is string {
    return typeof name === "string" && name.startsWith("_") && name !== "_" && !NODE_BUILTINS.has(name);
};

const isFunctionParam = function isFunctionParam(node: AstNode): boolean {
    const { parent } = node;
    if (!parent) {
        return false;
    }
    return FN_TYPES.has(parent.type) && (parent.params?.includes(node) ?? false);
};

const isDestructuringParam = function isDestructuringParam(node: AstNode): boolean {
    if (!node.parent || !PATTERN_TYPES.has(node.parent.type)) {
        return false;
    }
    let current: AstNode | undefined = node.parent;
    while (current) {
        if (FN_TYPES.has(current.type)) {
            return true;
        }
        current = current.parent;
    }
    return false;
};

const isExemptParam = function isExemptParam(node: AstNode): boolean {
    return isFunctionParam(node) || node.parent?.type === "CatchClause" || isDestructuringParam(node);
};

export default {
    create(context: Rule.RuleContext): Rule.RuleListener {
        const check = function check(id: AstNode | undefined): void {
            if (id?.type !== "Identifier" || !isUnderscorePrefixed(id.name) || isExemptParam(id)) {
                return;
            }
            const { name } = id;
            if (isRuleNode(id)) {
                context.report({ data: { name }, messageId: "noUnderscoreDeadCode", node: id });
            }
        };
        const onDecl = (node: Rule.Node): void => {
            check(asNode(node)?.id);
        };
        const handlers: [string, (node: Rule.Node) => void][] = [
            ["ClassDeclaration", onDecl],
            ["FunctionDeclaration", onDecl],
            ["VariableDeclarator", onDecl],
        ];
        return Object.fromEntries(handlers);
    },
    meta: govlabMeta({
        canonical: ["no-unused"],
        description: "Disallow underscore-prefixed declarations (dead code markers)",
        messages: {
            noUnderscoreDeadCode:
                "Underscore-prefixed '{{name}}' is dead code. Either implement it (remove the _) or delete it entirely — no 'keep for later'.",
        },
        ruleId: "underscore_prefix_decision_tree",
    }),
} satisfies Rule.RuleModule;