import tseslint from 'typescript-eslint'; /** * Enforces the architecture's working agreements that were previously only * documented (CLAUDE.md): no `any`, domain/ stays framework-free, and the * dependency direction between contexts (herregistratie → registratie → shared, * auth → shared; shared depends on nothing). Boundary rules use path patterns on * the import aliases, so they read as the direction statement they enforce. */ export default [ { ignores: [ 'dist/**', 'node_modules/**', 'storybook-static/**', '.angular/**', 'coverage/**', 'backend/**', // Generated client — owns its own /* eslint-disable */ header. 'src/app/shared/infrastructure/api-client.ts', ], }, { files: ['src/**/*.ts'], languageOptions: { parser: tseslint.parser, parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, }, plugins: { '@typescript-eslint': tseslint.plugin }, rules: { '@typescript-eslint/no-explicit-any': 'error' }, }, // Tests legitimately use `any` to feed invalid messages/states into reducers. { files: ['src/**/*.spec.ts'], rules: { '@typescript-eslint/no-explicit-any': 'off' }, }, // domain/ = pure business rules + types. No Angular, ever. { files: ['src/app/**/domain/**/*.ts'], rules: { 'no-restricted-imports': [ 'error', { patterns: [{ group: ['@angular/*', '@angular/**'], message: 'domain/ must stay framework-free (pure TS) — no Angular imports.' }] }, ], }, }, // shared/ is the base layer: it may not depend on any feature context. // The dev-only debug panel is the sanctioned exception (it observes every store). { files: ['src/app/shared/**/*.ts'], ignores: ['src/app/shared/ui/debug-state/**'], rules: { 'no-restricted-imports': [ 'error', { patterns: [{ group: ['@auth/*', '@registratie/*', '@herregistratie/*', '@brief/*'], message: 'shared/ must not depend on a feature context.' }] }, ], }, }, // auth/ may depend only on shared. { files: ['src/app/auth/**/*.ts'], rules: { 'no-restricted-imports': [ 'error', { patterns: [{ group: ['@registratie/*', '@herregistratie/*', '@brief/*'], message: 'auth/ may depend only on shared.' }] }, ], }, }, // registratie/ may depend on shared, not on herregistratie (direction points the other way). { files: ['src/app/registratie/**/*.ts'], rules: { 'no-restricted-imports': [ 'error', { patterns: [{ group: ['@herregistratie/*', '@brief/*'], message: 'Dependencies point herregistratie → registratie → shared, never back.' }] }, ], }, }, // brief/ (letter composition) is an independent leaf context: it may depend only on shared. { files: ['src/app/brief/**/*.ts'], rules: { 'no-restricted-imports': [ 'error', { patterns: [{ group: ['@auth/*', '@registratie/*', '@herregistratie/*'], message: 'brief/ may depend only on shared.' }] }, ], }, }, // contracts/ is the FE⇄BE wire seam: pure DTO shapes that must import NOTHING // (CLAUDE.md §1, ADR-0001) — not Angular, not a context alias, not relative app // code. Enums are inlined string-literal unions; the adapter's parse* maps them. // (This comes after the per-context rules so it wins for contracts files.) { files: ['src/app/**/contracts/**/*.ts'], rules: { 'no-restricted-imports': [ 'error', { patterns: [ { group: ['@angular/**', '@shared/**', '@auth/**', '@registratie/**', '@herregistratie/**', '@brief/**', './*', '../*', './**', '../**'], message: 'contracts/ is the wire seam — it must import NOTHING (pure DTO shapes). Map wire → domain in the infrastructure adapter, not here.', }, ], }, ], }, }, // BFF-lite anti-corruption boundary (ADR-0001): the ApiClient (the network // client) may be imported as a VALUE only from infrastructure-role files. // Type-only imports of generated wire DTOs are allowed anywhere — they grant no // network access. UI/application reach the network through an adapter or command. { files: ['src/app/**/*.ts'], plugins: { '@typescript-eslint': tseslint.plugin }, rules: { '@typescript-eslint/no-restricted-imports': [ 'error', { patterns: [ { group: ['@shared/infrastructure/api-client'], allowTypeImports: true, message: 'The ApiClient lives only in infrastructure/ adapters (ADR-0001). UI/application call an adapter or a command, not the network client. (Type-only DTO imports are fine: use `import type`.)', }, ], }, ], }, }, // …the infrastructure adapters ARE that boundary and own the client. shared/upload // is a feature-scoped adapter that lives outside a /infrastructure/ folder. { files: ['src/app/**/infrastructure/**/*.ts', 'src/app/shared/upload/**/*.ts'], plugins: { '@typescript-eslint': tseslint.plugin }, rules: { '@typescript-eslint/no-restricted-imports': 'off' }, }, ];