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

rules/eslint/closure-no-platform-product-import.eslint.rule.ts is a file in GovLab Extension Host. 61 lines of code and 8 definitions.

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;