core/quality/eslint/no-barrel-reexport.eslint.rule.ts

core/quality/eslint/no-barrel-reexport.eslint.rule.ts is a file in GovLab Quality. 46 lines of code and 9 definitions.

import type { Rule } from "eslint";
import { govlabMeta } from "#core/factories/eslint.factory";

interface AstNode {
    type: string;
    source?: { value?: unknown };
}

const INDEX_SUFFIXES = ["/index.ts", "/index"];

const isAstNode = function isAstNode(value: unknown): value is AstNode {
    return value !== null && typeof value === "object" && "type" in value;
};

const asNode = function asNode(value: unknown): AstNode | null {
    return isAstNode(value) ? value : null;
};

const isIndexReexport = function isIndexReexport(node: AstNode | null): boolean {
    const source = node?.source?.value;
    if (typeof source !== "string") {
        return false;
    }
    return INDEX_SUFFIXES.some((suffix) => source.endsWith(suffix));
};

export default {
    create(context: Rule.RuleContext): Rule.RuleListener {
        if (!context.filename.endsWith("index.ts")) {
            return {};
        }
        const onExport = (node: Rule.Node): void => {
            if (isIndexReexport(asNode(node))) {
                context.report({ messageId: "noBarrelReexport", node });
            }
        };
        const handlers: [string, (node: Rule.Node) => void][] = [
            ["ExportAllDeclaration", onExport],
            ["ExportNamedDeclaration", onExport],
        ];
        return Object.fromEntries(handlers);
    },
    meta: govlabMeta({
        canonical: ["module-boundaries", "circular-dependency"],
        description: "Disallow re-exporting from index.ts inside another index.ts (circular barrel prevention)",
        messages: {
            noBarrelReexport:
                "A barrel (index) file must not re-export from another index — this creates circular dependency chains. Import directly from the source module file instead.",
        },
        ruleId: "module_boundary_index",
    }),
} satisfies Rule.RuleModule;