@noctcore/eslint-plugin-code-quality
Guard clauses, comment hygiene, test discipline and deterministic time.
What it solves
Section titled “What it solves”A grab bag with one theme: code and tests that stay honest over time. Focused and silently skipped tests, assertions that can never fail, fake timers that leak into the next test, unit tests that hit the real network, comments that narrate or reference a PR number that means nothing in a year, Date.now() scattered through business logic.
What it will yell at you about
Section titled “What it will yell at you about”It will yell at you about tests and comments more than about runtime code. The comment rules (no-narration-comments, no-historical-comments, no-pr-reference-comments) are opinionated and will fire on a lot of existing code; that is the point, but expect a cleanup pass. no-bare-date-now assumes you read time through a shared clock. The test rules match on method names, so they cover Jest, Vitest and Bun alike. It is a bad fit if your team writes explanatory step-by-step comments on purpose. interface-prefix-i and no-template-trim-empty-ternary are exported but left out of the preset: they are house style, not correctness.
Install and configure
Section titled “Install and configure”npm install --save-dev @noctcore/eslint-plugin-code-quality @typescript-eslint/parser# or: bun add -D / pnpm add -D @noctcore/eslint-plugin-code-quality @typescript-eslint/parser// eslint.config.jsimport tsParser from '@typescript-eslint/parser';import codeQuality from '@noctcore/eslint-plugin-code-quality';
export default [ { ...codeQuality.configs.recommended, files: ['**/*.{ts,tsx}'], languageOptions: { parser: tsParser }, },];
// Rules outside the preset are enabled one by one, e.g.// { rules: { 'noctcore-code-quality/interface-prefix-i': 'error' } }The recommended preset enables 14 of 16 rules.
Each rule links to its page, with the options it takes and Incorrect and Correct examples. A good first
read is no-conditional-expect.
| Rule | What it reports | Preset | Fix | Types |
|---|---|---|---|---|
fake-timers-must-be-restored | A test file that calls useFakeTimers() must also call useRealTimers(), so fake timers do not leak into later tests. | error | ||
interface-prefix-i | Interface names must be prefixed with I followed by an uppercase letter. Module/global augmentations are exempt. | not listed | ||
no-bare-date-now | Disallow bare Date.now() / new Date() in business logic. Read wall-clock time through a shared clock util (nowMs() / now()) so time is mockable. | error | ||
no-conditional-expect | Disallow expect() inside a branch, catch or loop that may not run: a skipped assertion lets a broken test pass. | error | ||
no-elided-code-comments | Disallow comments that stand in for elided code ('// ... existing code ...', '// rest of the function unchanged', '// your code here'). They are what an agent leaves when it rewrites a file from an abbreviated draft, and the code they replaced has usually been deleted. | error | ||
no-focused-tests | Ban focused tests (it.only / describe.only / test.only, fdescribe / fit / ddescribe) so a focused test never silently lands in CI. | error | ||
no-historical-comments | Disallow comments that frame code relative to what it used to do or to a past incident ('before the fix', 'after the refactor', 'we used to', 'no longer'). Source comments describe the current invariant; history belongs in the commit message or PR description, where it does not rot when the code changes again. | error | ||
no-narration-comments | Disallow narrative comments like 'Here we...', 'Now we...', 'First, we...'. These read as step-by-step prose and add no information a future reader cannot get from the code itself. Often a tell that the comment was generated by an agent describing its own changes. | error | ||
no-pr-reference-comments | Disallow PR/issue references in comments. They belong in commit messages and PR descriptions, where they do not rot when the repo moves, the issue tracker migrates, or the numbering changes. | error | ||
no-process-exit | Disallow process.exit() outside bootstrap/shutdown paths and standalone CLIs. Application and service code must throw or reject so the lifecycle can shut down gracefully. | error | ||
no-real-network-in-unit-tests | Unit tests must not perform real network I/O: mock the HTTP client, or move the test to an integration suite. | error | ||
no-swallowed-assertion | Disallow assertions inside a try whose catch neither rethrows nor asserts, and .catch() handlers that swallow an expect(...).rejects/.resolves failure: the assertion fails, the error is dropped, and the test passes. | error | ||
no-template-trim-empty-ternary | Disallow inline <template>.trim() === '' ? fallback : <template>.trim() patterns. Extract to a named utility so the expression is built once and is unit-testable in one place. | not listed | ||
no-vacuous-expect | Disallow vacuous expects (typeof checks, literal tautologies, a sole toBeDefined/toBeTruthy): a test must assert behaviour that a real regression would break. | error | ||
prefer-early-return | Prefer guard clauses (early return) over wrapping the whole function body in a multi-statement if without an else. | error | ||
skipped-tests-need-tracking | Skipped tests (.skip / .fixme / xit / xdescribe) must carry a tracking marker (an issue URL or TODO(@owner)) on or above the line, so the debt has an owner instead of rotting silently. | error |
Preset: severity in configs.recommended; off means the preset registers the rule switched off, not listed means it leaves the rule out; both are opt-in, so you turn the rule on yourself. Fix: whether the rule ships an autofix or an editor suggestion. Types: whether the rule needs a type-checked program (parserOptions.projectService).