core/quality/eslint/require-central-config.eslint.rule.ts
core/quality/eslint/require-central-config.eslint.rule.ts is a file in GovLab Quality. 90 lines of code and 17 definitions.
import { isDigit, isUpperAlpha } from "@govlab/constants";
import type { Rule } from "eslint";
import { packageRelOf } from "#core/resolvers/package.resolver";
const CONFIG_INIT_TYPES = new Set(["Literal"]);
const SELECTORS = ["ExportNamedDeclaration"];
const MESSAGE =
"Scalar config constant '{{name}}' is declared inline in a concern module, not in a constants module. " +
"Move exported UPPER_SNAKE scalar knobs (number/string literals) into a co-located `constants/` folder (or src/constants.ts) so a package's tunable surface lives in dedicated constants modules. [central_config]";
interface VarDeclarator {
id: { type: string; name?: string | undefined };
init?: { type: string } | null | undefined;
}
const isWorkspaceSrcFile = function isWorkspaceSrcFile(filename: string): boolean {
const normalized = filename.replaceAll("\\", "/");
if (!normalized.includes("/src/") || !normalized.endsWith(".ts") || normalized.endsWith(".d.ts")) {
return false;
}
return packageRelOf(normalized) !== null;
};
const isCentralConstantsLocation = function isCentralConstantsLocation(filename: string): boolean {
const normalized = filename.replaceAll("\\", "/");
return (
normalized.includes("/constants/") ||
normalized.endsWith("/constants.ts") ||
normalized.endsWith("-constants.ts") ||
normalized.endsWith(".constants.ts")
);
};
const isUpperSnake = function isUpperSnake(name: string): boolean {
let hasUpper = false;
for (const ch of name) {
if (isUpperAlpha(ch)) {
hasUpper = true;
continue;
}
if (!isDigit(ch) && ch !== "_") {
return false;
}
}
return hasUpper;
};
const offendingConstName = function offendingConstName(declarator: VarDeclarator): string | null {
if (declarator.id.type !== "Identifier" || typeof declarator.id.name !== "string") {
return null;
}
const { name } = declarator.id;
if (!isUpperSnake(name)) {
return null;
}
const initType = declarator.init?.type;
return typeof initType === "string" && CONFIG_INIT_TYPES.has(initType) ? name : null;
};
const onExport = function onExport(context: Rule.RuleContext, node: Rule.Node): void {
if (node.type !== "ExportNamedDeclaration") {
return;
}
const { declaration } = node;
if (declaration?.type !== "VariableDeclaration" || declaration.kind !== "const") {
return;
}
for (const declarator of declaration.declarations) {
const name = offendingConstName(declarator);
if (name !== null) {
context.report({ data: { name }, messageId: "decentralizedConfig", node });
}
}
};
export default {
create(context): Rule.RuleListener {
if (!isWorkspaceSrcFile(context.filename) || isCentralConstantsLocation(context.filename)) {
return {};
}
const listeners: Rule.RuleListener = {};
for (const selector of SELECTORS) {
listeners[selector] = (node: Rule.Node): void => {
onExport(context, node);
};
}
return listeners;
},
meta: {
docs: {
description:
"Exported UPPER_SNAKE config constants (objects/arrays/literals) must live in the package's central constants location, not scattered across concern modules.",
},
messages: { decentralizedConfig: MESSAGE },
schema: [],
type: "problem",
},
} satisfies Rule.RuleModule;