# rules/eslint/closure-no-business-inheritance.eslint.rule.ts

> 61 lines of code and 12 definitions.

Tree: Governance tree
Language: typescript
Canonical: https://banes-lab.com/anatomy/governance#file-governance-rules-eslint-closure-no-business-inheritance-eslint-rule-ts
Source text: https://banes-lab.com/source/governance/rules/eslint/closure-no-business-inheritance.eslint.rule.ts.txt

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), after [rules/eslint/closure-no-browser-derivation.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-no-browser-derivation.eslint.rule.ts.md) and before [rules/eslint/closure-no-cross-member-relative.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-no-cross-member-relative.eslint.rule.ts.md).

## Definitions

- `isFoundationFile` (lexical_declaration, line 10)
- `isAllowedBase` (lexical_declaration, line 29)
- `extendedClassName` (lexical_declaration, line 14)
- `create` (method_definition, line 34, exported)
- `checkClass` (lexical_declaration, line 38, exported)
- `ALLOWED_BUILTIN_BASES` (lexical_declaration, line 8)
- `superClass` (lexical_declaration, line 15)
- `callee` (lexical_declaration, line 25)
- `parent` (lexical_declaration, line 42, exported)
- `id` (lexical_declaration, line 46, exported)
- `child` (lexical_declaration, line 47, exported)
- `payload` (lexical_declaration, line 48, exported)

## Uses

- [shared/factories/listener.factory.ts](https://banes-lab.com/source/governance/shared/factories/listener.factory.ts.md)
- [shared/selectors/syntax.selector.ts](https://banes-lab.com/source/governance/shared/selectors/syntax.selector.ts.md)

## Records this file checks

- [Composition Over Inheritance](https://banes-lab.com/records/architecture/composition-over-inheritance.md)

## Source

```typescript
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;
```
