Что компилятор находит в вашем коде из части I

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

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

Файл из части I можно переименовать в .ts и не трогать в нём ни строчки — это уже TypeScript. Меняется только одно: перед запуском код читает компилятор и вслух говорит, что в нём не так. Иногда это опечатка, которую вы искали бы полчаса с отладчиком.

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

TypeScript — надстройка над JavaScript: любая законная программа на JS является программой на TS. Никакого перевода не требуется, достаточно расширения файла. Возьмите счётчик из главы про замыкания и запустите его как TypeScript.

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

Компилятор не сказал ни слова, а программа напечатала то же, что печатала в части I. Молчание здесь — результат работы, а не безразличия: он прочитал каждую строку, выяснил, что count — число, что стрелка возвращает число, и придраться не к чему.

Одно место в старом коде он всё-таки не пропустит — параметр, про который ничего не известно.

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

Строка 1: Параметр "name" неявно имеет тип "any". Тип параметра взять неоткуда — аннотации нет, значения по умолчанию нет, а гадать по вызовам компилятор не станет. Это самая частая находка при переносе чужого JavaScript, и у неё будет отдельная статья — «TS7006: параметр неявно имеет тип any».

Диагностика есть, а вывод всё равно появился: «Привет, Рико». Мнение компилятора о коде и запуск кода — две разные вещи, дальше будет видно, насколько.

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

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

Две находки. Строка 2: Свойство "nmae" не существует в типе "{ name: string; age: number; }". Строка 4: Свойство "trim" не существует в типе "number". При запуске у этих ошибок разная судьба — первая тихо превратила приветствие в «ПРИВЕТ, UNDEFINED», вторая уронила программу. Компилятор увидел обе одинаково и до запуска.

Форму объекта никто не описывал: компилятор прочитал её из литерала в первой строке — отсюда и { name: string; age: number; } в тексте диагностики. Как именно он это делает, разберём в статье про вывод типов.

Вторая классика части I — величина, которая незаметно стала строкой.

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

Строка 2: Тип "string" не может быть назначен для типа "number". Если находку проигнорировать, дальше сработает преобразование типов из части I: плюс увидит строку и склеит вместо сложения, и вместо 20 в консоли окажется 182. В JavaScript такое ищут отладчиком, здесь оно подчёркнуто в той самой строке, где сделано.

Функции проверяются по подписи, а не по надежде.

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

Строка 6: Ожидалось аргументов: 2, получено: 3. Строка 7: Аргумент типа "number" нельзя назначить параметру типа "string". Для JavaScript оба вызова законны: первый напечатал «хахаха», второй — пустую строку, и ни один не упал. Ни один при чтении не выглядит как ошибка — а вот в списке диагностик выглядит.

Классика главы про поиск в массивах: find не обязан что-нибудь найти.

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

Строка 6: Возможно, "found" имеет значение undefined. Компилятор помнит, что find возвращает либо элемент, либо undefined, и требует это учесть. Пустоте в типах посвящена отдельная статья — «null, undefined и strictNullChecks».

И классика асинхронной главы — забытый await.

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

Строка 5: Свойство "toFixed" не существует в типе "Promise<number>". В JavaScript о такой ошибке узнают по загадочному [object Promise] в консоли или по этому же исключению.

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

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

Программа отработала без единого сбоя, а строка 3 помечена: Свойство "toUpperCase" не существует в типе "number". Эта строка не выполнялась ни разу — и не выполнится, пока аргумент меньше тысячи. Компилятор рассуждает не про сегодняшний запуск, а про все возможные: параметр объявлен числом, у чисел нет toUpperCase, значит ветка сломается, как только в неё попадут.

Достаточно поменять аргумент, чтобы предупреждение стало происшествием.

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

Тот же файл, та же строка, другой аргумент — и вместо спокойного вывода исключение. В чистом JavaScript эта ошибка спала бы в коде до первого числа больше тысячи; так и появляются падения через месяц после выкладки, на данных, которых не было в тестах.

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

Отсюда правило на всю часть III: «собралось» и «нет ошибок типов» — разные утверждения. Проверять надо второе.

Обратная сторона: компилятор проверяет согласованность, а не смысл. Что вы хотели посчитать, в типах не записано.

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

Ни одной диагностики: число делится на число, из числа вычитается число, возвращается число — с точки зрения типов всё безупречно. Лишнее - 1 в формуле — ошибка смысла, и ловят её тесты, а не компилятор.

Второе слепое пятно неожиданнее.

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

Компилятор молчит, а запуск падает. При обычном strict обращение по индексу считается удачным: тип элементов — string, значит и words[5] — string. Сомнение «а вдруг элемента нет» включается отдельным флагом noUncheckedIndexedAccess, и разговор о нём — в статье про строгость сверх strict из главы про настройку проекта.

Третье — данные из-за границы программы: разобранный JSON, ответ сервера, содержимое поля ввода. Типы описывают ваш код, а не мир снаружи; чужим данным посвящена отдельная глава, и открывает её статья «Данные извне — это unknown».

Проверка держится на том, что компилятор знает форму данных. Стоит ей потеряться — наступает молчание.

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

Диагностика ровно одна, и та про строку 5: Параметр "items" неявно имеет тип "any". Опечатку item.count вместо item.qty компилятор не заметил — у значения типа any можно спросить любое поле, и он не спорит.

Тот же цикл, но по самому массиву, без безымянного параметра посередине.

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

Теперь строка 7: Свойство "count" не существует в типе "{ title: string; price: number; qty: number; }". Разница между двумя примерами не в опечатке — она одна и та же, — а в том, знал ли компилятор, что перебирает.

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

Ловушка из части IЧто делает компилятор
Опечатка в имени свойства: user.nmaeЛовит: такого свойства в типе нет
Опечатка в имени переменной: totlЛовит: «Не удается найти имя»
Метод не от того типа: age.trim()Ловит
Число, ставшее строкой: fontSize = "18"Ловит присваивание; сам плюс со строкой остаётся законным
Забытый или лишний аргументЛовит: «Ожидалось аргументов: 2, получено: 1»
Забытый awaitЛовит, как только результат используют как значение
find ничего не нашёлЛовит: «Возможно, ... имеет значение undefined»
Выход за границу массиваНе ловит, пока не включён отдельный флаг
Ошибка в формулеНе ловит никогда
Данные из сети или JSONНе ловит сам: границу нужно описать вручную

В правой колонке нет ни одного пункта про то, что код станет лучше сам собой. Компилятор — второй читатель, которому надо объяснить, с какими данными вы работаете; чем точнее объяснили, тем больше он находит. Цена — время на объяснения, выгода — класс ошибок, который перестаёт доезжать до запуска.

Диагностики выше мы читали по смыслу, на глаз. У каждой есть ещё номер, устойчивая формулировка и иногда цепочка вложенных причин на десяток строк — с этим разберёмся в следующей статье.

  • Любой код из части I — уже TypeScript: достаточно сменить расширение файла, и его начинает читать компилятор.
  • Ошибка типа — утверждение обо всём коде, ошибка выполнения — событие одного запуска; помеченная строка может не выполниться ни разу.
  • Диагностика не отменяет запуск: tsc по умолчанию всё равно отдаёт JavaScript, поэтому «собралось» и «нет ошибок типов» — разные утверждения.
  • Компилятор ловит опечатки в свойствах, не тот тип, не то число аргументов, забытый await и возможный undefined из find.
  • Он не видит ошибки в формуле, выхода за границу массива при обычном strict и ничего не знает о данных из сети.
  • Проверка работает там, где компилятору известна форма данных: один неявный any гасит её для всего, что через него прошло.

1/4Компилятор пометил строку внутри ветки if, в которую при этом запуске не заходят. Что произойдёт, если нажать Run?

2/4В коде const words = ["раз", "два"], а дальше words[5].toUpperCase(). Что скажет компилятор при обычном strict?

3/4Почему function greet(name) { ... } — законный JavaScript, но ошибка в TypeScript?

4/4В коде две ошибки: опечатка в имени свойства объекта и лишнее «- 1» в формуле среднего. Что найдёт компилятор?

Прогноз до запуска

Не запуская пример, выпишите две вещи: какие строки пометит компилятор и на какой строке программа упадёт при запуске. Потом нажмите Run и сверьтесь.

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

Компилятор помечает две строки. Строка 2: Свойство "titel" не существует в типе "{ title: string; year: number; }". Вы имели в виду "title"? — имя достаточно близко к существующему, чтобы компилятор рискнул подсказать. Заметьте: у user.nmae из раздела «Что он находит в знакомом коде» перестановка ровно такая же, а подсказки не было — допустимую правку компилятор отмеряет от длины самого имени, и короткое nmae в этот допуск уже не уложилось. Строка 4: Свойство "pages" не существует в типе "{ title: string; year: number; }" — такого поля нет вовсе, подсказывать нечего. Строка 3 в порядке: year — число, toFixed у чисел есть.

Запуск падает на строке 2: book.titel — это undefined, а у undefined нет toUpperCase. Строки 3 и 4 до консоли не доходят вовсе, поэтому вторая находка при запуске никак себя не проявляет.

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

Довести до тишины

Компилятор недоволен вызовом. Добейтесь, чтобы диагностик не осталось ни одной, а в консоли появилась цена с НДС 20% от тысячи. Тело функции менять нельзя.

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

Сначала находка ровно одна: Ожидалось аргументов: 2, получено: 1. Пока число аргументов не сходится, до проверки их типов дело не доходит — поэтому про строку в base компилятор пока молчит. Запуск при этом печатает 1000NaN: rate оказался undefined, умножение дало NaN, а плюс со строкой склеил результат.

Добавляем второй аргумент — и появляется вторая находка: Аргумент типа "string" нельзя назначить параметру типа "number". Убираем кавычки у тысячи, и компилятор замолкает.

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

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

Ошибка, о которой не предупредят

На этот пример компилятор не скажет ничего. Предскажите, что окажется в консоли, и объясните, почему он промолчал.

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

В консоли будет «ПН», «ВТ», «СР», а затем TypeError. Условие i <= days.length делает лишний, четвёртый шаг: индексы идут с нуля, последний равен двум, а цикл доходит до трёх. days[3] — это undefined, и toUpperCase у него нет.

Компилятор промолчал, потому что при обычном strict он не следит за длиной массива: days[i] для него строка при любом i. Ошибка на единицу из части I остаётся вашей заботой ровно до того момента, когда в настройках проекта появится флаг, заставляющий сомневаться в каждом индексе.

Починка — вернуть строгое сравнение:

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