# rules/eslint/centralized-events.eslint.rule.ts

> 53 lines of code and 13 definitions.

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

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), before [rules/eslint/centralized-strings.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/centralized-strings.eslint.rule.ts.md).

## Definitions

- `inAnyFolder` (lexical_declaration, line 18)
- `create` (method_definition, line 24, exported)
- `LISTENER_CONCERN` (lexical_declaration, line 8)
- `IDS_CONCERN` (lexical_declaration, line 9)
- `DIRS` (lexical_declaration, line 11)
- `LISTENER_FOLDERS` (lexical_declaration, line 12)
- `IDS_FOLDERS` (lexical_declaration, line 13)
- `IDS_SUFFIX` (lexical_declaration, line 15)
- `LISTENER_SUFFIX` (lexical_declaration, line 16)
- `norm` (lexical_declaration, line 19)
- `{ filename }` (lexical_declaration, line 25, exported)
- `basename` (lexical_declaration, line 26, exported)
- `program` (method_definition, line 36, exported)

## Uses

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

## Records this file checks

- [Introspection](https://banes-lab.com/records/architecture/introspection.md)

## Source

```typescript
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import { basenameOf, normalizePath, projectDirs } from "../../shared/resolvers/anchor.resolver.ts";
import { concernFolders } from "../../shared/resolvers/container.resolver.ts";
import { concernSuffix } from "../../shared/manifests/taxonomy.manifest.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";

const LISTENER_CONCERN = "listener";
const IDS_CONCERN = "ids";

const DIRS = projectDirs();
const LISTENER_FOLDERS = concernFolders(LISTENER_CONCERN, DIRS);
const IDS_FOLDERS = concernFolders(IDS_CONCERN, DIRS);

const IDS_SUFFIX = concernSuffix(IDS_CONCERN);
const LISTENER_SUFFIX = concernSuffix(LISTENER_CONCERN);

const inAnyFolder = function inAnyFolder(filename: string, folders: readonly string[]): boolean {
    const norm = normalizePath(filename);
    return folders.some((folder) => norm.startsWith(folder) || norm.includes(`/${folder}`));
};

export default {
    create(context: RuleContext): RuleListener {
        const { filename } = context;
        const basename = basenameOf(filename);
        if (basename.endsWith(IDS_SUFFIX) && !inAnyFolder(filename, IDS_FOLDERS)) {
            return listener({
                program(_view, node) {
                    context.report({ data: { basename }, messageId: "misplacedEventIdsFile", node });
                },
            });
        }
        if (basename.endsWith(LISTENER_SUFFIX) && !inAnyFolder(filename, LISTENER_FOLDERS)) {
            return listener({
                program(_view, node) {
                    context.report({ data: { basename }, messageId: "misplacedListenerFile", node });
                },
            });
        }
        return {};
    },
    meta: {
        docs: {
            checks: defineCheck({ detects: [], enforces: ["architecture:introspection"] }),
            description:
                "Ids modules live in an ids concern folder and listeners in a listeners concern folder; both folder sets are DISCOVERED from the tree, never hardcoded, so the rule follows a rename with no edit. Centralizing both is what lets a cross-cutting consumer — telemetry, replay, audit, an automated observer — introspect the whole surface at once. A scattered surface cannot be introspected, only searched, and a search misses what it does not know to look for.",
        },
        messages: {
            misplacedEventIdsFile:
                "Ids file '{{basename}}' sits outside every declared ids folder. Move it into one — an ids module is a registry's symbol table, and a scattered symbol table cannot be introspected as a whole.",
            misplacedListenerFile:
                "Listener file '{{basename}}' sits outside every declared listeners folder. Move it into one — listeners self-register through the glob barrel, so one outside it is never discovered and never runs.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;
```
