tsconfig-doctor
v0.1.0
Published
Audit your tsconfig.json for strictness and footguns — resolves the extends chain, scores the effective config, and flags missing safety flags (noUncheckedIndexedAccess…) plus contradictions (module/moduleResolution mismatch, allowJs without checkJs). Det
Maintainers
Readme
🩺 tsconfig-doctor
Score your tsconfig's strictness and catch its footguns — extends chain and all.
Your tsconfig.json says "strict": true, so you're safe — right? Except
noUncheckedIndexedAccess (the flag that makes arr[i] return T | undefined)
isn't part of strict, your module / moduleResolution are a mismatched
pair that mis-resolves ESM imports, allowJs is on but checkJs is off so your
JS is never type-checked, and the strict flag you think you have is actually
turned back off three files up the extends chain.
TypeScript has 100+ compiler options and no tool that grades your config.
tsconfig-doctor resolves the whole extends chain into the effective config,
scores its strictness, and flags every missing safety flag and contradiction —
locally, deterministically, with the exact fix.
npx tsconfig-doctor scantsconfig.json 53/100 (F) · strictness 4%
✗ `strict` mode is off strict
⚠ noUncheckedIndexedAccess is off noUncheckedIndexedAccess
⚠ L6 Modern `module` with legacy `moduleResolution` footgun.module-resolution
⚠ L7 allowJs is on but checkJs is off footgun.allowjs-no-checkjs
ℹ L4 Legacy compile target (es5) footgun.legacy-targetWhy tsconfig-doctor?
- 🧮 It grades your config, not a preset.
@tsconfig/strictestis a preset to copy;tsc --showConfigdumps the effective config with no judgment. Neither tells you which safety flags you're missing or which settings contradict. - 🔗 It follows
extends. Relative paths,extendsarrays (TS 5.0+), and packages like@tsconfig/strictest— resolved throughnode_modulesinto one effective config, so it catches a strict flag disabled in a base you inherited. - 🎯 Every finding has the exact fix. Not "consider strictness" — the precise option to add and why it matters (with the failure it prevents).
- 🔒 Local & deterministic. No network, no API key. Drop it in CI and the build
fails the moment a PR weakens
strictor dropsnoUncheckedIndexedAccess.
Why not ask an LLM "is my tsconfig strict"? It can't resolve your real extends
chain across node_modules, can't run on every PR, and option interactions
(strict → 8 flags, module/moduleResolution pairing) are exact rules, not a
guess.
Install
# run it now, from your project root
npx tsconfig-doctor scan
# or add it
npm install -g tsconfig-doctor # global CLI
npm install -D tsconfig-doctor # CI dependencyNode ≥ 18. With no arguments it audits ./tsconfig.json.
Quick start
tsconfig-doctor scan # audit ./tsconfig.json
tsconfig-doctor scan packages/app/tsconfig.json # a specific file
tsconfig-doctor scan --min-score 80 # CI gate
tsconfig-doctor scan --md tsconfig-report.md # Markdown for a PR comment
tsconfig-doctor scan --allow noUnusedLocals,noUnusedParameters
tsconfig-doctor init # write a configSee examples/sample-report.md, and
examples/strict.tsconfig.json for a config that
scores an A.
What it checks
| Group | Examples |
| ----- | -------- |
| Strictness | strict and its 8 family flags (strictNullChecks, noImplicitAny, …) — including any you disabled under strict |
| Extra safety | noUncheckedIndexedAccess (the big one strict omits), noImplicitReturns, noFallthroughCasesInSwitch, noImplicitOverride, exactOptionalPropertyTypes, unused locals/params |
| Interop & modules | esModuleInterop, forceConsistentCasingInFileNames, isolatedModules, verbatimModuleSyntax |
| Footguns | module/moduleResolution mismatch, allowJs without checkJs, noEmit with declaration/composite, composite without declaration, verbatimModuleSyntax vs deprecated flags, legacy target |
Each finding is a weighted error / warning / info; the file rolls up to a 0–100 score, an A–F grade, and a strictness % (how much of the recommended rubric weight you've achieved).
Real scenarios
1. Stop strictness regressions in CI. A PR that flips strict off, or disables
strictNullChecks "just to ship", fails the gate:
# .github/workflows/ci.yml
- run: npx tsconfig-doctor scan --min-score 902. Audit a monorepo's packages at once. Point it at every package tsconfig and see which ones quietly inherit a loose base:
tsconfig-doctor scan packages/*/tsconfig.json --md report.md3. Harden a project you inherited. npx tsconfig-doctor scan gives you a
prioritized checklist — the exact flags to add, hardest-hitting first.
Configuration
tsconfig-doctor init writes tsconfig-doctor.config.json:
{
"ignore": [], // rule/option ids to skip entirely
"allow": [], // options you've intentionally left off (e.g. ["noUnusedLocals"])
"minScore": 0 // CI gate threshold
}Library API
import { analyzeTsconfigFile, resolveTsconfig, DEFAULT_CONFIG } from "tsconfig-doctor";
const report = analyzeTsconfigFile("tsconfig.json", DEFAULT_CONFIG);
console.log(report.score, report.strictness, report.findings);
// Or just resolve the effective config yourself:
const { options } = resolveTsconfig("tsconfig.json");Also exported: analyzeResolved (pure), RUBRIC, STRICT_FAMILY, parseJsonc,
and types.
Roadmap
- 🤖 Optional
--ailayer (bring-your-own key) to explain a finding's impact on your code or draft the migration. The core stays 100% offline and deterministic. - A
--fixmode that writes the safe, additive flags into your tsconfig. - TS-version-aware defaults (some defaults changed across releases).
- More footguns (
pathswithoutbaseUrl,jsxwithoutjsxImportSource, lib/target mismatch). - A web playground — paste a tsconfig, see the audit, nothing uploaded.
💖 Sponsor
tsconfig-doctor is free and MIT-licensed, built and maintained in spare time. If it hardened your types, please consider supporting it:
- ⭐ Star this repo — the simplest free way to help others find it.
- 🍋 Sponsor via Lemon Squeezy — one-time or recurring.
License
MIT © tsconfig-doctor contributors
