Meldungen entziffern

Dreißig Zeilen Compilerausgabe auf einen Satz eindampfen

Die Kette läuft von außen nach innen, und die Ursache sitzt am Ende. Dazu ein knappes Dutzend Fehlercodes, die zusammen den größten Teil des Alltags abdecken.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Seit 1997 am Markt Kleine Gruppen Präsenz und Live-Online Zertifizierte Trainer
Worum es geht

Die erste Zeile nennt den Ort, nicht den Grund

Eine typische Meldung beginnt mit zwei Typen, die im Code so nirgends stehen, weil sie aus mehreren Ableitungen zusammengesetzt sind. Wer dort anfängt zu lesen, sucht nach einem Fehler in einer Struktur, die er nie geschrieben hat. Der Compiler arbeitet aber anders: Er vergleicht zuerst die beiden äußeren Typen, stellt fest, dass sie nicht passen, und begründet das, indem er Ebene für Ebene hineingeht, bis er auf das kleinste Paar stößt, das wirklich nicht zueinander passt. Genau dieses Paar steht am Ende.

Dazu kommt, dass der Compiler ab einer gewissen Tiefe abkürzt und mitten im Typ drei Punkte setzt. Wer diese Abkürzung nicht kennt, hält sie für einen Teil des Typs oder sucht nach dem fehlenden Stück im eigenen Code. Es gibt dafür einen Schalter, der die vollständige Ausgabe erzwingt, und er ist bei genau diesen Meldungen den Neustart des Prüflaufs wert.

Der Zeitverlust ist real und wiederholt sich täglich. Eine Meldung, die von unten gelesen in zehn Sekunden erledigt ist, kostet von oben gelesen mehrere Minuten, und in der Zwischenzeit entstehen Reparaturen an der falschen Stelle. Besonders teuer sind dabei Zusicherungen, die jemand einbaut, um eine Meldung loszuwerden, deren Ursache er nicht gefunden hat. Danach ist die Meldung weg und der Fehler noch da.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Symptom, Ursache, Lösung

Symptom, Ursache, Lösung

Symptom

Die Meldung ist dreißig Zeilen lang und beginnt mit zwei Typnamen, die im eigenen Code so nirgends vorkommen.

Ursache

Der Compiler vergleicht zwei zusammengesetzte Typen und begründet das Ergebnis, indem er sich Ebene für Ebene hineinarbeitet. Die oberen Zeilen zeigen den Weg, nicht den Grund.

Lösung

Lies die letzte, am tiefsten eingerückte Zeile zuerst. Dort steht das kleinste Paar, das nicht zusammenpasst, meist zwei einfache Typen. Erst danach gehst du nach oben, um zu sehen, in welchem Feld welchen Arguments es steckt.

Symptom

Mitten in der Meldung stehen drei Punkte, und genau an dieser Stelle bräuchte man die Information.

Ursache

Der Compiler kürzt lange Typen in der Ausgabe ab. Bei abgeleiteten oder generischen Typen trifft das regelmäßig genau den Teil, um den es geht.

Lösung

Setz noErrorTruncation in der Konfiguration oder gib die Option am Aufruf mit und lass den Prüflauf noch einmal durchlaufen. Die Ausgabe wird länger, enthält dafür aber den vollständigen Typ.

Symptom

Object literal may only specify known properties, obwohl derselbe Wert über eine Variable ohne Beanstandung durchgeht.

Ursache

Für direkt hingeschriebene Objekte gilt eine zusätzliche Prüfung auf überzählige Felder. Bei einer Variablen greift sie nicht, weil dort ein Typ mit mehr Feldern zu einem mit weniger passt.

Lösung

Klär, ob das zusätzliche Feld ein Tippfehler ist oder tatsächlich dazugehört. Im ersten Fall korrigierst du den Namen, im zweiten nimmst du das Feld in den Typ auf. Für Konfigurationen ist satisfies der Weg, der prüft und trotzdem die genauen Werte behält.

Symptom

Cannot find module oder Could not find a declaration file, und es ist nicht klar, was von beidem gemeint ist.

Ursache

Es sind zwei verschiedene Meldungen. Die erste, TS2307, findet das Modul überhaupt nicht. Die zweite, TS7016, findet es, aber ohne Typinformationen, sodass der Import stillschweigend zu any würde.

Lösung

Bei TS2307 prüfst du Installation, Pfad und die Auflösungseinstellung. Bei TS7016 installierst du das passende Typpaket oder schreibst eine eigene Deklarationsdatei mit den Funktionen, die ihr tatsächlich benutzt.

Symptom

Ein Wert gilt als möglicherweise undefined, obwohl zwei Zeilen darüber genau darauf geprüft wurde.

Ursache

Verengt wird nur ein benannter Ausdruck, also eine Variable oder ein Feld. Steht dort ein Funktionsergebnis wie hole().wert, greift die Prüfung für den nächsten Zugriff nicht. Dasselbe gilt für ein Feld, auf das erst in einer Rückruffunktion zugegriffen wird, und für einen Zugriff über einen Index, wenn noUncheckedIndexedAccess aktiv ist.

Lösung

Zieh den geprüften Wert in eine Konstante und arbeite mit dieser weiter. Eine Konstante kann sich zwischendurch nicht geändert haben, deshalb bleibt die Verengung erhalten, auch innerhalb eines Rückrufs. Das Ausrufezeichen ist an dieser Stelle die schlechtere Antwort, weil es nichts prüft.

Sechs Codes decken den größten Teil des Alltags ab

  1. 01 TS2322 meldet eine Zuweisung, bei der die beiden Typen nicht zueinander passen.
  2. 02 TS2345 meldet dasselbe für ein Argument an einem Funktionsaufruf.
  3. 03 TS2339 sagt, dass es die Eigenschaft auf diesem Typ nicht gibt.
  4. 04 TS2307 findet das Modul nicht, TS7016 findet es ohne Typinformationen.
  5. 05 TS18047 und TS18048 melden einen Wert, der null oder undefined sein kann.
  6. 06 TS2367 warnt vor einem Vergleich zweier Typen ohne gemeinsame Werte.
Was du mitnimmst

Was du danach in Sekunden erkennst

Es gibt zwei Fertigkeiten, und beide lassen sich üben. Die erste ist, eine verschachtelte Meldung in ihre Ebenen zu zerlegen. Die zweite ist, ein knappes Dutzend häufiger Codes so gut zu kennen, dass du die Ursache schon aus der Überschrift ableitest.

Von unten nach oben lesen

Du fängst bei der am tiefsten eingerückten Zeile an, denn dort steht das kleinste Paar, das nicht zusammenpasst. Erst wenn du das verstanden hast, gehst du nach oben, um zu sehen, über welchen Weg es an die markierte Stelle gelangt ist.

Zwischen Ort und Ursache unterscheiden

Die Datei und Zeile in der Meldung sind der Ort, an dem es auffällt. Die Ursache liegt oft in einer Deklaration ganz woanders, etwa in einem zu weiten Rückgabetyp. Du reparierst dort, nicht an der Fundstelle.

Die Abkürzung im Typ auflösen

Wenn mitten in der Ausgabe drei Punkte stehen, ist der Typ abgeschnitten. Mit noErrorTruncation bekommst du ihn vollständig, was bei zusammengesetzten Typen der Unterschied zwischen Raten und Sehen ist.

Die Sonderprüfung für Objektliterale kennen

Ein direkt hingeschriebenes Objekt wird strenger geprüft als eine Variable mit demselben Inhalt. Wenn eine Meldung nach dem Zwischenspeichern in einer Variablen verschwindet, weißt du, dass es um ein zusätzliches Feld ging.

Fehlende Module von fehlenden Typen trennen

Es gibt zwei ähnlich klingende Meldungen: Das Paket ist nicht auffindbar, oder es ist da und bringt keine Typen mit. Die erste löst du mit einer Installation, die zweite mit einem Typpaket oder einer eigenen Deklaration.

Verlorene Verengungen erkennen

Wenn ein Wert trotz einer Prüfung direkt darüber als möglicherweise undefined gilt, ist die Verengung unterwegs verloren gegangen. Du weißt, welche Konstruktionen das auslösen, und ziehst den Wert in eine Konstante.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt

Wie eine verschachtelte Meldung aufgebaut ist

Der Aufbau ist immer derselbe. Zuoberst steht der Vergleich, den der Compiler an der markierten Stelle angestellt hat, meist in der Form, dass ein Typ einem anderen nicht zugewiesen werden kann. Darunter folgt eine Begründung, eingeleitet mit einem Hinweis darauf, welche Eigenschaft nicht kompatibel ist. Diese Begründung ist selbst wieder ein Vergleich zweier Typen, und so geht es weiter, bis der Compiler bei etwas ankommt, das sich nicht weiter zerlegen lässt, etwa string gegen number. Jede Einrückungsebene ist also ein Schritt tiefer in dieselbe Struktur.

Daraus folgt die Lesereihenfolge. Die letzte Zeile sagt dir, was tatsächlich nicht passt. Die Zeilen darüber sagen dir, auf welchem Weg der Compiler dorthin gekommen ist, also welches Feld in welchem Feld in welchem Argument steckt. Für die Reparatur brauchst du beides, aber in dieser Reihenfolge: erst das Was, dann das Wo. Bei Meldungen über Funktionstypen kommt eine Besonderheit hinzu, die Verwirrung stiftet: Bei den Parametern dreht sich die Richtung des Vergleichs um. Eine Funktion, die weniger verlangt, passt dorthin, wo mehr erlaubt wäre. Wer das nicht weiß, liest die entsprechende Zeile für vertauscht und sucht den Fehler auf der falschen Seite.

Für Meldungen, die trotzdem unlesbar bleiben, gibt es zwei einfache Hilfsmittel. Das erste ist der Schalter noErrorTruncation, der die drei Punkte im Typ auflöst und die vollständige Form zeigt. Das zweite ist ein Zwischenschritt im Code: Weise den fraglichen Wert einer Konstanten mit dem erwarteten Typ zu und sieh, ob die Meldung dort schon auftritt. Damit halbierst du die Kette und kommst der Ursache in zwei Schritten näher, statt sie in einer dreißig Zeilen langen Ausgabe zu suchen.

Die Codes, die du auswendig kennen solltest

TS2322 ist die Zuweisung: Ein Wert soll in eine Variable oder ein Feld, dessen Typ nicht passt. TS2345 ist derselbe Fall an einem Funktionsaufruf, dort heißt es Argument statt Zuweisung. Beide erzeugen die langen verschachtelten Ketten, und für beide gilt die Leserichtung von unten. TS2339 dagegen ist kurz und meist eindeutig: Die Eigenschaft gibt es auf diesem Typ nicht. Häufigste Ursache ist nicht ein Tippfehler, sondern ein zu weiter Typ, etwa eine Vereinigung, bei der das Feld nur in einer der Varianten vorkommt. Der Compiler bietet in diesen Fällen oft einen Vorschlag an, und der ist erstaunlich zuverlässig.

TS2307 und TS7016 klingen ähnlich und verlangen verschiedene Reparaturen. Der erste sagt, dass das Modul nicht gefunden wurde, also fehlt die Installation, der Pfad stimmt nicht, oder die Auflösung passt nicht zur Konfiguration. Der zweite sagt, dass das Modul da ist, aber keine Typinformationen mitbringt, weshalb der Import stillschweigend zu any würde. Hier hilft ein Typpaket oder eine eigene Deklarationsdatei. Wer die beiden verwechselt, installiert Pakete, die nichts ändern.

TS18047 und TS18048 sind die Meldungen aus strictNullChecks: Ein Wert ist möglicherweise null beziehungsweise möglicherweise undefined. Sie sind fast immer berechtigt und fast immer schnell zu beheben, sobald man den Ort versteht. TS2367 ist seltener, aber wertvoll: Der Compiler weist darauf hin, dass ein Vergleich nie wahr werden kann, weil die beiden Typen keinen gemeinsamen Wert haben. Das ist regelmäßig der Hinweis auf einen veralteten Statuswert oder einen Vergleich mit einer Zeichenkette, die es in der Vereinigung gar nicht gibt.

Die Sonderprüfung für direkt hingeschriebene Objekte

Ein Objektliteral wird strenger geprüft als eine Variable. Übergibst du { name: 'Meier', farbe: 'blau' } an einen Parameter vom Typ Kunde, der kein Feld farbe kennt, meldet der Compiler, dass ein Objektliteral nur bekannte Eigenschaften angeben darf. Weist du dasselbe Objekt vorher einer Konstanten zu und übergibst diese, bleibt es still, weil die Konstante nun einen Typ mit einem zusätzlichen Feld hat und ein Typ mit mehr Feldern zu einem mit weniger passt. Dieses Verhalten wirkt widersprüchlich und ist es nicht: Die zusätzliche Prüfung existiert genau deshalb, weil ein direkt hingeschriebenes Feld fast immer ein Tippfehler oder ein Missverständnis ist.

Für die Fehlersuche ist das ein nützlicher Test. Verschwindet eine Meldung, sobald du den Wert in eine Variable ziehst, ging es um ein überzähliges Feld und nicht um einen echten Typkonflikt. Ob das ein Problem ist, entscheidest du: Bei einem Tippfehler im Feldnamen war die Meldung berechtigt, bei einem bewusst mitgeschickten Zusatzfeld gehört das Feld entweder in den Typ oder es hat dort nichts zu suchen.

In diesem Zusammenhang lohnt sich der Operator satisfies. Er prüft, ob ein Wert zu einem Typ passt, ohne den abgeleiteten Typ des Werts durch den geprüften zu ersetzen. Bei einer Konfiguration bedeutet das: Der Compiler meldet ein falsches Feld, du behältst aber die genauen Werte für alles Weitere. Ohne satisfies musst du dich zwischen beidem entscheiden, mit einer ausdrücklichen Typangabe bekommst du die Prüfung und verlierst die Genauigkeit.

Wenn eine Verengung unterwegs verloren geht

Der irritierendste Fall im Alltag: Zwei Zeilen über der Fundstelle steht eine Prüfung auf undefined, und trotzdem meldet der Compiler, der Wert könne undefined sein. Das hat fast immer einen von drei Gründen. Erstens steht links kein Name, sondern ein Aufruf: Ein Ausdruck wie hole().wert wird nicht verengt, weil jeder Aufruf etwas anderes liefern kann. Zweitens liegt der Zugriff in einer Rückruffunktion, die theoretisch später läuft, und dorthin nimmt der Compiler die Verengung eines Feldes nicht mit. Und drittens greifst du über einen Index oder einen dynamischen Schlüssel zu, was mit noUncheckedIndexedAccess grundsätzlich undefined enthalten kann. Ein gewöhnlicher Funktionsaufruf zwischen Prüfung und Verwendung gehört übrigens nicht dazu, den verkraftet die Verengung.

Die Lösung ist in allen drei Fällen dieselbe und dauert eine Zeile: Zieh den Wert nach der Prüfung in eine Konstante und arbeite mit dieser weiter. Eine Konstante kann sich nicht geändert haben, deshalb hält die Verengung. Das ist kein Trick, um den Compiler ruhigzustellen, sondern die genauere Formulierung dessen, was du ohnehin meinst.

Der falsche Weg an dieser Stelle ist das Ausrufezeichen hinter dem Ausdruck. Es unterdrückt die Meldung, ohne irgendetwas zu prüfen, und ist damit dieselbe Behauptung, die vorher schon nicht gestimmt hat. Es gibt wenige berechtigte Fälle, etwa ein Element, das garantiert im eigenen Template steht. Als allgemeine Antwort auf diese Meldungsart macht es die Prüfung wertlos und erzeugt genau die Ausnahmen zur Laufzeit, gegen die sie eingeführt wurde.

Dazu passende Kurse

Wer seitenlange Meldungen des Compilers entziffern will, ohne jedes Mal fünf Minuten zu verlieren, findet das Vorgehen in den TypeScript-Kursen.

Weil dasselbe Vorgehen auch außerhalb des Typprüfers hilft, passen dazu Kurse zur systematischen Fehlersuche im Code .

Wissen prüfen

Wie sicher bist du beim Thema wirklich?

Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.

Ein sehr gutes, praxisorientiertes und nachhaltiges Seminar. So sollte es immer sein. Vielen Dank.
LimeSurvey - Anwendertraining Teil 1 (Grundlagen)
Die Schulung ist gerade für Einsteiger oder Entwickler*innen mit eingestaubtem Basiswissen sehr hilfreich.
Vue.js 3 Grundkurs
Ich bin sehr glücklich Franz als Trainer gehabt zu haben. Er hat offensichtlich unglaublich viel Wissen zu den Themen, und schafft es, dieses auch interaktiv und verständlich weiterzugeben.
React Komplettausbildung

Häufige Fragen

Warum sind Meldungen bei generischen Typen so viel länger?
Weil der Compiler die eingesetzten Typen ausschreibt, statt den Namen des Platzhalters zu nennen. Aus einer Signatur mit zwei Platzhaltern wird in der Meldung eine ausgeschriebene Struktur mit allen Feldern. Das lässt sich nicht abschalten, aber entschärfen: Ein benannter Zwischentyp erscheint in der Meldung unter seinem Namen und kürzt die Ausgabe erheblich.
Ist der Vorschlag am Ende der Meldung verlässlich?
Bei Tippfehlern in Feldnamen ja, dort trifft der Vorschlag fast immer. Bei Zuweisungen zwischen zusammengesetzten Typen ist er eine Vermutung des Compilers und nicht selten der bequemste statt der richtige Weg. Lies in diesen Fällen die Kette zu Ende, bevor du dem Vorschlag folgst.
Helfen Erweiterungen, die Meldungen übersetzen?
Für den Einstieg deutlich, weil sie die verschachtelte Kette in verständliche Sätze umformen und die Ebenen sichtbar machen. Als Dauerlösung ersetzen sie das Lesen nicht, denn sobald es um eigene, weit abgeleitete Typen geht, kann auch eine Erweiterung nur wiedergeben, was dasteht. Nützlich sind sie vor allem in der Phase, in der ein Team gerade auf strengere Prüfungen umstellt.
Warum meldet der Editor etwas anderes als der Prüflauf im Terminal?
Fast immer, weil beide unterschiedliche Versionen benutzen. Editoren bringen eine eigene TypeScript-Version mit und lassen sich auf die des Projekts umstellen. Die zweite häufige Ursache sind unterschiedliche Konfigurationen: Der Editor nimmt die tsconfig, die er am nächsten findet, während im Terminal ausdrücklich eine andere angegeben ist.
Persönlich für dich da

Deine Ansprechpartner

Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.

Yves Hoppe

Yves Hoppe

Weiterbildung & Beratung

Hilft dir, aus dem Webentwicklung-Programm den passenden Kurs für deinen Stand zu finden.

Norbert Jansen

Norbert Jansen

Beratung & Inhouse

Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.

Vom Suchen in der Ausgabe zum gezielten Reparieren

In den TypeScript-Kursen bei cmt zerlegst du echte Meldungen aus laufenden Projekten und siehst dabei, an welcher Stelle der Kette die Reparatur wirklich hingehört.