# rules/eslint/require-self-registration.eslint.rule.ts

> 124 lines of code and 26 definitions.

Tree: Governance tree
Language: typescript
Canonical: https://banes-lab.com/anatomy/governance#file-governance-rules-eslint-require-self-registration-eslint-rule-ts
Source text: https://banes-lab.com/source/governance/rules/eslint/require-self-registration.eslint.rule.ts.txt

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), after [rules/eslint/require-pool-registration.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/require-pool-registration.eslint.rule.ts.md) and before [rules/eslint/require-signal-guard.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/require-signal-guard.eslint.rule.ts.md).

## Definitions

- `isVariantFile` (lexical_declaration, line 61)
- `isTopLevelCallTo` (lexical_declaration, line 88)
- `walk` (lexical_declaration, line 18)
- `program` (method_definition, line 103, exported)
- `barrelFolders` (lexical_declaration, line 15)
- `surfaceFor` (lexical_declaration, line 75)
- `create` (method_definition, line 97, exported)
- `BARREL_MARKER` (lexical_declaration, line 12)
- `INFRASTRUCTURE_CONCERNS` (lexical_declaration, line 13)
- `found` (lexical_declaration, line 16)
- `memberPrefix` (lexical_declaration, line 17)
- `p` (lexical_declaration, line 20)
- `deriveSurfaces` (lexical_declaration, line 36)
- `folders` (lexical_declaration, line 37)
- `tally` (lexical_declaration, line 38)
- `graph` (lexical_declaration, line 39)
- `dir` (lexical_declaration, line 41)
- `counts` (lexical_declaration, line 45)
- `surfaces` (lexical_declaration, line 49)
- `[winner]` (lexical_declaration, line 51)
- `SURFACES` (lexical_declaration, line 59)
- `parsed` (lexical_declaration, line 65)
- `norm` (lexical_declaration, line 76)
- `expression` (lexical_declaration, line 92)
- `surface` (lexical_declaration, line 98, exported)
- `payload` (lexical_declaration, line 107, exported)

## Uses

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

## Records this file checks

- [Registry Pattern](https://banes-lab.com/records/architecture/registry-pattern.md)
- [Open/Closed Principle](https://banes-lab.com/records/architecture/open-closed.md)

## Source

```typescript
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import { MEMBER_ROOT, basenameOf, normalizePath } from "../../shared/resolvers/anchor.resolver.ts";
import { calleeName, isType, nodeAt, nodesAt } from "../../shared/selectors/syntax.selector.ts";
import { existsSync, readdirSync, statSync } from "node:fs";
import { isExempt, isParsed, parseFilename } from "../../shared/matchers/filename.matcher.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import { defineCheck } from "@govlab/context/check";
import { join } from "node:path";
import { listener } from "../../shared/factories/listener.factory.ts";
import { loadClosureGraph } from "../../shared/loaders/graph.loader.ts";

const BARREL_MARKER = ".barrel.ts";
const INFRASTRUCTURE_CONCERNS = ["registry", "types", "constants", "schema", "ids"];

const barrelFolders = function barrelFolders(): Set<string> {
    const found = new Set<string>();
    const memberPrefix = normalizePath(MEMBER_ROOT);
    const walk = function walk(absDir: string): void {
        for (const name of readdirSync(absDir)) {
            const p = join(absDir, name);
            if (statSync(p).isDirectory()) {
                walk(p);
                continue;
            }
            if (name.endsWith(BARREL_MARKER)) {
                found.add(`${normalizePath(absDir).slice(memberPrefix.length + 1)}/`);
            }
        }
    };
    if (existsSync(MEMBER_ROOT)) {
        walk(MEMBER_ROOT);
    }
    return found;
};

const deriveSurfaces = function deriveSurfaces(): Map<string, string> {
    const folders = barrelFolders();
    const tally = new Map<string, Map<string, number>>();
    const graph = loadClosureGraph();
    for (const row of graph === null ? [] : graph.registers) {
        const dir = row.file.slice(0, row.file.lastIndexOf("/") + 1);
        if (!folders.has(dir)) {
            continue;
        }
        const counts = tally.get(dir) ?? new Map<string, number>();
        counts.set(row.fn, (counts.get(row.fn) ?? 0) + 1);
        tally.set(dir, counts);
    }
    const surfaces = new Map<string, string>();
    for (const [dir, counts] of tally) {
        const [winner] = [...counts].toSorted((a, b) => b[1] - a[1]);
        if (winner !== undefined) {
            surfaces.set(dir, winner[0]);
        }
    }
    return surfaces;
};

const SURFACES = deriveSurfaces();

const isVariantFile = function isVariantFile(basename: string): boolean {
    if (basename.endsWith(BARREL_MARKER)) {
        return false;
    }
    const parsed = parseFilename(basename);
    if (isExempt(parsed)) {
        return false;
    }
    if (isParsed(parsed)) {
        return !INFRASTRUCTURE_CONCERNS.includes(parsed.concern);
    }
    return !INFRASTRUCTURE_CONCERNS.some((concern) => basename.endsWith(`.${concern}.ts`));
};

const surfaceFor = function surfaceFor(filename: string): { dir: string; required: string } | null {
    const norm = normalizePath(filename);
    if (!isVariantFile(basenameOf(norm))) {
        return null;
    }
    for (const [dir, required] of SURFACES) {
        if (norm.includes(`/${dir}`)) {
            return { dir, required };
        }
    }
    return null;
};

const isTopLevelCallTo = function isTopLevelCallTo(statement: AstNode, name: string): boolean {
    if (statement.type !== "ExpressionStatement") {
        return false;
    }
    const expression = nodeAt(statement, "expression");
    return isType(expression, "CallExpression") && calleeName(expression) === name;
};

export default {
    create(context: RuleContext): RuleListener {
        const surface = surfaceFor(context.filename);
        if (surface === null) {
            return {};
        }
        return listener({
            program(view, node) {
                if (nodesAt(view, "body").some((stmt) => isTopLevelCallTo(stmt, surface.required))) {
                    return;
                }
                const payload = {
                    basename: basenameOf(context.filename),
                    dir: surface.dir,
                    required: surface.required,
                };
                context.report({ data: payload, messageId: "missing", node });
            },
        });
    },
    meta: {
        docs: {
            checks: defineCheck({
                detects: [],
                enforces: ["architecture:registry-pattern", "architecture:open-closed"],
            }),
            description:
                "Every variant file in a self-registration surface must call that surface's register function at module top level. The surfaces are DERIVED, never listed: a folder holding a glob barrel is a glob-discovered variant surface, and the register function it requires is read from the closure graph's register call sites in that folder. A folder whose barrel globs elsewhere records no register call of its own and is not a surface. Infrastructure files — registry, types, constants, schemas, and the barrel itself — are excluded.",
        },
        messages: {
            missing:
                "File '{{basename}}' sits in self-registration surface '{{dir}}' but does not call '{{required}}(...)' at module top level. Call it, or — if the file is infrastructure for the folder rather than a variant — give it the concern it plays: registry, types, constants or schemas.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;
```
