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;