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
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
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
- 01 TS2322 meldet eine Zuweisung, bei der die beiden Typen nicht zueinander passen.
- 02 TS2345 meldet dasselbe für ein Argument an einem Funktionsaufruf.
- 03 TS2339 sagt, dass es die Eigenschaft auf diesem Typ nicht gibt.
- 04 TS2307 findet das Modul nicht, TS7016 findet es ohne Typinformationen.
- 05 TS18047 und TS18048 melden einen Wert, der null oder undefined sein kann.
- 06 TS2367 warnt vor einem Vergleich zweier Typen ohne gemeinsame Werte.
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 .
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.
Wo du genau das übst
Ein sehr gutes, praxisorientiertes und nachhaltiges Seminar. So sollte es immer sein. Vielen Dank.
Die Schulung ist gerade für Einsteiger oder Entwickler*innen mit eingestaubtem Basiswissen sehr hilfreich.
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.
Häufige Fragen
Warum sind Meldungen bei generischen Typen so viel länger?
Ist der Vorschlag am Ende der Meldung verlässlich?
Helfen Erweiterungen, die Meldungen übersetzen?
Warum meldet der Editor etwas anderes als der Prüflauf im Terminal?
Deine Ansprechpartner
Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.
Yves Hoppe
Weiterbildung & Beratung
Hilft dir, aus dem Webentwicklung-Programm den passenden Kurs für deinen Stand zu finden.
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.