# rules/eslint/closure-no-platform-product-import.eslint.rule.ts

> 61 lines of code and 8 definitions.

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

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), after [rules/eslint/closure-no-platform-capability-redefine.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-no-platform-capability-redefine.eslint.rule.ts.md) and before [rules/eslint/closure-no-reachable-dead-exports.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-no-reachable-dead-exports.eslint.rule.ts.md).

## Definitions

- `productImportsIn` (lexical_declaration, line 10)
- `create` (method_definition, line 28, exported)
- `program` (method_definition, line 34, exported)
- `GRAPH` (lexical_declaration, line 8)
- `violations` (lexical_declaration, line 14)
- `resolved` (lexical_declaration, line 19)
- `rel` (lexical_declaration, line 29, exported)
- `payload` (lexical_declaration, line 40, exported)

## Uses

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

## Records this file checks

- [Dependency Inversion Principle](https://banes-lab.com/records/architecture/dependency-inversion.md)
- [Boundary Leakage](https://banes-lab.com/records/architecture/boundary-leakage.md)

## Source

```typescript
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import { classifyFile, relativeFromMember, resolveImportPath } from "../../shared/manifests/layer.manifest.ts";
import type { ClosureGraph } from "../../types/closure.types.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";
import { loadClosureGraph } from "../../shared/loaders/graph.loader.ts";

const GRAPH = loadClosureGraph();

const productImportsIn = function productImportsIn(
    closure: ClosureGraph,
    sourceFile: string,
): { from: string; resolved: string }[] {
    const violations: { from: string; resolved: string }[] = [];
    for (const imp of closure.imports) {
        if (relativeFromMember(imp.file) !== sourceFile) {
            continue;
        }
        const resolved = resolveImportPath(imp.file, imp.from);
        if (resolved !== null && classifyFile(resolved) === "product") {
            violations.push({ from: imp.from, resolved });
        }
    }
    return violations;
};

export default {
    create(context: RuleContext): RuleListener {
        const rel = relativeFromMember(context.filename);
        if (classifyFile(rel) !== "platform") {
            return {};
        }
        return listener({
            program(_view, node) {
                if (GRAPH === null) {
                    context.report({ messageId: "graphMissing", node });
                    return;
                }
                for (const v of productImportsIn(GRAPH, rel)) {
                    const payload = { from: v.from, resolved: v.resolved };
                    context.report({ data: payload, messageId: "platformImportsProduct", node });
                }
            },
        });
    },
    meta: {
        docs: {
            checks: defineCheck({
                detects: ["architecture:boundary-leakage"],
                enforces: ["architecture:dependency-inversion"],
            }),
            description:
                "A file classified platform may not import one classified product. The platform tier is application-agnostic by definition, so a product dependency inverts the direction and makes the platform unreusable in the next application. The classification is declared in the layer manifest, not inferred from a path here.",
        },
        messages: {
            graphMissing:
                "closure-graph missing. Run `npm run verify` to regenerate. Fail-close until the graph is available.",
            platformImportsProduct:
                "Platform file imports the product tier: `{{from}}` resolves to `{{resolved}}`, which is classified product. Three resolutions, in order of preference: move the imported file into the platform tier if it is genuinely generic; invert the flow so the platform emits and the product subscribes; or inject the dependency behind an interface the platform declares.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;
```
