# rules/eslint/require-cache-coverage.eslint.rule.ts

> 96 lines of code and 26 definitions.

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

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), after [rules/eslint/no-undeclared-dependency.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/no-undeclared-dependency.eslint.rule.ts.md) and before [rules/eslint/require-component.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/require-component.eslint.rule.ts.md).

## Definitions

- `isMapField` (lexical_declaration, line 44)
- `countMapFields` (lexical_declaration, line 57)
- `isStatefulPlatformFile` (lexical_declaration, line 32)
- `inPlatformTier` (lexical_declaration, line 21)
- `importSourceMentionsCache` (lexical_declaration, line 40)
- `create` (method_definition, line 64, exported)
- `classDeclaration` (method_definition, line 75, exported)
- `importDeclaration` (method_definition, line 78, exported)
- `PLATFORM_ROOTS` (lexical_declaration, line 10)
- `CACHING_CONCERNS` (lexical_declaration, line 12)
- `OWNS_STATE_MARKER` (lexical_declaration, line 13)
- `MIN_KEYED_INDEXES` (lexical_declaration, line 14)
- `MAP_CONSTRUCTOR` (lexical_declaration, line 15)
- `CACHE_IMPORT_FRAGMENTS` (lexical_declaration, line 17)
- `baseNameOf` (lexical_declaration, line 25)
- `lastSlash` (lexical_declaration, line 27)
- `normalized` (lexical_declaration, line 33)
- `annotation` (lexical_declaration, line 45)
- `init` (lexical_declaration, line 49)
- `callee` (lexical_declaration, line 53)
- `{ filename }` (lexical_declaration, line 65, exported)
- `source` (lexical_declaration, line 66, exported)
- `base` (lexical_declaration, line 70, exported)
- `hasCacheImport` (lexical_declaration, line 71, exported)
- `maxMapFields` (lexical_declaration, line 72, exported)
- `specifier` (lexical_declaration, line 79, 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

- [Caching](https://banes-lab.com/records/architecture/caching.md)

## Source

```typescript
import { GOVERNED_ROOT, normalizePath, projectDirs } from "../../shared/resolvers/anchor.resolver.ts";
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import { concernFolders, containerPath } from "../../shared/resolvers/container.resolver.ts";
import { isType, literalString, nameOf, nodeAt, nodesAt } from "../../shared/selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import { PLATFORM_PATH_PREFIXES } from "../../shared/manifests/layer.manifest.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";

const PLATFORM_ROOTS = PLATFORM_PATH_PREFIXES.map((prefix) => `/${containerPath(prefix.slice(0, -1), GOVERNED_ROOT)}`);

const CACHING_CONCERNS = ["cache", "pool"];
const OWNS_STATE_MARKER = "export class";
const MIN_KEYED_INDEXES = 2;
const MAP_CONSTRUCTOR = "Map";

const CACHE_IMPORT_FRAGMENTS = CACHING_CONCERNS.flatMap((tag) =>
    concernFolders(tag, projectDirs()).map((dir) => `/${dir.slice(dir.lastIndexOf("/", dir.length - 2) + 1)}`),
);

const inPlatformTier = function inPlatformTier(normalized: string): boolean {
    return PLATFORM_ROOTS.some((root) => normalized.includes(root));
};

const baseNameOf = function baseNameOf(filename: string): string {
    const normalized = normalizePath(filename);
    const lastSlash = normalized.lastIndexOf("/");
    const base = lastSlash === -1 ? normalized : normalized.slice(lastSlash + 1);
    return base.endsWith(".ts") ? base.slice(0, -3) : base;
};

const isStatefulPlatformFile = function isStatefulPlatformFile(filename: string, source: string): boolean {
    const normalized = normalizePath(filename);
    if (!inPlatformTier(normalized) || normalized.endsWith(".test.ts")) {
        return false;
    }
    return source.includes(OWNS_STATE_MARKER);
};

const importSourceMentionsCache = function importSourceMentionsCache(source: string): boolean {
    return CACHE_IMPORT_FRAGMENTS.some((fragment) => source.includes(fragment));
};

const isMapField = function isMapField(member: AstNode): boolean {
    const annotation = nodeAt(nodeAt(member, "typeAnnotation"), "typeAnnotation");
    if (isType(annotation, "TSTypeReference") && nameOf(nodeAt(annotation, "typeName")) === MAP_CONSTRUCTOR) {
        return true;
    }
    const init = nodeAt(member, "value");
    if (!isType(init, "NewExpression")) {
        return false;
    }
    const callee = nodeAt(init, "callee");
    return isType(callee, "Identifier") && nameOf(callee) === MAP_CONSTRUCTOR;
};

const countMapFields = function countMapFields(classNode: AstNode): number {
    return nodesAt(nodeAt(classNode, "body"), "body").filter(
        (member) => member.type === "PropertyDefinition" && isMapField(member),
    ).length;
};

export default {
    create(context: RuleContext): RuleListener {
        const { filename } = context;
        const source = context.sourceCode.getText();
        if (!isStatefulPlatformFile(filename, source)) {
            return {};
        }
        const base = baseNameOf(filename);
        let hasCacheImport = false;
        let maxMapFields = 0;
        return listener(
            {
                classDeclaration(view) {
                    maxMapFields = Math.max(maxMapFields, countMapFields(view));
                },
                importDeclaration(view) {
                    const specifier = literalString(nodeAt(view, "source"));
                    if (specifier !== null && importSourceMentionsCache(specifier)) {
                        hasCacheImport = true;
                    }
                },
            },
            (_view: AstNode, node): void => {
                if (hasCacheImport || maxMapFields >= MIN_KEYED_INDEXES) {
                    return;
                }
                context.report({ data: { name: base }, messageId: "missing", node });
            },
        );
    },
    meta: {
        docs: {
            checks: defineCheck({ detects: [], enforces: ["architecture:caching"] }),
            description:
                "A stateful coordinator over a collection either uses the shared caching primitives or declares its own keyed secondary index. Neither means every read is a scan, which is a performance defect that only appears at scale — the check makes the choice explicit rather than accidental.",
        },
        messages: {
            missing:
                "Stateful platform-tier file '{{name}}' has no cache coverage. Choose one: import a caching primitive from the pool or cache concern, or declare a keyed secondary index of your own.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;
```
