Type Coverage messen und verbessern: any systematisch aufspüren
AI generated
<T>
type
TypeScript · Type Coverage · Codequalität · CI/CD
Type Coverage messen und verbessern
Wie man any systematisch statt zufällig aufspürt

Ein Projekt ohne Compiler-Fehler ist nicht automatisch typsicher. any breitet sich unbemerkt aus, über externe Bibliotheken ohne Typen, über JSON.parse und über implizit inferierte Parameter. Type Coverage macht diese Lücken sichtbar, quantifiziert sie in einer einzigen Kennzahl und lässt sich als CI-Gate gegen schleichende Verschlechterung absichern.

13 Min. Lesezeit type-coverage · CI-Gates · any eliminieren TypeScript 5.x

1. Was Type Coverage misst und warum Fehlerfreiheit nicht genug ist

Type Coverage beschreibt den Anteil aller Typpositionen in einer Codebasis, die tatsächlich zu einem konkreten Typ statt zu any aufgelöst werden. Ein Projekt kann null Compiler-Fehler haben und trotzdem eine erschreckend niedrige Type Coverage aufweisen, weil any selbst kein Fehler ist, sondern ein gültiger, wenn auch typloser Zustand. Der Compiler beschwert sich nicht, wenn eine Variable any ist, weil any per Definition mit jedem anderen Typ kompatibel ist.

Genau diese Eigenschaft macht any so gefährlich in grösseren Projekten: Es breitet sich in beide Richtungen aus. Eine Funktion, die einen any-Parameter entgegennimmt, gibt diesen Typverlust an jede Stelle weiter, die den Rückgabewert weiterverwendet, oft über mehrere Funktionsaufrufe hinweg, ohne dass irgendwo eine Warnung erscheint. Type Coverage macht dieses schleichende Problem sichtbar, indem es eine einzige, messbare Prozentzahl liefert: Wie viel Prozent aller Ausdrücke im Projekt sind tatsächlich typgeprüft, statt sich hinter any zu verstecken.

Für Teams mit gewachsenen Codebasen, insbesondere nach einer JavaScript-zu-TypeScript-Migration, ist Type Coverage oft die ehrlichere Kennzahl als "null Compiler-Fehler", weil sie aufdeckt, wie viel vom nominell migrierten Code tatsächlich von echter Typprüfung profitiert und wie viel nur die Dateiendung gewechselt hat.

2. Das Tool type-coverage: Installation und erster Report

Das gebräuchlichste Werkzeug zur Messung ist das npm-Paket type-coverage, das ein bestehendes TypeScript-Projekt analysiert und eine einzige Prozentzahl ausgibt. Nach der Installation genügt ein einzelner Befehl, um für die gesamte Codebasis, wie sie in der tsconfig.json definiert ist, die aktuelle Type Coverage zu ermitteln. Das Tool nutzt intern dieselbe TypeScript Compiler API und denselben TypeChecker wie tsc selbst, wodurch die Ergebnisse konsistent mit dem sind, was der reguläre Build tatsächlich sieht.

Der erste Lauf in einem gewachsenen Projekt liefert häufig eine überraschend niedrige Zahl, oft deutlich unter 90 Prozent, selbst wenn das Projekt komplett fehlerfrei kompiliert. Dieser erste Report ist der Ausgangspunkt für jede weitere Verbesserung und sollte als Baseline festgehalten werden, bevor irgendetwas am Code verändert wird.


# Install as a dev dependency
npm install --save-dev type-coverage

# Run against the project's tsconfig.json
npx type-coverage

# Example output
# 18234 / 19850 (91.85%)

# Fail the command (non-zero exit code) if coverage drops below a threshold
npx type-coverage --at-least 92

3. Wie type-coverage intern zählt: any, implicit any und Sonderfälle

type-coverage zählt für jede Identifier-Position im Programm, ob der TypeChecker dafür einen konkreten Typ oder any auflöst, sowohl bei explizit geschriebenem any als auch bei implizit inferiertem any, das entsteht, wenn der Compiler keinen anderen Typ ableiten kann. Beide Fälle zählen gegen die Type Coverage, weil beide denselben praktischen Effekt haben: An dieser Stelle prüft der Compiler nichts.

Eine wichtige Nuance betrifft unknown, den typsicheren Gegenpart zu any. unknown zählt als voll typgeprüft, weil der Compiler bei unknown jede Operation verweigert, bis der Typ explizit eingeschränkt wurde, während any jede Operation klaglos zulässt. Diese Unterscheidung ist einer der stärksten praktischen Hebel zur Verbesserung der Type Coverage: Wo eine Migration von any zu einem konkreten Typ nicht sofort möglich ist, ist unknown mit anschliessendem Type Guard fast immer die bessere Zwischenlösung.


// Counts against Type Coverage: explicit any
function parseConfigBad(raw: any) {
  return raw.database.host; // No check at all, fails silently at runtime
}

// Also counts against Type Coverage: implicit any (no annotation, no inference source)
function handleEventBad(event) {
  console.log(event.target.value); // event is implicitly any
}

// Does NOT count against Type Coverage: unknown forces an explicit check
function parseConfigGood(raw: unknown) {
  if (
    typeof raw === "object" &&
    raw !== null &&
    "database" in raw &&
    typeof (raw as { database: unknown }).database === "object"
  ) {
    return (raw as { database: { host: string } }).database.host;
  }
  throw new Error("Invalid config shape");
}

4. Type Coverage in CI-Gates mit Schwellwerten einbinden

Der praktische Nutzen von Type Coverage entfaltet sich erst, wenn die Messung nicht einmalig, sondern kontinuierlich in der CI-Pipeline erfolgt. Der Parameter --at-least lässt type-coverage mit einem Nicht-Null-Exit-Code abbrechen, sobald die gemessene Abdeckung unter den angegebenen Schwellwert fällt, wodurch sich ein Pull Request, der die Abdeckung verschlechtert, automatisch blockieren lässt, genau wie ein fehlgeschlagener Test.

Der Schwellwert sollte dabei nicht das theoretische Ziel von 100 Prozent sein, sondern die aktuelle Baseline, gemessen mit dem ersten Report aus Abschnitt 2. So verhindert das CI-Gate zuverlässig eine weitere Verschlechterung, ohne das Team mit einer unrealistischen Hürde zu blockieren. Der Schwellwert wird danach schrittweise angehoben, sobald echte Verbesserungen gemessen wurden, nie umgekehrt.


# .github/workflows/type-coverage.yml
name: Type Coverage Gate
on: [pull_request]

jobs:
  type-coverage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm ci
      # Fails the build if coverage drops below the current baseline
      - run: npx type-coverage --at-least 91 --strict --ignore-catch

// package.json excerpt: keep the threshold and options in one place
{
  "scripts": {
    "type-coverage": "type-coverage --at-least 91 --detail",
    "type-coverage:ci": "type-coverage --at-least 91 --strict"
  },
  "typeCoverage": {
    "atLeast": 91,
    "strict": true,
    "ignoreCatch": true,
    "cache": true
  }
}

5. Typische Quellen für schlechte Type Coverage

In der Praxis konzentriert sich schlechte Type Coverage meist auf wenige wiederkehrende Quellen statt auf zufällig verteiltes any. Externe JavaScript-Bibliotheken ohne mitgelieferte oder über @types verfügbare Typdefinitionen sind eine häufige Quelle, weil jede Nutzung dieser Bibliothek automatisch any zurückgibt, sofern keine eigene Deklarationsdatei geschrieben wird. Ebenso häufig sind DOM-Events, deren target-Property ohne explizite Typ-Assertion nur als EventTarget statt als konkretes Input-Element typisiert ist.

JSON.parse() ist eine dritte, besonders häufige Quelle, weil die Funktion laut Typdefinition immer any zurückgibt, unabhängig davon, was tatsächlich geparst wird. Jede unveränderte Nutzung von JSON.parse() ohne anschliessende Typprüfung reduziert die Type Coverage um die Anzahl der Property-Zugriffe auf das Ergebnis, oft deutlich mehr, als auf den ersten Blick sichtbar ist.

6. any gezielt aufspüren: der Detail-Modus

Eine einzelne Prozentzahl allein hilft wenig, wenn nicht klar ist, wo die verbleibenden any-Stellen konkret liegen. Der Parameter --detail listet jede einzelne gefundene any-Position mit Datei, Zeile und Spalte auf, sodass sich gezielt priorisieren lässt, welche Datei zuerst angegangen wird, etwa die mit den meisten Treffern oder die geschäftskritischste.

Für grössere Projekte lohnt sich zusätzlich eine Aggregation nach Datei oder Verzeichnis, um systematische Cluster zu erkennen, etwa ein komplettes Legacy-Modul, das noch nicht migriert wurde, statt vieler kleiner Einzelstellen über die gesamte Codebasis verteilt. Diese Aggregation lässt sich einfach aus der --detail-Ausgabe ableiten, indem die Dateipfade gezählt und sortiert werden.


# List every any occurrence with file, line and column
npx type-coverage --detail

# Example output
# src/legacy/price-calculator.ts:34:12: any
# src/legacy/price-calculator.ts:41:8: any
# src/api/fetch-wrapper.ts:12:22: any
# ...

# Aggregate by file to find the worst offenders
npx type-coverage --detail | grep -oE '^[^:]+' | sort | uniq -c | sort -rn | head -10

7. Schrittweise Verbesserung in gewachsenen Codebasen

In einer grossen, gewachsenen Codebasis auf einen Schlag alle any-Stellen zu beseitigen, ist selten realistisch und blockiert oft die eigentliche Feature-Arbeit über Wochen. Der pragmatischere Weg ist eine schrittweise Strategie, angelehnt an eine graduelle TypeScript-Migration: Der CI-Schwellwert wird als Ratchet eingesetzt, der nur nach oben, nie nach unten bewegt werden darf, sodass jede neue Codeänderung die Abdeckung mindestens hält, meist sogar leicht verbessert.

Für Stellen, an denen any bewusst und temporär bleiben soll, ist // @ts-expect-error mit einem klaren Kommentar dem stillschweigenden any vorzuziehen, weil @ts-expect-error selbst einen Compiler-Fehler auslöst, sobald die Zeile darunter tatsächlich fehlerfrei wird, und so verhindert, dass ein technischer Schuldposten unbemerkt liegen bleibt, nachdem die eigentliche Ursache längst behoben wurde. Neue Dateien sollten von Anfang an mit striktem Anspruch geschrieben werden, während bestehende Dateien schrittweise über den Ratchet-Mechanismus nachgezogen werden.

8. Konkrete any-Quellen beheben: JSON.parse und Event-Handler

Die beiden in Abschnitt 5 genannten Hauptquellen lassen sich mit überschaubarem Aufwand systematisch beheben. Für JSON.parse() ist ein generischer Wrapper mit einem Runtime-Validator wie Zod die robusteste Lösung, weil er nicht nur den TypeScript-Typ korrekt setzt, sondern auch zur Laufzeit tatsächlich prüft, ob die geparsten Daten der erwarteten Struktur entsprechen, statt dem Compiler nur eine unbewiesene Behauptung per Type Assertion vorzugaukeln.

Für DOM-Event-Handler löst eine präzise Typisierung des Event-Parameters, etwa event: Event & { target: HTMLInputElement } oder das generische ChangeEvent<HTMLInputElement> aus React-typischen Type-Definitionen, das Problem an der Wurzel, ohne dass an jeder einzelnen Zugriffsstelle eine Type Assertion nötig wäre.


import { z } from "zod";

// Generic, runtime-validated JSON.parse wrapper — replaces "any" with real safety
function parseJsonSafe<T>(raw: string, schema: z.ZodType<T>): T {
  const parsed: unknown = JSON.parse(raw);
  return schema.parse(parsed); // Throws with a clear message if the shape doesn't match
}

const ConfigSchema = z.object({
  database: z.object({ host: z.string(), port: z.number() }),
});

const config = parseJsonSafe(rawConfigString, ConfigSchema);
console.log(config.database.host); // Fully typed, no any anywhere

// Typed event handler instead of an implicit any parameter
function handleInputChange(event: Event) {
  const target = event.target as HTMLInputElement;
  console.log(target.value); // No any, precise type at the boundary
}

9. Type Coverage im Vergleich zu strict-Modus und ESLint-Regel

Mehrere Werkzeuge zielen auf verwandte, aber nicht identische Probleme ab. Die folgende Übersicht ordnet Type Coverage gegenüber striktem Compiler-Modus und einer ESLint-Regel für any ein.

Werkzeug Misst Ergebnis Typischer Einsatz
type-coverage Prozentuale Typabdeckung, inkl. implizit any Eine Kennzahl, CI-Gate-fähig Projektweiter Fortschritt und Regression verhindern
strict: true in tsconfig Compiler-Fehler bei fehlender Typsicherheit Binär: Build besteht oder scheitert Grundlegendes Sicherheitsniveau erzwingen
ESLint no-explicit-any Nur explizit geschriebenes any im Quelltext Pro-Zeile-Warnung, Editor-Integration Neue any-Stellen im Review sofort sichtbar machen
Custom Compiler-API-Check Beliebige, projektspezifische Typregeln Frei definierbar Sehr spezifische Architekturregeln

Type Coverage und strict: true ergänzen sich, statt sich zu ersetzen: strict verhindert, dass neuer Code offensichtliche Typfehler enthält, während Type Coverage die verbleibende, oft grössere Grauzone aus explizitem und implizitem any quantifiziert, die strict allein nicht eliminiert. Die ESLint-Regel no-explicit-any ergänzt beide, weil sie im Editor sofort sichtbar ist, aber implizites any nicht erfasst, weshalb keines der drei Werkzeuge die anderen vollständig ersetzt.

Mironsoft

TypeScript-Codequalität, CI-Gates und Magento/Hyvä-Integrationen

Wie hoch ist die tatsächliche Type Coverage eures Projekts?

Wir analysieren bestehende TypeScript-Projekte auf versteckte any-Stellen, richten type-coverage als CI-Gate ein und begleiten die schrittweise Erhöhung der Typabdeckung ohne die laufende Feature-Entwicklung zu blockieren.

Type-Coverage-Audit

Baseline messen, any-Cluster identifizieren und priorisieren

CI-Gate-Setup

Ratchet-Schwellwerte in GitHub Actions oder GitLab CI einrichten

any-Elimination

Runtime-validierte Ersatzlösungen für JSON.parse und Event-Handling

10. Zusammenfassung

Type Coverage schliesst eine wichtige Lücke, die reine Fehlerfreiheit offen lässt: Es misst, wie viel Prozent des tatsächlichen Codes von echter Typprüfung profitiert, statt sich hinter explizitem oder implizitem any zu verstecken. Das Tool type-coverage nutzt denselben TypeChecker wie tsc selbst, liefert eine einzige, CI-taugliche Prozentzahl und lässt sich mit --at-least als Ratchet gegen Regression einsetzen.

Die häufigsten Quellen für schlechte Type Coverage, externe Bibliotheken ohne Typen, JSON.parse() und ungetypte DOM-Events, lassen sich mit gezielten, wiederholbaren Mustern beheben: Runtime-Validierung statt blinder Type Assertion, unknown statt any als sicherer Zwischenschritt, und @ts-expect-error statt stillschweigendem any für bewusste, temporäre Ausnahmen. In Kombination mit striktem Compiler-Modus und einer ESLint-Regel entsteht ein mehrschichtiges Sicherheitsnetz, das keines der drei Werkzeuge allein bieten könnte.

Type Coverage messen und verbessern - Das Wichtigste auf einen Blick

Messen

npx type-coverage liefert eine Prozentzahl über den TypeChecker, zählt explizites und implizites any.

CI-Gate

--at-least als Ratchet einsetzen, Baseline messen, Schwellwert nur nach oben verschieben.

Hauptquellen

Ungetypte Libraries, JSON.parse ohne Validierung, DOM-Events ohne präzise Typisierung.

Fix-Strategie

unknown statt any als Zwischenschritt, Runtime-Validierung mit Zod, @ts-expect-error für bewusste Ausnahmen.

11. FAQ: Type Coverage messen und verbessern

1Was ist Type Coverage?
Der Anteil aller Typpositionen, die zu einem konkreten Typ statt zu any aufgelöst werden.
2Reicht Fehlerfreiheit nicht aus?
any ist kein Fehler. Ein fehlerfreies Projekt kann trotzdem grosse ungeprüfte Bereiche enthalten.
3type-coverage installieren?
npm install --save-dev type-coverage, dann npx type-coverage oder mit --at-least Schwellwert.
4unknown genauso schlecht wie any?
Nein, unknown zählt voll, weil jede Operation erst nach expliziter Prüfung erlaubt ist.
5CI-Gate einrichten?
--at-least SCHWELLWERT in der Pipeline, Baseline messen, nur nach oben verschieben.
6Typische any-Quellen?
Ungetypte Libraries, JSON.parse ohne Prüfung, DOM-Events mit ungetyptem target.
7Konkrete any-Stellen finden?
--detail listet jede Position mit Datei, Zeile und Spalte, aggregierbar für Priorisierung.
8JSON.parse ohne vorgetäuschte Sicherheit?
Generischer Wrapper mit Runtime-Validator wie Zod prüft Typ und tatsächliche Struktur zugleich.
9@ts-expect-error statt any?
Löst selbst einen Fehler aus, sobald die Zeile fehlerfrei wird, verhindert unbemerkte Altlasten.
10Ersetzt es strict-Modus oder ESLint?
Nein, alle drei ergänzen sich und decken unterschiedliche Teile desselben Problems ab.