---
title: "TypeScript pack"
description: "Cards specific to typescript: stable ones are vetted, drafts are not. Use them with the level page that matches your input."
canonical: "https://goodcodereview.com/pack-typescript"
updated: "2026-11-06"
---

# TypeScript pack

Each card says whether it is stable or draft. Stable cards are vetted and also appear in their level file. A draft card is not vetted. Report a `FAIL` of one under Draft findings, at `should-fix` at most, never in the ledger or the verdict. Draft cards never change the verdict.

## typescript-mock-at-the-boundary

severity: should-fix  
concerns: testing, maintainability  
applies_when: A Vitest test calls `vi.mock` on a module of the project's own code.  
Cites: Vitest (unjudged), Testing Library (unjudged). Backing: unjudged.

**Rule.** Fake the system boundary (network, clock, storage), for example with Mock Service Worker; do not `vi.mock` your own modules when a boundary fake would do.

**Why.** A mocked internal module makes the test check the mock. The test then passes while the real wiring is broken, and fails on a harmless refactor.

**FAIL**

```ts
vi.mock('./api/client', () => ({ getUser: vi.fn().mockResolvedValue({ name: 'Ada' }) }));
```

**PASS**

```ts
import { http, HttpResponse } from 'msw';
server.use(http.get('/api/user', () => HttpResponse.json({ name: 'Ada' })));
```

**Counterpoint.** "Some internal seams are slow or nondeterministic." Mock those at their edge (the clock, the random source), not the module that uses them.

## typescript-no-floating-promises

severity: should-fix  
concerns: correctness, reliability  
applies_when: TypeScript code calls an async function or creates a Promise.  
tool gate: "@typescript-eslint/no-floating-promises"  
Cites: typescript-eslint project (unjudged). Backing: unjudged.

**Rule.** Every Promise is awaited, returned, or given a `.catch`; enable `@typescript-eslint/no-floating-promises`.

**Why.** A Promise nobody handles turns a failure into an unhandled rejection, or into work that finishes after the code assumed it was done.

**FAIL**

```ts
async function onSave() {
  saveDraft(doc); // returns a Promise; errors vanish
  closeDialog();
}
```

**PASS**

```ts
async function onSave() {
  await saveDraft(doc);
  closeDialog();
}
```

**Counterpoint.** "Fire-and-forget is intended." Mark it with `void saveDraft(doc).catch(report)` so the choice is visible and failures are still reported.

## typescript-partial-mock-keeps-original

severity: should-fix  
concerns: testing, correctness  
applies_when: A `vi.mock` factory replaces a module that exports error classes, enums or constants the code under test also uses.  
Cites: Vitest (unjudged). Backing: unjudged.

**Rule.** A `vi.mock` factory spreads `await importOriginal()` and overrides only what the test needs; never swap an exported error class for a stub.

**Why.** Code under test does `err instanceof ApiError`. If the mock exports a different or empty class, the check is false and the error path is never exercised.

**FAIL**

```ts
vi.mock('./errors', () => ({ ApiError: class {} , fetchUser: vi.fn() }));
```

**PASS**

```ts
vi.mock('./errors', async (importOriginal) => ({
  ...(await importOriginal<typeof import('./errors')>()),
  fetchUser: vi.fn(),
}));
```

**Counterpoint.** "The real module has side effects on import." Then mock the effect's boundary; a factory that drops half the exports breaks other callers.

## typescript-shared-test-setup

severity: discuss  
concerns: testing, maintainability  
applies_when: Several Vitest test files repeat the same setup (i18n init, MSW server, DOM matchers).  
Cites: Vitest (unjudged). Backing: unjudged.

**Rule.** Put setup that every test file repeats into one file listed in `setupFiles`, not into each test file.

**Why.** Copies drift: one file inits i18n with other resources and a test passes alone but fails in the suite. One file is one place to change.

**FAIL**

```ts
// a.test.tsx, b.test.tsx, c.test.tsx each start with:
import i18n from 'i18next';
i18n.init({ lng: 'en', resources });
```

**PASS**

```ts
// vitest.config.ts
export default defineConfig({ test: { setupFiles: ['./test/setup.ts'] } });
// test/setup.ts runs i18n.init once per test file
```

**Counterpoint.** "A global setup hides what a test needs." Keep the shared file to things every test needs; per-test data stays in the test.

## typescript-strict-mode

severity: should-fix  
concerns: correctness, maintainability  
applies_when: The repository has a `tsconfig.json`.  
tool gate: tsc  
Cites: Microsoft (unjudged). Backing: unjudged.

**Rule.** `tsconfig.json` sets `"strict": true`; switch off a single strict flag only with a written reason.

**Why.** The strict family (null checks, implicit `any`, strict function types) is where most type errors that reach users are caught. Without it, `tsc` passing says little.

**FAIL**

```json
{ "compilerOptions": { "target": "ES2022", "strict": false } }
```

**PASS**

```json
{ "compilerOptions": { "target": "ES2022", "strict": true } }
```

**Counterpoint.** "Migrating a big codebase is too costly." Enable it per flag, or per package with project references, and ratchet; do not leave it off by default for new code.
