# rules/eslint/closure-no-inline-exported-types.eslint.rule.ts

> 66 lines of code and 12 definitions.

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

Listed in [rules/eslint](https://banes-lab.com/api/source/governance/rules/eslint.md), after [rules/eslint/closure-no-import-cycles.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-no-import-cycles.eslint.rule.ts.md) and before [rules/eslint/closure-no-module-singleton.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-no-module-singleton.eslint.rule.ts.md).

## Definitions

- `isExemptFile` (lexical_declaration, line 20)
- `create` (method_definition, line 34, exported)
- `TYPES_CONCERN` (lexical_declaration, line 14)
- `INTERFACE_DECLARATION` (lexical_declaration, line 15)
- `TYPE_ALIAS_DECLARATION` (lexical_declaration, line 16)
- `EXEMPT_BASENAME_SUFFIXES` (lexical_declaration, line 18)
- `basename` (lexical_declaration, line 21)
- `path` (lexical_declaration, line 25)
- `root` (lexical_declaration, line 26)
- `exportNamedDeclaration` (method_definition, line 39, exported)
- `declaration` (lexical_declaration, line 40, exported)
- `payload` (lexical_declaration, line 44, exported)

## Uses

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

## Records this file checks

- [Separation of Concerns](https://banes-lab.com/records/architecture/separation-of-concerns.md)

## Source

```typescript
import type { LocalRule, RuleContext, RuleListener } from "../../types/rule.types.ts";
import { TEST_ROOT_SEGMENT, basenameOf, isInContainer, normalizePath } from "../../shared/resolvers/anchor.resolver.ts";
import {
    concernForPath,
    concernSuffix,
    isImportedRoot,
    isTestRoot,
    rootFor,
} from "../../shared/manifests/taxonomy.manifest.ts";
import { nameOf, nodeAt } from "../../shared/selectors/syntax.selector.ts";
import { defineCheck } from "@govlab/context/check";
import { listener } from "../../shared/factories/listener.factory.ts";

const TYPES_CONCERN = "types";
const INTERFACE_DECLARATION = "TSInterfaceDeclaration";
const TYPE_ALIAS_DECLARATION = "TSTypeAliasDeclaration";

const EXEMPT_BASENAME_SUFFIXES = [concernSuffix(TYPES_CONCERN), ".test.ts", ".spec.ts"];

const isExemptFile = function isExemptFile(filename: string): boolean {
    const basename = basenameOf(filename);
    if (EXEMPT_BASENAME_SUFFIXES.some((suffix) => basename.endsWith(suffix))) {
        return true;
    }
    const path = normalizePath(filename);
    const root = rootFor(path);
    if (isImportedRoot(root)) {
        return isTestRoot(root) || concernForPath(path) === TYPES_CONCERN;
    }
    return isInContainer(filename, TYPES_CONCERN) || path.includes(TEST_ROOT_SEGMENT);
};

export default {
    create(context: RuleContext): RuleListener {
        if (isExemptFile(context.filename)) {
            return {};
        }
        return listener({
            exportNamedDeclaration(view, node) {
                const declaration = nodeAt(view, "declaration");
                if (declaration === null) {
                    return;
                }
                const payload = { name: nameOf(nodeAt(declaration, "id")) };
                if (declaration.type === INTERFACE_DECLARATION) {
                    context.report({ data: payload, messageId: "inlineExportedInterface", node });
                    return;
                }
                if (declaration.type === TYPE_ALIAS_DECLARATION) {
                    context.report({ data: payload, messageId: "inlineExportedTypeAlias", node });
                }
            },
        });
    },
    meta: {
        docs: {
            checks: defineCheck({ detects: [], enforces: ["architecture:separation-of-concerns"] }),
            description:
                "An exported interface or type alias belongs in the type bucket, not beside the runtime code that happens to use it first. A shared contract has a review axis of its own, exactly as the editorial surface does, and one location is what makes reviewing it possible. A file-local, non-exported type is fine — only the exported surface is centralized.",
        },
        messages: {
            inlineExportedInterface:
                "Exported interface `{{name}}` declared outside the type bucket. Move it into a types file there, or add one. A code file carries runtime and imported types only; it must not contain`export interface` or `export type` declarations; inline non-exported types are allowed.",
            inlineExportedTypeAlias:
                "Exported type alias `{{name}}` declared outside the type bucket. Move it into a types file there, or add one. A code file carries runtime and imported types only; it must not contain`export type X = ...` declarations; inline non-exported types are allowed.",
        },
        schema: [],
        type: "problem",
    },
} satisfies LocalRule;
```
