Skip to content
Fallow home
All docs pages

Rules & severity

Adopt fallow in CI one issue type at a time. Set each issue type to error, warn, or off to decide what fails the build.

Rules let you decide which findings fail CI. You set a severity per issue type, so you can adopt fallow one issue type at a time.

Start with all rules as warn. Promote one type at a time to error when you have fixed its findings.

Severity levels

LevelBehavior
errorReport and fail CI (non-zero exit code)
warnReport but exit 0
offDo not detect or report

When you do not configure a rule, it uses its default severity. The Default column in Available rule names shows the default of each rule. The defaults by group:

  • Default off (opt-in): private-type-leaks, deprecated-exports-in-use, prop-drilling, thin-wrapper, duplicate-prop-shape, coverage-gaps, feature-flags, require-suppression-reason, security-client-server-leak, and security-sink.
  • Default warn:
    • Dependency hygiene: unused-dev-dependencies, unused-optional-dependencies, type-only-dependencies, test-only-dependencies, and dev-dependencies-in-production.
    • Framework components: unused-store-members, unprovided-injects, unrendered-components, unused-component-props, unused-component-emits, unused-component-inputs, unused-component-outputs, unused-svelte-events, unused-server-actions, and unused-load-data-keys.
    • Client and server directives: invalid-client-export, mixed-client-server-barrel, and misplaced-directive.
    • Styling: css-token-drift, css-duplicate-block, css-selector-complexity, css-dead-surface, and css-broken-reference.
    • pnpm cleanup: unused-catalog-entries, empty-catalog-groups, and unused-dependency-overrides.
    • Other: re-export-cycle, package-cycle, stale-suppressions, and policy-violation.
  • Default error: all other rules.

To print the resolved severity of every rule for your project, run fallow config.

In production mode, fallow sets unused-dev-dependencies and unused-optional-dependencies to off.

Configuration

{
  "rules": {
    "unused-files": "error",
    "unused-exports": "warn",
    "unused-types": "off",
    "private-type-leaks": "off",
    "unused-dependencies": "error",
    "unused-dev-dependencies": "warn",
    "unused-optional-dependencies": "warn",
    "unused-enum-members": "warn",
    "unused-class-members": "off",
    "unresolved-imports": "error",
    "unlisted-dependencies": "error",
    "duplicate-exports": "warn",
    "circular-dependencies": "error",
    "re-export-cycle": "warn",
    "boundary-violation": "error",
    "type-only-dependencies": "error",
    "test-only-dependencies": "warn",
    "dev-dependencies-in-production": "warn",
    "coverage-gaps": "off",
    "stale-suppressions": "warn"
  }
}

Available rule names

RuleDefaultDescription
unused-fileserrorFiles not reachable from any entry point
unused-exportserrorExported symbols never imported
unused-typeserrorType aliases and interfaces never referenced
private-type-leaksoffOpt-in API hygiene check. Finds exported signatures that reference a private type from the same file
deprecated-exports-in-useoffOpt-in. An export with a @deprecated JSDoc tag that a reachable file still uses. See Deprecated exports in use
unused-dependencieserrorPackages in dependencies never used
unused-dev-dependencieswarnPackages in devDependencies never used
unused-optional-dependencieswarnPackages in optionalDependencies never used
unused-enum-memberserrorEnum values never referenced
unused-class-memberserrorClass members never referenced
unused-store-memberswarnPinia store members (state, getters, actions) that no code accesses. Runs only when pinia or @pinia/nuxt is a dependency
unprovided-injectswarninject() or getContext() reads a key that no provide() or setContext() supplies
unrendered-componentswarnA Vue or Svelte component that a barrel exports but that no code renders
unused-component-propswarnA Vue, Svelte, or React component prop that its own component never references. See unusedComponentProps
unused-component-emitswarnA Vue <script setup> defineEmits event that its own component never emits
unused-component-inputswarnAn Angular @Input(), signal input(), or model() input that its own component never reads
unused-component-outputswarnAn Angular @Output() or signal output() output that its own component never emits
unused-svelte-eventswarnA Svelte createEventDispatcher event that no code in the project listens to
unused-server-actionswarnA Next.js Server Action, exported from a "use server" file, that no code references
unused-load-data-keyswarnA key of a SvelteKit load() return object that no consumer reads
prop-drillingoffA React or Preact prop that passes unchanged through 3 or more components to a distant consumer
thin-wrapperoffA React or Preact component whose whole body renders one child with spread props (a candidate for inlining)
duplicate-prop-shapeoffThree or more React or Preact components, in two or more files, that declare the same set of prop names (a missing shared Props type)
css-token-driftwarnA hardcoded styling value in CSS or CSS-in-JS that bypasses the design tokens
css-duplicate-blockwarnA CSS or CSS-in-JS declaration block that repeats across rules
css-selector-complexitywarnA structurally complex CSS selector, nesting depth, or !important use
css-dead-surfacewarnCSS or CSS-in-JS surface that no code appears to use
css-broken-referencewarnA CSS or CSS-in-JS reference that resolves to no stylesheet definition
unused-catalog-entrieswarnEntries in the catalog: or catalogs: maps of pnpm-workspace.yaml that no workspace package references with the catalog: protocol
empty-catalog-groupswarnNamed catalogs.<name>: groups in pnpm-workspace.yaml that contain no package entries (top-level catalog: placeholders are ignored)
unresolved-catalog-referenceserrorpackage.json references to catalog: or catalog:<name> where the catalog does not declare the package. pnpm install would fail
unused-dependency-overrideswarnEntries in pnpm-workspace.yaml#overrides or package.json#pnpm.overrides for a package that no workspace package declares and that is not in pnpm-lock.yaml. Without a lockfile, fallow checks the manifests only and sets the hint field on these less certain findings
misconfigured-dependency-overrideserrorpnpm.overrides entries with a key that fallow cannot parse or with no value. pnpm install would fail
unresolved-importserrorImports that fallow cannot resolve
unlisted-dependencieserrorImported packages not in package.json
duplicate-exportserrorSame symbol exported from multiple files
circular-dependencieserrorModules that import each other, directly or transitively
re-export-cyclewarnBarrel files that re-export from each other in a loop (kind: "multi-node"), or one barrel that re-exports from itself (kind: "self-loop"). A re-export cycle does nothing and is almost always a bug. circular-dependencies covers runtime import loops instead. The cycle spans multiple files, so a per-file overrides.rules.re-export-cycle has no effect. Use the project-wide rules block, or // fallow-ignore-file re-export-cycle on any file in the cycle. Fallow also accepts the aliases re-export-cycles, reexport-cycle, and reexport-cycles.
package-cyclewarnWorkspace packages that import each other in a loop, so that they cannot be built in dependency order. Built from resolved imports between packages, not from declared dependencies. Imports from test, spec, story, fixture, and tooling config files do not count. Fallow also accepts the alias package-cycles. See Package cycles
boundary-violationerrorImports that cross user-defined architecture zone boundaries
policy-violationwarnCalls, imports, or catalogue-derived effects that a rule pack bans (the rulePacks config key). The default is warn, so a new pack does not fail CI on its first run. A per-rule severity in the pack overrides this value for each finding, and the exit code uses that per-finding severity. off turns off all rule-pack checks.
invalid-client-exportwarnA "use client" file that exports a server-only or route-config name
mixed-client-server-barrelwarnA barrel that re-exports both a "use client" module and a server-only module
misplaced-directivewarnA "use client" or "use server" directive that is not in the leading position and therefore has no effect
complexity-cyclomaticerrorA function above the cyclomatic ceiling (health.maxCyclomatic or a thresholdOverrides entry). See Complexity rules
complexity-cognitiveerrorA function above the cognitive ceiling (health.maxCognitive or a thresholdOverrides entry)
complexity-craperrorA function above the CRAP ceiling (health.maxCrap or a thresholdOverrides entry)
route-collisionerrorTwo or more Next.js App Router route files that resolve to the same URL
dynamic-segment-name-conflicterrorSibling Next.js dynamic route segments that use different slug names at the same position
type-only-dependencieswarnProduction dependencies that are only imported with import type
test-only-dependencieswarnProduction dependencies that only test files import
dev-dependencies-in-productionwarnPackages in devDependencies that production code imports at runtime. Move them to dependencies
coverage-gapsoffRuntime files and exports that no test reaches through the import graph
feature-flagsoffDetected feature flag patterns. See fallow flags
security-client-server-leakoffClient-bound code that reaches a non-public environment variable read. See fallow security. overrides[].rules can set it per path. See Per-path severity
security-sinkoffSyntactic security sink candidates that need verification. See fallow security. overrides[].rules can set it per path. See Per-path severity
stale-suppressionswarnfallow-ignore comments or @expected-unused JSDoc tags that no longer match any issue
require-suppression-reasonoffOpt-in suppression hygiene check. When you enable it, every fallow-ignore-* comment and @expected-unused tag must end with -- <reason>. Fallow reports a suppression without a reason as missing-suppression-reason

circular-dependencies and circular-dependency are the same rule. You can use either form in rules, overrides[].rules, and inline directives.

Complexity rules

The rules complexity-cyclomatic, complexity-cognitive, and complexity-crap decide if a complexity finding fails fallow health and fallow audit. The thresholds still decide which functions have a finding. The thresholds are health.maxCyclomatic, health.maxCognitive, health.maxCrap, and health.thresholdOverrides.

Rule valueResult
error (default)The finding fails the run. The exit code and the verdict do not change from earlier releases.
warnThe finding stays in every output format. fallow health exits 0, and the audit verdict is warn.
offFallow does not report the finding.
{
  "rules": {
    "complexity-cyclomatic": "error",
    "complexity-cognitive": "warn",
    "complexity-crap": "warn"
  },
  "overrides": [
    { "files": ["src/legacy/**"], "rules": { "complexity-cyclomatic": "warn" } }
  ]
}
  • A function above more than one threshold gets the most severe rule of those kinds. Fallow drops the finding only when all of these rules are off.
  • overrides[].rules can set the complexity rules for some files.
  • The rule applies before fallow health --min-severity. So a warn finding never fails the run, also when its band is at or above the --min-severity level.
  • --fail-on-issues (and --ci, which sets it) raises a warn complexity finding to error in fallow health and in the bare fallow run. fallow audit ignores --fail-on-issues and keeps its own verdict.
  • health.maxCrap: 0 stays the full off switch for CRAP. It also removes the CRAP signals of the file scores. complexity-crap: "off" hides the findings only, and keeps these signals.
  • After you set a kind to off, save the health baseline again. Its entries for the hidden findings no longer match, so they count as stale.
  • A thresholdOverrides row still lists the dimensions that its function breaches, also when a rule hides the finding.

Each complexity finding in the JSON output has an optional effective_severity field (error or warn). The severity field keeps the band (moderate, high, or critical).

A fallow version without these rules warns about an unknown rule name and ignores it. A shared config therefore works with an older binary, but its complexity findings still fail the run there.

Levels in CI formats

The CI formats take the level of each finding from its rule severity. This includes the severity that an overrides[].rules entry sets for the file. For a complexity finding, the complexity-* rule sets the level, not the band. The band stays in the title and the message.

Rule severitygithub-annotationsSARIFCodeClimate
error::errorerrormajor
warn::warningwarningminor

With the default rules, a moderate complexity finding is an ::error annotation and a SARIF error, because the finding fails the run. Error annotations come before warnings, so the max-annotations cap of the GitHub Action keeps the errors.

Each dead-code and complexity finding in the JSON output has the optional effective_severity field. fallow report --from reads it, so a saved report gives the same levels as the direct run. A saved report from an older version has no effective_severity. For such a report, report --from uses the earlier levels.

prop-drilling, thin-wrapper, and duplicate-prop-shape findings never fail the run. Their SARIF level is warning at most, also when the rule is error. The human, json, sarif, compact, and markdown reports list these findings, and the editor shows them as hints. The pull request formats (codeclimate, github-annotations, github-summary, and the comment and review formats) do not list them.

Incremental adoption strategy

In an existing codebase, adopt fallow in steps:

  1. Start permissive. Set all rules to warn so you see issues without blocking CI.
  2. Fix high-confidence rules first. Promote unresolved-imports and unlisted-dependencies to error. These rules almost never give false positives.
  3. Fix unused files. Promote unused-files to error. You can safely remove unreachable files.
  4. Fix unused exports. This is usually the largest category. Promote unused-exports to error when the count is manageable.
  5. Enable the remaining rules. Promote unused-types, unused-enum-members, and unused-class-members as you clean up each category. Enable private-type-leaks only for packages where the public API or the declaration files matter.

Use baselines to enforce rules on new code without failing CI on existing issues.

To treat all warnings as errors for one run, pass --fail-on-issues. The config file stays the same. Use it for pre-merge gates on critical branches. In fallow health and the bare fallow run, it also raises warn complexity findings to error.

fallow dead-code --fail-on-issues

Exit codes

CodeMeaning
0No error-severity issues
1Error-severity issues found
2Fatal error (invalid config, parse failure)

When all findings of a run are warn, the run exits 0 and the human summary line shows a yellow ⚠. A red ✗ shows only when a gate fails the run, for example an error finding or --fail-on-issues.

A source file that fallow cannot parse does not change the exit code by default. To fail the run on such a file, see failOnParseError.

See also