Как читать диагностику: код TS####, текст, вложенные причины
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
Компилятор не ругается — он составляет протокол. В каждом сообщении ровно три части: место (файл, строка, столбец), код вида TS2345 и текст, который называет, кто кому не подошёл. Прочитанное целиком, оно показывает не только симптом, но и дорогу к настоящей причине — а она нередко на десяток строк выше подчёркнутого места.
Начнём с самой короткой диагностики, какая бывает. Что скажет компилятор — понятно; интереснее, что будет после того, как он это скажет.
В терминале эта же претензия печатается одной строкой: пример.ts(2,1): error TS2322: Тип "string" не может быть назначен для типа "number". Под примером песочница показывает укороченную версию — «строка 2» и текст. Номер TS2322 она опускает, но в редакторе и в выводе tsc он будет на месте, и дальше мы увидим, зачем он нужен.
| Часть | Что это | Зачем читать |
|---|---|---|
пример.ts(2,1) | файл, строка и столбец | единственное место, которое указывает на ваш код |
error TS2322 | вид ошибки — номер, не зависящий от языка | по нему ищут объяснение и похожие случаи |
| текст после двоеточия | кто кому не подошёл | первый тип — ваш, второй — ожидаемый |
| строки с отступом под текстом | вложенные причины | самая нижняя — суть претензии |
Чтение начинают с номера строки, а не с текста: остальные части сообщения говорят про типы, и только он — про ваш файл.
И сразу главная неожиданность: диагностика была, а console.log всё равно напечатал «три». Так ведёт себя и tsc по умолчанию; к этому вернёмся в последнем разделе.
Два самых частых кода описывают одну и ту же беду в разных местах. TS2322 — значение кладут в переменную или свойство. TS2345 — значение передают в функцию.
«Аргумент типа "string" нельзя назначить параметру типа "number"» — порядок слов всегда один: сначала то, что вы дали, потом то, чего ждали. А на исполнении всё сработало: repeat сам привёл «3» к числу, как это принято в JavaScript. Компилятор жалуется не на будущее падение, а на нарушенный договор.
TS2554 говорит то же самое совсем без слова «тип»: «Ожидалось аргументов: 2, получено: 1». Это буквальная форма «ожидалось / получено», и она же объясняет вывод: greeting не получил значения и уехал в строку как undefined.
Формулировка — калька с английского «is not assignable to»: слева источник, справа приёмник. Мнемоника простая — «X не влезает в Y». Порядок одинаков во всех сообщениях такого вида, и проверить, что вы его усвоили, можно на двух строках.
Когда сравниваются не два примитива, а две структуры, компилятор не ограничивается вердиктом: он показывает путь, которым до несовместимости дошёл.
Цепочка читается как воронка, и уровней в ней столько, сколько шагов до виноватого места, — здесь их четыре. Верхний — общий вердикт: объект mug не годится на роль Product. Второй называет виноватое свойство: «Типы свойства "tags" несовместимы». Третий спускается внутрь этого свойства: массив чисел вместо массива строк. И только нижний говорит суть: где ждали строку, лежит число. Чинят по нижней строке, а место в коде ищут по имени свойства со второго уровня.
В песочнице цепочка склеена в одну строку — уровни видны по нарастающим пробелам. В терминале и редакторе те же уровни печатаются лесенкой.
Но цепочка бывает не всегда. Если объект написан прямо в месте использования, компилятор разбирает его по свойствам и указывает на каждое виноватое отдельно.
Две диагностики от одной невнимательности, обе на строке 3, но в разных столбцах — по одной на свойство. Правило такое: литерал на месте разбирают по косточкам, готовую переменную сравнивают целиком и объясняют цепочкой. Первое точнее показывает место, второе лучше объясняет причину.
Функции сравниваются по параметрам и результату, и средний уровень цепочки называет, что именно не сошлось.
«Типы параметров "value" и "value" несовместимы»: тот, кто получит значение типа Handler, вправе передать в него число, а наша функция к числу не готова. Последняя строка примера это подтверждает уже на исполнении — метода trim у числа нет.
Самая дорогая ошибка чтения — считать, что править надо там, где подчёркнуто. Отсюда берутся правки наугад: подогнать сигнатуру, добавить объединение, лишь бы подчёркивание пропало.
Подчёркнута строка 7, а опечатка — в строке 1, где timeout получил строку «5s». Компилятор молчал шесть строк не по невнимательности: сам по себе settings был совершенно законным объектом — просто не тем, который ждёт connect. Несовместимость появилась в момент встречи, там о ней и сообщили.
Имя виноватого свойства цепочка уже назвала — timeout. Дальше дело техники: найти, откуда оно берётся, и починить источник. Заметьте, что типов в первой строке не написано вовсе, а компилятор всё равно знает, что там строка; откуда — тема следующей статьи.
Прочитав цепочку, ищите по имени свойства, которое она назвала, а не по номеру строки из шапки.
Номер в тексте не участвует и переводу не поддаётся — этим он и ценен. Формулировки меняются от версии к версии и от языка к языку, номер не меняется: по TS2345 находится и документация, и чужие обсуждения ровно той же беды, а по русской фразе — почти ничего.
| Код | Когда появляется | Как звучит |
|---|---|---|
TS2322 | значение кладут не в ту переменную или свойство | Тип "string" не может быть назначен для типа "number". |
TS2345 | не тот аргумент в вызове | Аргумент типа "string" нельзя назначить параметру типа "number". |
TS2554 | не то количество аргументов | Ожидалось аргументов: 2, получено: 1. |
TS2339 | читают свойство, которого нет в типе | Свойство "nmae" не существует в типе "{ name: string; }". |
TS2552 | опечатка в имени переменной | Не удается найти имя "totl". Вы имели в виду "total"? |
TS7006 | параметр без типа при строгих проверках | Параметр "width" неявно имеет тип "any". |
TS18048 | значение может оказаться undefined | Возможно, "found" имеет значение undefined. |
Одного запуска хватает, чтобы получить сразу три разных кода — и увидеть, что диагностики и выполнение живут по разным правилам.
Три сообщения — три независимые беды: TS2552 про опечатку в имени, TS2362 про умножение строки, TS2322 про единицу вместо boolean. Выполнение при этом добралось только до третьей строки: totl не существует и для JavaScript, дальше ReferenceError и тишина.
Отсюда рабочий порядок: чинить сверху вниз и перезапускать. Иногда нижние сообщения исчезают сами — они были следствием верхнего; здесь беды не связаны, и починить придётся все три.
TypeScript не запрещает запуск. tsc по умолчанию выдаёт JavaScript даже при ошибках — за это отвечает выключенный noEmitOnError, — а типы при переводе просто стираются. Песочница учебника ведёт себя так же, поэтому под примером всегда два голоса: сначала претензии компилятора, потом настоящий вывод.
Диагностика на строке 2 оказалась предсказанием. Компилятор сказал: «здесь лежит не строка», — и последняя строка доказала это уже на исполнении. В этом смысле TS2322 — это TypeError, увиденный за минуту до запуска.
А здесь предсказание другое: падения нет, есть тихое undefined, которое поедет дальше по программе и всплывёт где-нибудь в разметке через десять функций. Компилятор говорит не «упадёт», а «вы описали не то» — и во втором случае это ценнее: TypeError вы бы и сами нашли.
Весь приём чтения складывается в четыре шага: место, код, текст, цепочка. Пройдя их, правку вносят туда, где ошибка родилась, а не туда, где её заметили.
- Диагностика состоит из трёх частей: место (файл, строка, столбец), код
TS####и текст; читать начинают с места. - В тексте сначала называют тип, который у вас есть, и только потом ожидаемый — и в присваивании (
TS2322), и в аргументе (TS2345). - Вложенные причины — воронка: верхний уровень про типы целиком, следующий называет виноватое свойство, нижний объясняет несовместимость.
- Литерал, написанный на месте, компилятор разбирает по свойствам; готовую переменную сравнивает целиком и объясняет цепочкой.
- Строка диагностики — место встречи двух типов, а не место рождения ошибки: править часто нужно выше по коду.
- Ошибка типов не отменяет запуск: код всё равно выполняется, поэтому диагностика — предсказание, а не приговор.
1/4Компилятор пишет: «Тип "string" не может быть назначен для типа "number"». Какой из двух типов описывает ваше значение?
2/4Цепочка из четырёх уровней: «Аргумент типа {…} нельзя назначить параметру типа "Product"», ниже «Типы свойства "tags" несовместимы», ниже «Тип "number[]" не может быть назначен для типа "string[]"», в самом низу «Тип "number" не может быть назначен для типа "string"». Что здесь главное?
3/4TS2345 указывает на строку с вызовом функции, а переменную с неправильным полем создали двадцатью строками выше. Почему подчёркнут вызов?
4/4Пример на TypeScript выдал диагностику, но под ним всё равно появился вывод console.log. Почему?
Прочитать цепочку и найти виноватую строку
Запустите пример, прочитайте цепочку целиком и почините код, изменив ровно одно значение. Оно не в той строке, которую назовёт компилятор.
Компилятор подчеркнёт строку 9 — вызов print(draft). Средний уровень цепочки называет виноватое свойство: «Типы свойства "paid" несовместимы», нижний объясняет, чем именно: строка вместо boolean. Значит, править нужно строку 3.
После правки диагностик нет, а вывод становится «7: позиций 2, оплачен: true». Попытка вместо этого расширить тип поля до boolean | string убрала бы сообщение, но оставила бы в данных строку «да», которая в if ведёт себя не так, как её задумывали.
Какая из двух диагностик уронит код
Здесь две ошибки, обе с кодом TS2322 и обе на одной строке. Одна из них приведёт к падению при запуске, вторая пройдёт незамеченной. Решите, какая именно, и только потом запускайте.
Уронит host. Строка 4 зовёт toUpperCase — метод строк, а пришло число 80, и на исполнении получается TypeError: config.host.toUpperCase is not a function. Со вторым свойством ничего бы не случилось: port участвует только в склейке строк, а туда годится и «8080».
Обе диагностики одинаково серьёзны для типов и совершенно по-разному — для запуска. Компилятор не сортирует ошибки по тому, упадёт код или нет, и это к лучшему: вторую беду иначе никто бы не заметил.
Где чинить: данные или сигнатура
Цепочка называет обе стороны конфликта. Прочитайте её и решите, что здесь правильно поменять — тип параметра или сами данные.
Цепочка звучит так: «Аргумент типа "string[]" нельзя назначить параметру типа "number[]"», ниже — «Тип "string" не может быть назначен для типа "number"». Соблазн велик: расширить параметр до (number | string)[], и сообщение исчезнет. Но вывод 0199350 показывает, во что превращается сложение цифр в кавычках — беда никуда не денется, просто станет молчаливой.
Правило: подгонять типы под сломанные данные — способ спрятать ошибку. Сигнатуру меняют, когда она описывает функцию неверно; здесь она описывала её точно.