Rules
domainlint enforces two core architectural rules plus a permissive default for same-feature imports.
R1 — No import cycles
Section titled “R1 — No import cycles”Violation code: noImportCycle
Circular dependencies make code harder to reason about, cause runtime issues with module initialization, and block tree-shaking. domainlint builds a directed graph of every local import edge and checks that it is acyclic.
What triggers it
Section titled “What triggers it”Any strongly connected component with more than one node, or a self-loop (A → A), is reported as a cycle.
src/features/auth/session.ts noImportCycle Cycle: auth/session.ts → auth/user.ts → auth/session.tsHow to fix
Section titled “How to fix”- Extract shared state into a third module that both files import.
- Use dependency inversion — depend on an interface rather than a concrete implementation.
- Merge tightly coupled files if they truly belong together.
R2 — Cross-feature imports must use the barrel
Section titled “R2 — Cross-feature imports must use the barrel”Violation code: noCrossFeatureDeepImport
Features expose a public API through a barrel file (index.ts by default). Any import that reaches into a feature’s internals from outside that feature is a violation.
Formal definition
Section titled “Formal definition”For every resolved import edge from → to:
- Let
FX = featureOf(from)andFY = featureOf(to). - If
FYis notnullandFX ≠ FY(includingFX = null), thentomust equalbarrelOf(FY).
Allowed
Section titled “Allowed”// ✅ Importing through the barrelimport { Invoice } from "../features/billing";
// ✅ Same-feature deep import (see R3)// (from src/features/billing/ui/Form.tsx)import { validate } from "../domain/invoice";Forbidden
Section titled “Forbidden”// ❌ Bypasses the barrel — reaches into billing internalsimport { Invoice } from "../features/billing/domain/invoice";src/pages/home.tsx:5:1 noCrossFeatureDeepImport Importing src/features/billing/domain/invoice.ts directly. Expected: src/features/billing/index.tsHow to fix
Section titled “How to fix”Re-export the symbol from the feature’s barrel file (index.ts), then import from there.
R3 — Same-feature deep imports are allowed
Section titled “R3 — Same-feature deep imports are allowed”Within a feature, files may import each other freely. domainlint does not enforce internal layering (e.g. ui cannot import domain). This keeps day-to-day development ergonomic while still protecting feature boundaries.
Rule severity
Section titled “Rule severity”Each rule can be configured to "error", "warn", or "off" — globally or per-feature. See Configuration — Rule overrides for details.