# rules/eslint/closure-register-verb-reserved.eslint.rule.ts

> 57 lines of code and 13 definitions.

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

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), after [rules/eslint/closure-register-needs-consumer.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-register-needs-consumer.eslint.rule.ts.md) and before [rules/eslint/closure-registry-consumer-naming.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-registry-consumer-naming.eslint.rule.ts.md).

## Definitions

- `isSkippedFile` (lexical_declaration, line 15)
- `isExportedFunction` (lexical_declaration, line 23)
- `create` (method_definition, line 29, exported)
- `functionDeclaration` (method_definition, line 34, exported)
- `REGISTER_VERB` (lexical_declaration, line 10)
- `REGISTRY_FILE_SUFFIXES` (lexical_declaration, line 11)
- `EXEMPT_BASENAME_SUFFIXES` (lexical_declaration, line 12)
- `EXPORT_PARENTS` (lexical_declaration, line 13)
- `basename` (lexical_declaration, line 16)
- `parent` (lexical_declaration, line 24)
- `id` (lexical_declaration, line 35, exported)
- `name` (lexical_declaration, line 39, exported)
- `suffix` (lexical_declaration, line 40, 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

- [Intent-Revealing Interface](https://banes-lab.com/records/architecture/intent-revealing-interface.md)

## Source

```typescript
import type { LocalRule, 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 { basenameOf } from "../../shared/resolvers/anchor.resolver.ts";
import { concernSuffix } from "../../shared/manifests/taxonomy.manifest.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";
import { suffixAfterVerb } from "../../shared/manifests/verb.manifest.ts";

const REGISTER_VERB = "register";
const REGISTRY_FILE_SUFFIXES = [concernSuffix("registry")];
const EXEMPT_BASENAME_SUFFIXES = [".test.ts", ".spec.ts"];
const EXPORT_PARENTS = new Set(["ExportNamedDeclaration", "ExportDefaultDeclaration"]);

const isSkippedFile = function isSkippedFile(filename: string): boolean {
    const basename = basenameOf(filename);
    if (REGISTRY_FILE_SUFFIXES.some((suffix) => basename.endsWith(suffix))) {
        return true;
    }
    return EXEMPT_BASENAME_SUFFIXES.some((suffix) => basename.endsWith(suffix));
};

const isExportedFunction = function isExportedFunction(node: AstNode): boolean {
    const parent = nodeAt(node, "parent");
    return parent !== null && EXPORT_PARENTS.has(parent.type);
};

export default {
    create(context: RuleContext): RuleListener {
        if (isSkippedFile(context.filename)) {
            return {};
        }
        return listener({
            functionDeclaration(view, node) {
                const id = nodeAt(view, "id");
                if (!isExportedFunction(view) || !isType(id, "Identifier")) {
                    return;
                }
                const name = nameOf(id);
                const suffix = suffixAfterVerb(name, REGISTER_VERB);
                if (suffix === null) {
                    return;
                }
                context.report({ data: { name, suffix }, messageId: "reservedVerb", node });
            },
        });
    },
    meta: {
        docs: {
            checks: defineCheck({ detects: [], enforces: ["architecture:intent-revealing-interface"] }),
            description:
                "The `register*` verb is reserved for module-scope dispatch-table operations — functions that mutate a shared registry. A function that attaches a callback to one specific instance is doing something else, and naming it `register*` makes the two indistinguishable to every tool that finds registrations by that verb. Instance-scoped attachment uses `subscribe*` / `add*` / `track*` / `attach*` instead.",
        },
        messages: {
            reservedVerb:
                "Function `{{ name }}` uses the `register*` prefix but is declared outside a *.registry.ts file. The `register*` verb is reserved for dispatch-table registrations. Rename to `subscribe{{ suffix }}` / `track{{ suffix }}` / `attach{{ suffix }}` depending on the operation's semantics.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;
```
