Suppress specific findings with inline comments or JSDoc tags. Useful for false positives or intentional exceptions.
Prefer config-level rules for broad patterns. Use inline suppression sparingly for individual exceptions. For library exports consumed externally, use JSDoc visibility tags (@public, @internal, @beta, @alpha) instead of suppression comments.
Exports annotated with JSDoc visibility tags are never reported as unused. Fallow recognizes @public, @internal, @beta, and @alpha. Use these for library APIs consumed by external projects, or exports intentionally reserved for specific audiences. They have zero internal references but are intentionally part of your API surface.
/** @public */
export function createClient() {
// Not imported anywhere in this repo, but consumed by users of the library
}
/**
* Configuration options for the client.
* @public
*/
export interface ClientOptions {
baseUrl: string;
timeout?: number;
}
/** @internal */
export function resetState() {
// Used by sibling packages in the monorepo, not public API
}
/** @beta */
export function experimentalFeature() {
// Available to early adopters, not yet stable
}
/** @alpha */
export function unstableApi() {
// Early-stage API, subject to breaking changes
}
/** @api public */
export type UserId = string; // TSDoc @api convention also supported
The tags work with all export types: named, default, classes, interfaces, enums, type aliases, and multi-specifier exports (export { foo, bar }).
Only /** */ JSDoc block comments are recognized. Line comments (// @public) and regular block comments (/* @public */) have no effect.
@expected-unused JSDoc tagMark exports as intentionally unused with /** @expected-unused */. Unlike visibility tags (@public, @internal), this tag is tracked for staleness. If the export later becomes imported by another module, fallow reports the tag as a stale suppression.
/** @expected-unused */
export const deprecatedHelper = () => {
// Intentionally kept but not used anywhere
};
Use @expected-unused when you want to:
stale-suppressions rule)The stale-suppressions rule (default: warn) controls the severity of stale @expected-unused tags. See dead code explained for details.
// Suppress all issues on the next line
// fallow-ignore-next-line
export const keepThis = 1;
// Suppress a specific issue type
// fallow-ignore-next-line unused-export
export const keepThisToo = 2;
// Suppress all issues in the entire file
// fallow-ignore-file
// Suppress a specific issue type file-wide
// fallow-ignore-file unused-export
Suppress individual functions from the health command's complexity report. Both cyclomatic and cognitive metrics are suppressed together.
// fallow-ignore-next-line complexity
function* parseCsv(text) {
// intentionally complex parser, not worth refactoring
// ...
}
function processRow(row) {
// this function is NOT suppressed, still appears in health output
// ...
}
File-level suppression excludes all functions in the file from complexity findings (file health scores are unaffected):
// fallow-ignore-file complexity
| Comment | Scope |
|---|---|
// fallow-ignore-next-line | All issues on the next line |
// fallow-ignore-next-line <type> | Specific issue on the next line |
// fallow-ignore-next-line <type>, <type> | Multiple issue types on the next line (comma- or space-separated) |
// fallow-ignore-file | All issues in the file |
// fallow-ignore-file <type> | Specific issue type for the file |
A marker can list more than one token. When one of the tokens does not match a known issue kind (typo, or a kind renamed in a newer fallow release), the recognized tokens still apply and each unknown token surfaces as a stale-suppression finding. fallow includes a "Did you mean ...?" Levenshtein hint when a known kind is within edit distance 2, so an upgrade across a rename never silently breaks the entire marker.
// Recognized + unknown on the same marker:
// fallow-ignore-next-line unused-export, complexity-typo
export const foo = 1;
// `unused-export` still suppresses `foo`; `complexity-typo` surfaces as a stale
// suppression so you can fix the typo or drop the unknown token.
JSON consumers can distinguish unknown-kind tokens from stale-but-known tokens via the additive origin.kind_known field on stale_suppressions[].origin (present and false only in the unknown-kind case; absent when the kind is recognized).
Every suppression comment and @expected-unused tag can carry a trailing -- <reason> explaining why it is there:
// fallow-ignore-next-line unused-export -- public compatibility export
export const legacyHelper = () => {};
// fallow-ignore-file -- generated route map
/** @expected-unused -- kept for the v3 migration */
export const oldApi = () => {};
By default the reason is optional and recorded in suppression hygiene output. Enable the opt-in require-suppression-reason rule (default off) to enforce that every suppression documents itself:
{ "rules": { "require-suppression-reason": "warn" } }
With the rule at warn (or error to fail CI), any fallow-ignore-* comment or @expected-unused tag without a -- <reason> reports as a missing-suppression-reason finding, so you can find and backfill undocumented suppressions across the codebase. See Rules & Severity for the rule reference.
Use these tokens with suppression comments. The Scope column shows whether each type supports line-level (// fallow-ignore-next-line), file-level (// fallow-ignore-file), or both.
| Token | Issue | Scope |
|---|---|---|
unused-file | Unused file | file |
unused-export | Unused export | both |
unused-type | Unused type export | both |
unused-dependency | Unused dependency | file |
unused-dev-dependency | Unused devDependency | file |
unused-enum-member | Unused enum member | both |
unused-class-member | Unused class member | both |
unresolved-import | Unresolved import | both |
unlisted-dependency | Unlisted dependency | both |
duplicate-export | Duplicate export | both |
code-duplication | Code duplication | file |
circular-dependency | Circular dependency | both |
boundary-violation | Boundary violation (covers the whole family: import direction, zone coverage, forbidden calls; boundary-call-violation and boundary-call-violations are accepted aliases) | both |
policy-violation:<pack>/<rule-id> | One rule-pack policy violation. Bare policy-violation still covers every rule-pack rule on the line or file | both |
type-only-dependency | Type-only dependency | file |
test-only-dependency | Test-only dependency | file |
complexity | Function exceeding complexity threshold | both |
coverage-gaps | File with no test dependency path | file |
feature-flag | Detected feature flag pattern | both |
fallow dead-code (dead code analysis):
unused-file, unused-export, unused-type, unused-dependency, unused-dev-dependency, unused-enum-member, unused-class-member, unresolved-import, unlisted-dependency, duplicate-export, circular-dependency, boundary-violation, policy-violation, policy-violation:<pack>/<rule-id>, type-only-dependency, test-only-dependency
fallow health (complexity and coverage):
complexity, coverage-gaps
fallow dupes (code duplication):
code-duplication
fallow flags (feature flag detection):
feature-flag
The circular-dependency and circular-dependencies slugs are interchangeable wherever rule slugs are accepted (inline directives, config rules, overrides[].rules). The singular form is canonical for inline directives, the plural form is canonical for rules config; both are accepted on either surface as aliases.
Prefer config-level solutions (rules, ignoreExports) for broad patterns. Use inline suppression for:
Don't suppress systemic issues. If a pattern affects many files, configure it at the project level instead.
// src/components/Button.tsx
// fallow-ignore-next-line unused-export
export const Button = () => { /* ... */ };
// src/components/Input.tsx
// fallow-ignore-next-line unused-export
export const Input = () => { /* ... */ };
// src/components/Modal.tsx
// fallow-ignore-next-line unused-export
export const Modal = () => { /* ... */ };
// Repeated across dozens of component files...