Skip to content

@noctcore/eslint-plugin-code-quality

Guard clauses, comment hygiene, test discipline and deterministic time.

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.

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
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.js
// eslint.config.js
import 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.

RuleWhat it reportsPresetFixTypes
fake-timers-must-be-restoredA test file that calls useFakeTimers() must also call useRealTimers(), so fake timers do not leak into later tests.error
interface-prefix-iInterface names must be prefixed with I followed by an uppercase letter. Module/global augmentations are exempt.not listed
no-bare-date-nowDisallow 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-expectDisallow expect() inside a branch, catch or loop that may not run: a skipped assertion lets a broken test pass.error
no-elided-code-commentsDisallow 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-testsBan focused tests (it.only / describe.only / test.only, fdescribe / fit / ddescribe) so a focused test never silently lands in CI.error
no-historical-commentsDisallow 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-commentsDisallow 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-commentsDisallow 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-exitDisallow 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-testsUnit tests must not perform real network I/O: mock the HTTP client, or move the test to an integration suite.error
no-swallowed-assertionDisallow 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-ternaryDisallow 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-expectDisallow vacuous expects (typeof checks, literal tautologies, a sole toBeDefined/toBeTruthy): a test must assert behaviour that a real regression would break.error
prefer-early-returnPrefer guard clauses (early return) over wrapping the whole function body in a multi-statement if without an else.error
skipped-tests-need-trackingSkipped 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).