rules/eslint/closure-no-business-inheritance.eslint.rule.ts
rules/eslint/closure-no-business-inheritance.eslint.rule.ts is a file in GovLab Extension Host. 61 lines of code and 12 definitions.
import { FOUNDATION_FOLDER, FOUNDATION_PREFIX, normalizePath } from "../../shared/resolvers/anchor.resolver.ts";
import type { LocalRule, NodeHandler, RuleContext, RuleListener } from "../../types/rule.types.ts";
import { isType, nameOf, nodeAt } from "../../shared/selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";
const ALLOWED_BUILTIN_BASES = new Set(["Error", "TypeError", "RangeError", "Map", "Set", "EventTarget", "Array"]);
const isFoundationFile = function isFoundationFile(filename: string): boolean {
return normalizePath(filename).includes(FOUNDATION_FOLDER);
};
const extendedClassName = function extendedClassName(node: AstNode): string {
const superClass = nodeAt(node, "superClass");
if (superClass === null) {
return "";
}
if (superClass.type === "Identifier") {
return nameOf(superClass);
}
if (superClass.type !== "CallExpression") {
return "";
}
const callee = nodeAt(superClass, "callee");
return isType(callee, "Identifier") ? nameOf(callee) : "";
};
const isAllowedBase = function isAllowedBase(name: string): boolean {
return name === "" || name.startsWith(FOUNDATION_PREFIX) || ALLOWED_BUILTIN_BASES.has(name);
};
export default {
create(context: RuleContext): RuleListener {
if (isFoundationFile(context.filename)) {
return {};
}
const checkClass: NodeHandler = function checkClass(view, node) {
if (nodeAt(view, "superClass") === null) {
return;
}
const parent = extendedClassName(view);
if (isAllowedBase(parent)) {
return;
}
const id = nodeAt(view, "id");
const child = id === null ? "<anonymous>" : nameOf(id);
const payload = { child, parent };
context.report({ data: payload, messageId: "businessInheritance", node });
};
return listener({ classDeclaration: checkClass, classExpression: checkClass });
},
meta: {
docs: {
checks: defineCheck({ detects: [], enforces: ["architecture:composition-over-inheritance"] }),
description:
"Bans `class X extends Y` where Y is neither a foundational base — a class whose name opens with the foundation prefix derived from the declared foundation subject — nor a language built-in. Business logic composes rather than inherits: the foundational contracts are the only legitimate extension targets, and everything else shares behavior through a registry, a factory or a service.",
},
messages: {
businessInheritance:
"`class {{ child }} extends {{ parent }}` — {{ parent }} is not a foundational base and not a language built-in. Behavior is shared by composition here: lift what is common into a registry, a factory or a service, or move {{ parent }} into a foundational folder if it genuinely is one. Inheritance for reuse couples the child to the parent's whole shape, including the parts it never wanted.",
},
schema: [],
type: "problem",
},
} satisfies LocalRule;