core/quality/eslint/no-concrete-dependency.eslint.rule.ts
core/quality/eslint/no-concrete-dependency.eslint.rule.ts is a file in GovLab Quality. 92 lines of code and 13 definitions.
import type { Rule } from "eslint";
import { govlabMeta } from "#core/factories/eslint.factory";
const VALUE_BUILTINS = new Set<string>([
"Map",
"Set",
"WeakMap",
"WeakSet",
"Date",
"Array",
"Object",
"Promise",
"Error",
"RegExp",
"URL",
"URLSearchParams",
"ArrayBuffer",
"DataView",
"AbortController",
"EventTarget",
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array",
"BigInt64Array",
"BigUint64Array",
]);
interface AstNode {
type: string;
name?: string;
left?: AstNode;
right?: AstNode;
object?: AstNode;
callee?: AstNode;
value?: AstNode | null;
}
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 concreteName = function concreteName(valueNode: AstNode | null | undefined): string | null {
if (valueNode?.type !== "NewExpression") {
return null;
}
const { callee } = valueNode;
if (callee?.type !== "Identifier" || typeof callee.name !== "string") {
return null;
}
if (VALUE_BUILTINS.has(callee.name)) {
return null;
}
return callee.name;
};
export default {
create(context: Rule.RuleContext): Rule.RuleListener {
const report = function report(name: string | null, node: Rule.Node): void {
if (name !== null) {
context.report({ data: { name }, messageId: "concreteDependency", node });
}
};
const onAssign = (node: Rule.Node): void => {
const assign = asNode(node);
const left = assign?.left;
if (left?.type === "MemberExpression" && left.object?.type === "ThisExpression") {
report(concreteName(assign?.right), node);
}
};
const onPropDef = (node: Rule.Node): void => {
report(concreteName(asNode(node)?.value), node);
};
const handlers: [string, (node: Rule.Node) => void][] = [
["AssignmentExpression", onAssign],
["PropertyDefinition", onPropDef],
];
return Object.fromEntries(handlers);
},
meta: govlabMeta({
canonical: ["concrete-coupling"],
description: "Disallow constructing a concrete dependency as a class field — mandate injecting the abstraction",
messages: {
concreteDependency:
"This class field is bound to `new {{name}}()` — a concrete dependency hardcoded into the class (concrete coupling), so it cannot be substituted or the class tested in isolation. Accept `{{name}}`'s interface as a constructor parameter and inject it.",
},
ruleId: "concrete_dependency",
}),
} satisfies Rule.RuleModule;