Thank you for contributing to @coderrob/eslint-plugin-zero-tolerance! This guide walks you through the environment setup, the process for adding a new rule, and the release workflow.
| Tool | Version |
|---|---|
| Node.js | 20+ |
| pnpm | 10+ |
| Python | 3.x (only if working on the docs site) |
# Clone the repository
git clone https://github.com/Coderrob/eslint-config-zero-tolerance.git
cd eslint-config-zero-tolerance
# Install all workspace dependencies
pnpm installUse pnpm install for the workspace root. npm install is supported for consumers installing the published packages into their own projects, but not for bootstrapping this monorepo.
# Build all packages (plugin + config)
pnpm build
# Build a single package
pnpm --filter @coderrob/eslint-plugin-zero-tolerance build# Run all tests across the workspace
pnpm test
# Run tests for the plugin only (in watch mode during development)
pnpm --filter @coderrob/eslint-plugin-zero-tolerance test -- --watch
# Run a single plugin test file with focused coverage
pnpm --filter @coderrob/eslint-plugin-zero-tolerance test -- sort-imports.test.tsThis repository dogfoods its own rules. All source code in packages/ must pass:
pnpm build && pnpm lintKey conventions enforced by the plugin and documented in AGENTS.md:
- Interface names must start with
I(require-interface-prefix) - Named non-test functions must have a JSDoc comment (
require-jsdoc-functions) - Anonymous non-test function-like constructs must have a JSDoc comment (
require-jsdoc-anonymous-functions) - Barrel files (
index.*) must contain only module re-export declarations (require-clean-barrel) - Barrel re-export declarations must target descendant-relative
./paths (require-barrel-relative-exports) - Import declarations must be ordered by group (side-effect -> builtin -> external -> parent -> peer -> index) and alphabetically within each group (
sort-imports) - No parent-relative re-exports (
no-re-export) - No
eslint-disablecomments — fix the underlying issue (no-eslint-disable)
Rule naming is validated automatically by pnpm validate:rules. It verifies the rule filename, exported camelCase constant, createRule({ name: ... }) value, default export, sibling test/BDD/docs filenames, and plugin registration all stay aligned.
README synchronization is also automated. pnpm readme:sync regenerates the root README.md rule catalog from deterministic metadata and rule source metadata, while pnpm validate:readme fails if the generated output would differ from the checked-in README.
Follow these steps in order. Each step has a concrete example using a hypothetical no-foo rule.
packages/plugin/src/rules/no-foo.ts
Use the template below. The rule creator URL must stay in sync with the GitHub repo URL.
import { ESLintUtils } from '@typescript-eslint/utils';
const createRule = ESLintUtils.RuleCreator(
(name) => `https://github.com/Coderrob/eslint-config-zero-tolerance#${name}`,
);
/**
* Prevents use of foo() which is banned because it does X.
*/
export const noFoo = createRule({
name: 'no-foo',
meta: {
type: 'problem', // 'problem' | 'suggestion' | 'layout'
docs: {
description: 'Disallow use of foo()',
recommended: 'recommended',
},
messages: {
noFoo: 'foo() is not allowed; use bar() instead',
},
schema: [],
},
defaultOptions: [],
create(context) {
return {
CallExpression(node) {
if (node.callee.type === 'Identifier' && node.callee.name === 'foo') {
context.report({ node, messageId: 'noFoo' });
}
},
};
},
});
export default noFoo;packages/plugin/src/rules/no-foo.test.ts
import { RuleTester } from '@typescript-eslint/rule-tester';
import noFoo from './no-foo';
const ruleTester = new RuleTester();
ruleTester.run('no-foo', noFoo, {
valid: [{ name: 'should allow bar()', code: 'bar();' }],
invalid: [
{
name: 'should disallow foo()',
code: 'foo();',
errors: [{ messageId: 'noFoo' }],
},
],
});Test requirements:
- All test names must start with
should - Provide both
validandinvalidcases - Cover all code paths (target ≥ 95% coverage)
In packages/plugin/src/index.ts, add the import and register the rule in all four presets:
import noFoo from './rules/no-foo';
const rules = {
// ...existing rules
'no-foo': noFoo,
};
// Add to recommendedConfig, strictConfig, legacyRecommendedConfig, legacyStrictConfig:
'zero-tolerance/no-foo': 'warn', // recommended
'zero-tolerance/no-foo': 'error', // strictIn packages/config/src/recommended.ts and packages/config/src/strict.ts, add the rule at the appropriate severity. In packages/config/src/index.ts add it to both the flat and legacy exports.
Create docs/rules/no-foo.md following the structure of an existing rule page (rationale, ✅ correct examples, ❌ incorrect examples, options table, config snippet).
Register the page in the nav section of mkdocs.yml under the appropriate category.
Add an entry under ## [Unreleased] in CHANGELOG.md:
- **`no-foo` rule**: Short description of what it enforces and why.pnpm validate:readme # root README must match generated rule metadata
pnpm validate:rules # rule filenames, exports, docs, and registration must stay aligned
pnpm test # all tests must pass
pnpm build # TypeScript must compile cleanlyTo preview the documentation site locally:
pip install mkdocs-material
mkdocs serveThe site will be available at http://localhost:8000. The --strict flag is used in CI to treat warnings as errors:
mkdocs build --strict- One logical change per PR (one new rule, one bug fix, one refactor)
- All tests must pass
- The TypeScript build must succeed
- The changelog must be updated under
[Unreleased] - The docs must include a rule page if a new rule is added
See the Release Process section of AGENTS.md for the full versioned release workflow.