type-coverage

📁 marius-townhouse/effective-typescript-skills 📅 10 days ago
1
总安装量
1
周安装量
#49887
全站排名
安装命令
npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill type-coverage

Agent 安装分布

mcpjam 1
openhands 1
replit 1
windsurf 1
zencoder 1

Skill 文档

Track Your Type Coverage to Prevent Regressions in Type Safety

Overview

Monitor how much of your code is typed vs any.

Type coverage measures the percentage of symbols with real types vs any. Track it over time to prevent type safety regressions.

When to Use This Skill

  • Migrating JavaScript to TypeScript
  • Maintaining type quality over time
  • Code reviews for type safety
  • Measuring progress on any elimination

The Iron Rule

What gets measured gets managed.
Track type coverage; set coverage goals.

Remember:

  • any spreads silently through code
  • Coverage can regress without notice
  • Explicit tracking prevents decay
  • Set team goals for coverage improvement

Understanding Type Coverage

let x: number = 1;        // x is covered (has real type)
let y: any = 2;           // y is NOT covered (has any)
let z = JSON.parse('{}'); // z is NOT covered (implicit any from JSON.parse)

Type coverage = (covered symbols) / (total symbols)

Tools for Measuring Coverage

type-coverage package

npm install -g type-coverage
type-coverage --detail

Output:

23384/24058 97.20%

With --detail, it shows which symbols have any:

src/api.ts:15:7 - data
src/utils.ts:42:3 - result

Project-Specific Configuration

// package.json
{
  "scripts": {
    "type-coverage": "type-coverage --at-least 95"
  }
}

Fail CI if coverage drops below threshold.

Sources of any

1. Explicit any

function process(data: any) { ... }  // Developer wrote any

2. Implicit any (when noImplicitAny is off)

function process(data) { ... }  // data is implicitly any

3. any from Libraries

const data = JSON.parse(str);   // Returns any
const result = $.ajax(url);     // jQuery returns any

4. Contagious any

function getUser(): any { ... }
const user = getUser();
//    ^? any - spreads from function

const name = user.name;
//    ^? any - continues spreading

Strategies for Improvement

Replace JSON.parse

// Before: returns any
const data = JSON.parse(str);

// After: validate with zod
const schema = z.object({ name: z.string() });
const data = schema.parse(JSON.parse(str));
//    ^? { name: string }

Fix Library Types

// Augment JSON.parse to return unknown
declare global {
  interface JSON {
    parse(text: string): unknown;
  }
}

Use unknown Instead

// Before
function parse(): any { ... }

// After
function parse(): unknown { ... }

Add Type Annotations

// Before: inferred as any from library
const result = externalLib.process(data);

// After: explicitly typed
const result: ProcessedData = externalLib.process(data);

Tracking Over Time

# Record in CI
echo "$(date): $(type-coverage)" >> coverage-history.txt

# Fail if coverage decreased
type-coverage --at-least $(cat .type-coverage-baseline)

Setting Team Goals

Milestone Coverage Target
Migration start 50%
Phase 1 75%
Phase 2 90%
Stable 95%+

Increase targets as codebase matures.

Preventing Regressions

Pre-commit Hook

# .husky/pre-commit
type-coverage --at-least 95

CI Check

# .github/workflows/ci.yml
- name: Type Coverage
  run: npx type-coverage --at-least 95

Code Review Checklist

  • No new any types without justification
  • Type coverage didn’t decrease
  • External data is validated

Dealing with Necessary any

Sometimes any is unavoidable:

// Document why any is needed
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const legacy: any = oldSystem.getData();  // TODO: Type when migrating legacy system

Track these with comments and tickets.

Real-World Example

// Before migration: 60% coverage
// Sources of any:
// - JSON.parse: 15%
// - Legacy API: 12%
// - Untyped dependencies: 8%
// - Explicit any: 5%

// Action plan:
// 1. Add zod validation: +10%
// 2. Type legacy API responses: +10%
// 3. Add @types packages: +8%
// 4. Remove explicit any: +5%

// After: 93% coverage

Pressure Resistance Protocol

1. “We Can’t Achieve 100%”

Pressure: “Some code can’t be typed”

Response: Aim for improvement, not perfection. Track what you can.

Action: Set realistic goals; document necessary exceptions.

2. “It’s Too Much Work”

Pressure: “Fixing all any types takes too long”

Response: Incremental improvement. Block new any types first.

Action: Ratchet: prevent new any, fix old over time.

Red Flags – STOP and Reconsider

  • Coverage decreasing over time
  • New any types without justification
  • any spreading from function returns
  • Untested code with heavy any usage

Common Rationalizations (All Invalid)

Excuse Reality
“It’s just one any” any spreads; one becomes many
“We’ll fix it later” Later never comes without tracking
“Coverage is high enough” Set higher goals as you improve

Quick Reference

# Measure coverage
npx type-coverage

# With details
npx type-coverage --detail

# Fail if below threshold
npx type-coverage --at-least 95

# In package.json
"scripts": {
  "type-coverage": "type-coverage --at-least 95"
}

The Bottom Line

Track type coverage to prevent regression.

any types spread silently. Without measurement, type safety erodes over time. Use tools to track coverage, set goals, and fail builds when coverage drops.

Reference

Based on “Effective TypeScript” by Dan Vanderkam, Item 49: Track Your Type Coverage to Prevent Regressions in Type Safety.