# rules/eslint/closure-no-browser-derivation.eslint.rule.ts

> 59 lines of code and 10 definitions.

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

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), after [rules/eslint/closure-lifecycle-handle-must-be-stored.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-lifecycle-handle-must-be-stored.eslint.rule.ts.md) and before [rules/eslint/closure-no-business-inheritance.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-no-business-inheritance.eslint.rule.ts.md).

## Definitions

- `derivationImportsIn` (lexical_declaration, line 21)
- `browserFiles` (lexical_declaration, line 13)
- `create` (method_definition, line 29, exported)
- `program` (method_definition, line 42, exported)
- `ENTRYPOINT_SUFFIX` (lexical_declaration, line 11)
- `entrypoints` (lexical_declaration, line 14)
- `GRAPH` (lexical_declaration, line 18)
- `BROWSER` (lexical_declaration, line 19)
- `target` (lexical_declaration, line 23)
- `rel` (lexical_declaration, line 30, exported)

## Uses

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

## Records this file checks

- [Single Source of Truth](https://banes-lab.com/records/architecture/single-source-of-truth.md)

## Source

```typescript
import { DERIVATION_MODULES, relativeFromMember } from "../../shared/manifests/layer.manifest.ts";
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import { buildImportGraph, findEntrypoints, walkReachable } from "../../shared/analyzers/liveness.analyzer.ts";
import { loadClosureGraph, normalizeImport } from "../../shared/loaders/graph.loader.ts";
import type { ClosureGraph } from "../../types/closure.types.ts";
import { concernSuffix } from "../../shared/manifests/taxonomy.manifest.ts";
import { defineCheck } from "@govlab/context/check";
import { deriveBarrelPatterns } from "../../shared/loaders/barrel.loader.ts";
import { listener } from "../../shared/factories/listener.factory.ts";

const ENTRYPOINT_SUFFIX = concernSuffix("entrypoint");

const browserFiles = function browserFiles(closure: ClosureGraph): ReadonlySet<string> {
    const entrypoints = findEntrypoints(closure, [ENTRYPOINT_SUFFIX], deriveBarrelPatterns());
    return walkReachable(entrypoints, buildImportGraph(closure)).reachableFiles;
};

const GRAPH = loadClosureGraph();
const BROWSER = GRAPH === null ? null : browserFiles(GRAPH);

const derivationImportsIn = function derivationImportsIn(closure: ClosureGraph, file: string): readonly string[] {
    return [...closure.imports, ...closure.sideEffectImports].flatMap((imp) => {
        const target = imp.file === file ? normalizeImport(imp.file, imp.from) : null;
        return target !== null && DERIVATION_MODULES.has(target) ? [target] : [];
    });
};

export default {
    create(context: RuleContext): RuleListener {
        const rel = relativeFromMember(context.filename);
        if (GRAPH === null || BROWSER === null) {
            return listener({
                program(_view, node) {
                    context.report({ messageId: "graphMissing", node });
                },
            });
        }
        if (!BROWSER.has(rel) || DERIVATION_MODULES.has(rel)) {
            return {};
        }
        return listener({
            program(_view, node) {
                for (const target of derivationImportsIn(GRAPH, rel)) {
                    context.report({ data: { file: rel, module: target }, messageId: "browserDerivation", node });
                }
            },
        });
    },
    meta: {
        docs: {
            checks: defineCheck({ detects: [], enforces: ["architecture:single-source-of-truth"] }),
            description:
                "A file the browser bundle reaches may not import a module that derives relations. The build derives every relation and writes it into the generated assets a deployment ships, so a derivation in the browser answers the same question a second time. The modules are declared in DERIVATION_MODULES. This rule sees imports only; a derivation written inline in a browser file is not reported.",
        },
        messages: {
            browserDerivation:
                "`{{file}}` is part of the browser bundle and imports `{{module}}`, which derives relations while the page runs. Read the relation from the generated asset instead.",
            graphMissing:
                "The closure graph is missing, so this rule reports a failure instead of passing without reading anything. Run the gate, whose auto-fix stage rebuilds the graph before the lint stage reads it.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;
```
