69 opinionated ESLint rules for TypeScript teams that refuse to compromise on code quality.
Zero tolerance means every rule earns its place. No warnings you learn to ignore. No exceptions you forget about. Every violation is a conversation about quality — and quality always wins.
Most linting setups start strict and erode over time. A scattered eslint-disable here, an any cast there, and before long the rules exist in name only.
This plugin takes the opposite approach:
- No
eslint-disablecomments — fix the root cause, don't silence the symptom. - No
anysmuggling — type assertions and non-null assertions are flagged. - No magic values — every number and string earns a name.
- No leaky tests — persistent mocks, imprecise matchers, and timer abuse are caught.
- No complexity hiding — functions stay short, parameters stay few, imports stay clean.
The result is a codebase where the rules are the culture and the culture is visible in every file.
| Hosted docs | https://coderrob.github.io/eslint-config-zero-tolerance/ |
| Rules reference | docs/rules/index.md |
| Configuration guide | docs/configuration.md |
This monorepo publishes two packages:
| Package | Description |
|---|---|
@coderrob/eslint-plugin-zero-tolerance |
The ESLint plugin — 69 custom rules |
@coderrob/eslint-config-zero-tolerance |
Pre-built recommended and strict config presets |
- ESLint 8.57.0+, 9.x, or 10.x
- TypeScript-ESLint 8.x
- TypeScript 5.x
npm install --save-dev @coderrob/eslint-plugin-zero-tolerance @typescript-eslint/parserUsing the recommended preset:
// eslint.config.js
import zeroTolerance from '@coderrob/eslint-plugin-zero-tolerance';
export default [
zeroTolerance.configs.recommended,
// your other configs...
];Using the strict preset:
// eslint.config.js
import zeroTolerance from '@coderrob/eslint-plugin-zero-tolerance';
export default [
zeroTolerance.configs.strict,
// your other configs...
];Alternative: Import presets directly from the config package:
// eslint.config.js
import recommended from '@coderrob/eslint-config-zero-tolerance/recommended';
// or
import strict from '@coderrob/eslint-config-zero-tolerance/strict';
export default [
recommended, // or strict
// your other configs...
];Custom configuration:
// eslint.config.js
import zeroTolerance from '@coderrob/eslint-plugin-zero-tolerance';
export default [
{
plugins: {
'zero-tolerance': zeroTolerance,
},
rules: {
'zero-tolerance/require-interface-prefix': 'error',
'zero-tolerance/no-throw-literal': 'error',
'zero-tolerance/max-function-lines': ['warn', { max: 40 }],
// ... other rules
},
},
];Using .eslintrc.js:
module.exports = {
plugins: ['zero-tolerance'],
extends: ['plugin:zero-tolerance/legacy-recommended'],
// or for strict mode:
// extends: ['plugin:zero-tolerance/legacy-strict'],
};The plugin ships 69 rules across 8 categories. The grouped catalog below is exhaustive and links every rule to its dedicated documentation page.
Preset legend:
Both= enabled by bothrecommendedandstrictStrict only= enabled only bystrictOpt-in= not enabled by either preset
| Category | Rules | Focus |
|---|---|---|
| Naming Conventions | 1 | Interface naming standards |
| Documentation | 5 | JSDoc, BDD specs, optional chaining, readonly props |
| Testing | 8 | Test descriptions, mocks, timers, fetch, interfaces |
| Type Safety | 11 | Assertions, unions, imports, exported types |
| Code Quality | 15 | Function size, magic values, immutability, sorting |
| Error Handling | 3 | Throw safety, empty catches, Result patterns |
| Imports | 8 | Barrels, re-exports, dynamic imports, node protocol |
| Bug Prevention | 18 | Identical code, control flow, async safety |
| Rule | Type | Preset | Description |
|---|---|---|---|
require-interface-prefix |
suggestion |
Both | Enforce that interface names start with "I" |
| Rule | Type | Preset | Description |
|---|---|---|---|
require-bdd-spec |
suggestion |
Opt-in | Enforce that every TypeScript source file has a valid sibling .ts.bdd.json BDD spec file |
require-jsdoc-anonymous-functions |
suggestion |
Strict only | Require JSDoc comments on anonymous function-like constructs except in test files and known test callbacks |
require-jsdoc-functions |
suggestion |
Both | Require JSDoc comments on all functions and require @param/@returns/@throws tags when applicable (except in test files) |
require-optional-chaining |
suggestion |
Both | Require optional chaining instead of repeated logical guard access |
require-readonly-props |
suggestion |
Both | Require readonly typing for JSX component props |
| Rule | Type | Preset | Description |
|---|---|---|---|
require-test-description-style |
suggestion |
Both | Enforce that test descriptions start with "should" |
no-jest-have-been-called |
suggestion |
Both | Prohibit toBeCalled, toHaveBeenCalled, toBeCalledWith, toHaveBeenCalledWith, toHaveBeenLastCalledWith, and toLastCalledWith; use toHaveBeenCalledTimes with an explicit call count and toHaveBeenNthCalledWith with an explicit nth-call index and arguments instead |
no-mock-implementation |
suggestion |
Both | Prohibit persistent mock implementations; use the Once variants to avoid test bleeds |
no-set-timeout-in-tests |
suggestion |
Both | Disallow setTimeout usage in test files |
no-set-interval-in-tests |
suggestion |
Both | Disallow setInterval usage in test files |
no-fetch-in-tests |
suggestion |
Opt-in | Disallow fetch usage in test files |
no-restricted-imports-in-tests |
suggestion |
Opt-in | Disallow restricted dependency imports in test files |
no-test-interface-declaration |
suggestion |
Both | Disallow interface declarations in test files; import production types instead |
| Rule | Type | Preset | Description |
|---|---|---|---|
no-type-assertion |
suggestion |
Both | Prevent use of TypeScript "as" type assertions |
no-non-null-assertion |
problem |
Both | Disallow non-null assertions using the "!" postfix operator |
no-explicit-any |
problem |
Both | Disallow explicit any; model unknown values precisely and narrow them explicitly |
no-indexed-access-types |
problem |
Both | Disallow TypeScript indexed access types |
no-literal-unions |
suggestion |
Both | Ban literal unions in favor of enums |
no-literal-property-unions |
suggestion |
Both | Require property literal unions to use named domain types |
no-inline-type-import |
problem |
Both | Disallow TypeScript inline type imports using import("...") |
no-return-type |
problem |
Both | Disallow TypeScript ReturnType utility usage |
require-union-type-alias |
suggestion |
Both | Require inline union types with multiple type references to be extracted into named type aliases |
no-destructured-parameter-type-literal |
suggestion |
Both | Disallow inline object type literals on destructured parameters; require a named type instead |
require-exported-object-type |
suggestion |
Both | Require exported object constants to declare an explicit type annotation |
| Rule | Type | Preset | Description |
|---|---|---|---|
max-function-lines |
suggestion |
Both | Enforce a maximum number of lines per function body |
max-params |
suggestion |
Both | Enforce a maximum number of function parameters |
no-array-mutation |
suggestion |
Both | Disallow mutating array methods; prefer immutable alternatives such as spread, slice, and toSorted |
no-date-now |
suggestion |
Both | Disallow Date.now() and new Date(); prefer injected clocks for deterministic behavior |
no-magic-numbers |
suggestion |
Both | Disallow magic numbers; use named constants instead of raw numeric literals |
no-magic-strings |
suggestion |
Both | Disallow magic strings in comparisons and switch cases; use named constants instead |
no-map-set-mutation |
suggestion |
Both | Disallow direct Map and Set mutation methods; rebuild collections instead of mutating them in place |
no-object-mutation |
suggestion |
Both | Disallow direct object-property mutation; prefer creating new objects with immutable update patterns |
sort-imports |
suggestion |
Both | Require import declarations to be grouped (side-effect -> builtin -> external -> parent -> peer -> index) and sorted alphabetically within each group |
sort-functions |
suggestion |
Both | Require top-level functions to be sorted alphabetically |
prefer-nullish-coalescing |
suggestion |
Both | Prefer nullish coalescing instead of a nullish guard ternary |
prefer-object-spread |
suggestion |
Both | Enforce object spread syntax instead of Object.assign with an empty object literal as the first argument |
prefer-readonly-parameters |
suggestion |
Both | Prefer readonly typing for object and array-like parameters to prevent accidental mutation of inputs |
prefer-string-raw |
suggestion |
Both | Prefer String.raw for string literals containing escaped backslashes (Sonar S7780) |
prefer-structured-clone |
suggestion |
Both | Prefer structuredClone(...) over JSON.parse(JSON.stringify(...)) when creating a deep clone |
| Rule | Type | Preset | Description |
|---|---|---|---|
no-empty-catch |
problem |
Both | Disallow empty catch blocks that silently swallow errors |
no-throw-literal |
problem |
Both | Disallow throwing literals, objects, or templates; always throw a new Error instance |
prefer-result-return |
suggestion |
Strict only | Prefer Result-style return values instead of throw statements to make error flows explicit and composable |
| Rule | Type | Preset | Description |
|---|---|---|---|
require-clean-barrel |
suggestion |
Both | Require barrel files (index.*) to contain only module re-export declarations |
require-barrel-relative-exports |
suggestion |
Both | Require barrel re-export declarations to use current-directory descendant paths that start with './' |
no-dynamic-import |
problem |
Both | Ban await import() and require() except in test files |
no-export-alias |
suggestion |
Both | Prevent use of alias in export statements |
no-barrel-parent-imports |
suggestion |
Both | Disallow parent-directory imports (.. and ../*) inside barrel files (index.*) across import declarations, import expressions, require calls, and import-equals declarations |
no-parent-internal-access |
suggestion |
Opt-in | Disallow parent-relative access into protected internal directories such as src |
no-re-export |
suggestion |
Both | Disallow direct or indirect re-export statements from parent or ancestor modules; barrel files (index.*) are exempt from this restriction |
require-node-protocol |
suggestion |
Both | Require Node.js built-in module imports to use the node: protocol prefix |
| Rule | Type | Preset | Description |
|---|---|---|---|
no-identical-expressions |
problem |
Both | Disallow identical expressions on both sides of a binary or logical operator (Sonar S1764) |
no-identical-branches |
suggestion |
Both | Disallow identical if/else and conditional-expression branches; consolidate duplicate conditional fragments |
no-boolean-return-trap |
suggestion |
Both | Disallow ambiguous boolean-return APIs; prefer predicate naming or richer result types for clearer call sites |
no-redundant-boolean |
suggestion |
Both | Disallow redundant comparisons to boolean literals (Sonar S1125) |
no-for-in |
problem |
Both | Disallow for..in loops; use Object.keys/values/entries to avoid prototype-chain iteration |
no-labels |
problem |
Both | Disallow labels because they make control flow harder to reason about |
no-with |
problem |
Both | Disallow with statements because they make scope resolution unpredictable |
no-await-in-loop |
problem |
Both | Disallow await expressions inside loops; use Promise.all() for parallel execution |
no-floating-promises |
problem |
Both | Disallow floating promises; explicitly handle with await, void, or rejection handlers |
no-math-random |
problem |
Both | Disallow Math.random(); inject randomness explicitly or use a dedicated random source |
no-eslint-disable |
suggestion |
Both | Prevent use of eslint-disable comments |
no-parameter-reassign |
suggestion |
Both | Disallow assignments and updates to function parameters; use a new local variable instead |
no-process-env-outside-config |
problem |
Both | Disallow process.env reads outside configuration modules; import typed config instead |
no-flag-argument |
suggestion |
Both | Disallow boolean flag arguments in function declarations; prefer explicit methods or command objects |
prefer-guard-clauses |
suggestion |
Both | Prefer guard clauses by disallowing else blocks when the if branch already terminates control flow |
prefer-shortcut-return |
suggestion |
Both | Prefer shortcut boolean returns by replacing if/return true-false patterns with direct return expressions |
no-query-side-effects |
suggestion |
Both | Disallow side effects in query-style functions (get*/is*/has*/can*/should*); separate query from modifier |
require-exhaustive-switch |
suggestion |
Both | Require exhaustive switch statements over finite union, enum, and boolean discriminants |
This repository itself is a
pnpmworkspace and dogfoods its own rules througheslint.config.mjs. Every source file in the plugin must pass the same rules it enforces on consumers.
pnpm installpnpm buildpnpm testCoverage gates are enforced per-file at 95% minimum across statements, branches, functions, and lines. The test command also refreshes the coverage badge automatically.
pnpm readme:sync
pnpm validate:readmeThe root README.md rule catalog is generated from deterministic metadata in scripts/metadata/readme-rule-catalog.json, canonical rule source metadata, preset metadata from packages/plugin/src/rules/support/rule-map.ts, and rule docs page existence checks.
pnpm --filter @coderrob/eslint-plugin-zero-tolerance exec tsc -p tsconfig.json --noEmit
pnpm --filter @coderrob/eslint-config-zero-tolerance exec tsc -p tsconfig.json --noEmitpnpm deps:graph
pnpm deps:circularThis monorepo provides automated scripts to handle versioned releases.
Quick release (single command):
pnpm release:prepare --release patch --commit --tag --publishThis will:
- bump the root, plugin, and config package versions
- replace
workspace:*with a versioned peer dependency inpackages/config - run
pnpm buildandpnpm test - create a release commit and annotated git tag
- publish both packages to npm
If you want to restore workspace:* after publishing for local development, run:
pnpm release:restore-workspaceOr include --restore-workspace and commit that restoration separately.
Manual/stepwise release flow:
# 1. Build all packages
pnpm build
# 2. Run tests to ensure everything works
pnpm test
# 3. Prepare packages for publishing (converts workspace:* to versioned dependency)
pnpm release:prepare
# 4. Publish the plugin package
cd packages/plugin
npm publish
# 5. Publish the config package
cd ../config
npm publish
# 6. Restore workspace:* for local development
cd ../..
pnpm release:restore-workspaceAdditional release:prepare options:
# Bump versions and prepare manifests without publishing
pnpm release:prepare --release minor
# Skip build/test if already run in CI or a previous step
pnpm release:prepare --release 1.2.0 --skip-build --skip-test --commit --tag --publish
# Dry run the full flow
pnpm release:prepare --release patch --commit --tag --publish --dry-runApache 2.0 Copyright Robert Lindley