Как читать диагностику: код TS####, текст, вложенные причины

компилятор≈ 8 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Компилятор не ругается — он составляет протокол. В каждом сообщении ровно три части: место (файл, строка, столбец), код вида TS2345 и текст, который называет, кто кому не подошёл. Прочитанное целиком, оно показывает не только симптом, но и дорогу к настоящей причине — а она нередко на десяток строк выше подчёркнутого места.

Разделы темы:·

Начнём с самой короткой диагностики, какая бывает. Что скажет компилятор — понятно; интереснее, что будет после того, как он это скажет.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

В терминале эта же претензия печатается одной строкой: пример.ts(2,1): error TS2322: Тип "string" не может быть назначен для типа "number". Под примером песочница показывает укороченную версию — «строка 2» и текст. Номер TS2322 она опускает, но в редакторе и в выводе tsc он будет на месте, и дальше мы увидим, зачем он нужен.

ЧастьЧто этоЗачем читать
пример.ts(2,1)файл, строка и столбецединственное место, которое указывает на ваш код
error TS2322вид ошибки — номер, не зависящий от языкапо нему ищут объяснение и похожие случаи
текст после двоеточиякто кому не подошёлпервый тип — ваш, второй — ожидаемый
строки с отступом под текстомвложенные причинысамая нижняя — суть претензии

Чтение начинают с номера строки, а не с текста: остальные части сообщения говорят про типы, и только он — про ваш файл.

И сразу главная неожиданность: диагностика была, а console.log всё равно напечатал «три». Так ведёт себя и tsc по умолчанию; к этому вернёмся в последнем разделе.

Два самых частых кода описывают одну и ту же беду в разных местах. TS2322 — значение кладут в переменную или свойство. TS2345 — значение передают в функцию.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

«Аргумент типа "string" нельзя назначить параметру типа "number"» — порядок слов всегда один: сначала то, что вы дали, потом то, чего ждали. А на исполнении всё сработало: repeat сам привёл «3» к числу, как это принято в JavaScript. Компилятор жалуется не на будущее падение, а на нарушенный договор.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

TS2554 говорит то же самое совсем без слова «тип»: «Ожидалось аргументов: 2, получено: 1». Это буквальная форма «ожидалось / получено», и она же объясняет вывод: greeting не получил значения и уехал в строку как undefined.

Формулировка — калька с английского «is not assignable to»: слева источник, справа приёмник. Мнемоника простая — «X не влезает в Y». Порядок одинаков во всех сообщениях такого вида, и проверить, что вы его усвоили, можно на двух строках.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Когда сравниваются не два примитива, а две структуры, компилятор не ограничивается вердиктом: он показывает путь, которым до несовместимости дошёл.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Цепочка читается как воронка, и уровней в ней столько, сколько шагов до виноватого места, — здесь их четыре. Верхний — общий вердикт: объект mug не годится на роль Product. Второй называет виноватое свойство: «Типы свойства "tags" несовместимы». Третий спускается внутрь этого свойства: массив чисел вместо массива строк. И только нижний говорит суть: где ждали строку, лежит число. Чинят по нижней строке, а место в коде ищут по имени свойства со второго уровня.

В песочнице цепочка склеена в одну строку — уровни видны по нарастающим пробелам. В терминале и редакторе те же уровни печатаются лесенкой.

Но цепочка бывает не всегда. Если объект написан прямо в месте использования, компилятор разбирает его по свойствам и указывает на каждое виноватое отдельно.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Две диагностики от одной невнимательности, обе на строке 3, но в разных столбцах — по одной на свойство. Правило такое: литерал на месте разбирают по косточкам, готовую переменную сравнивают целиком и объясняют цепочкой. Первое точнее показывает место, второе лучше объясняет причину.

Функции сравниваются по параметрам и результату, и средний уровень цепочки называет, что именно не сошлось.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

«Типы параметров "value" и "value" несовместимы»: тот, кто получит значение типа Handler, вправе передать в него число, а наша функция к числу не готова. Последняя строка примера это подтверждает уже на исполнении — метода trim у числа нет.

Самая дорогая ошибка чтения — считать, что править надо там, где подчёркнуто. Отсюда берутся правки наугад: подогнать сигнатуру, добавить объединение, лишь бы подчёркивание пропало.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Подчёркнута строка 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.

Одного запуска хватает, чтобы получить сразу три разных кода — и увидеть, что диагностики и выполнение живут по разным правилам.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Три сообщения — три независимые беды: TS2552 про опечатку в имени, TS2362 про умножение строки, TS2322 про единицу вместо boolean. Выполнение при этом добралось только до третьей строки: totl не существует и для JavaScript, дальше ReferenceError и тишина.

Отсюда рабочий порядок: чинить сверху вниз и перезапускать. Иногда нижние сообщения исчезают сами — они были следствием верхнего; здесь беды не связаны, и починить придётся все три.

TypeScript не запрещает запуск. tsc по умолчанию выдаёт JavaScript даже при ошибках — за это отвечает выключенный noEmitOnError, — а типы при переводе просто стираются. Песочница учебника ведёт себя так же, поэтому под примером всегда два голоса: сначала претензии компилятора, потом настоящий вывод.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Диагностика на строке 2 оказалась предсказанием. Компилятор сказал: «здесь лежит не строка», — и последняя строка доказала это уже на исполнении. В этом смысле TS2322 — это TypeError, увиденный за минуту до запуска.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

А здесь предсказание другое: падения нет, есть тихое undefined, которое поедет дальше по программе и всплывёт где-нибудь в разметке через десять функций. Компилятор говорит не «упадёт», а «вы описали не то» — и во втором случае это ценнее: TypeError вы бы и сами нашли.

Весь приём чтения складывается в четыре шага: место, код, текст, цепочка. Пройдя их, правку вносят туда, где ошибка родилась, а не туда, где её заметили.

  • Диагностика состоит из трёх частей: место (файл, строка, столбец), код TS#### и текст; читать начинают с места.
  • В тексте сначала называют тип, который у вас есть, и только потом ожидаемый — и в присваивании (TS2322), и в аргументе (TS2345).
  • Вложенные причины — воронка: верхний уровень про типы целиком, следующий называет виноватое свойство, нижний объясняет несовместимость.
  • Литерал, написанный на месте, компилятор разбирает по свойствам; готовую переменную сравнивает целиком и объясняет цепочкой.
  • Строка диагностики — место встречи двух типов, а не место рождения ошибки: править часто нужно выше по коду.
  • Ошибка типов не отменяет запуск: код всё равно выполняется, поэтому диагностика — предсказание, а не приговор.

1/4Компилятор пишет: «Тип "string" не может быть назначен для типа "number"». Какой из двух типов описывает ваше значение?

2/4Цепочка из четырёх уровней: «Аргумент типа {…} нельзя назначить параметру типа "Product"», ниже «Типы свойства "tags" несовместимы», ниже «Тип "number[]" не может быть назначен для типа "string[]"», в самом низу «Тип "number" не может быть назначен для типа "string"». Что здесь главное?

3/4TS2345 указывает на строку с вызовом функции, а переменную с неправильным полем создали двадцатью строками выше. Почему подчёркнут вызов?

4/4Пример на TypeScript выдал диагностику, но под ним всё равно появился вывод console.log. Почему?

Прочитать цепочку и найти виноватую строку

Запустите пример, прочитайте цепочку целиком и почините код, изменив ровно одно значение. Оно не в той строке, которую назовёт компилятор.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Компилятор подчеркнёт строку 9 — вызов print(draft). Средний уровень цепочки называет виноватое свойство: «Типы свойства "paid" несовместимы», нижний объясняет, чем именно: строка вместо boolean. Значит, править нужно строку 3.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

После правки диагностик нет, а вывод становится «7: позиций 2, оплачен: true». Попытка вместо этого расширить тип поля до boolean | string убрала бы сообщение, но оставила бы в данных строку «да», которая в if ведёт себя не так, как её задумывали.

Какая из двух диагностик уронит код

Здесь две ошибки, обе с кодом TS2322 и обе на одной строке. Одна из них приведёт к падению при запуске, вторая пройдёт незамеченной. Решите, какая именно, и только потом запускайте.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Уронит host. Строка 4 зовёт toUpperCase — метод строк, а пришло число 80, и на исполнении получается TypeError: config.host.toUpperCase is not a function. Со вторым свойством ничего бы не случилось: port участвует только в склейке строк, а туда годится и «8080».

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Обе диагностики одинаково серьёзны для типов и совершенно по-разному — для запуска. Компилятор не сортирует ошибки по тому, упадёт код или нет, и это к лучшему: вторую беду иначе никто бы не заметил.

Где чинить: данные или сигнатура

Цепочка называет обе стороны конфликта. Прочитайте её и решите, что здесь правильно поменять — тип параметра или сами данные.

ts
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Цепочка звучит так: «Аргумент типа "string[]" нельзя назначить параметру типа "number[]"», ниже — «Тип "string" не может быть назначен для типа "number"». Соблазн велик: расширить параметр до (number | string)[], и сообщение исчезнет. Но вывод 0199350 показывает, во что превращается сложение цифр в кавычках — беда никуда не денется, просто станет молчаливой.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Правило: подгонять типы под сломанные данные — способ спрятать ошибку. Сигнатуру меняют, когда она описывает функцию неверно; здесь она описывала её точно.