limits

Each modules unit may be imported by at most max_fan_in files outside it, and import at most max_fan_out files and packages of the project outside it. A module that everything imports, or that imports everything, is hard to change without touching much else.

Example

[contracts.small-surface]
type = "limits"
modules = ["web/src/*"]
max_fan_in = 20
max_fan_out = 12
max_fan_in = 2 app/main.ts app/page.ts ui/button.ts web/src/lib log.ts format.ts inside: not counted max_fan_out = 3 config/env.ts utils/date.ts counts once fan-in 3, over its limit of 2 fan-out 2, within 3
With max_fan_in = 2, a third file importing web/src/lib breaks the contract. app/main.ts counts once, though it imports two of its files.

Each directory of web/src is checked on its own:

  • Fan-in is the number of files outside the unit that import it. A file counts once, however many of the unit's files it imports. A unit may be an external package: modules = ["npm:*"] limits how many files import each.
  • Fan-out is the number of files outside the unit that it imports, and of packages that it imports as a whole, such as a Go package. Each counts once, however many of the unit's files import it.
  • Not counted: imports between the unit's own files, imports of external packages such as npm:react and of builtins such as node:fs, and the imports that the contract leaves out: those of its exclude_kinds, its ignore_imports, suppressed ones, and those in the baseline. Only direct imports count. To restrict who may import an external package, use a protected contract.

A unit over a limit is a violation that names the count and the limit, and lists the imports that make up the count. A unit over both limits is two violations.

Options

OptionDefault
modulesrequiredSelectors of the limited units.
max_fan_innoneThe most files outside each unit that may import it.
max_fan_outnoneThe most files and packages of the project outside each unit that it may import.

At least one of max_fan_in and max_fan_out is required.

Common options

Like every contract type, limits also takes:

OptionDefault
typerequiredThe contract type, such as "layers".
namethe idThe title shown in the output.
severity"error""warn" reports a broken contract without failing the check, for rolling a contract out gradually.
exclude_kinds[analysis] exclude_kindsThe import kinds to leave out, e.g. ["type"].
reexports[analysis] reexports"transparent" sees an import of names from a [pass-through module](../concepts.md#pass-through modules) as imports of the modules that define them, as bundlers load them; "opaque" as an import of the pass-through module. Not for encapsulation.
ignore_imports[]"<selector> -> <selector>" entries: the direct imports between them are left out of the contract.
unmatched_ignore_imports"error"Whether an ignore_imports entry that matches no import breaks the contract ("error"), is a warning ("warn") or is fine ("none").
guidancenoneText shown under each violation, such as how to fix it.