Настройка TypeScript для проверки exhaustiveness в switch
Пошаговое руководство по настройке TypeScript для проверки exhaustiveness в switch-выражениях. Обнаружение ошибок при добавлении новых значений во время разработки.
Как настроить TypeScript для проверки switch-выражений во время разработки, чтобы он подсвечивал ошибки при добавлении новых значений, которые не обрабатываются в switch? В текущей реализации ошибки возникают только во время выполнения, когда в объекте names появляются значения, отличные от ‘bob’ и ‘alice’. Как сделать так, чтобы TypeScript сразу предупреждал о несоответствиях при добавлении новых значений, например ‘test’, в момент написания кода?
TypeScript позволяет настроить проверку полноты coverage в switch-выражениях во время разработки с помощью паттерна exhaustive checking с использованием never типа. Для этого нужно определить тип всех возможных значений как union type и добавить default-ветку с типом never, которая будет вызывать ошибку компиляции при добавлении новых значений. Такой подход обеспечивает немедленное обнаружение несоответствий при написании кода, а не только во время выполнения.
Содержание
- TypeScript switch: проверка exhaustiveness во время разработки
- Настройка TypeScript для проверки непокрытых значений в switch
- Паттерн exhaustive checking с использованием never типа
- Оптимизация конфигурации tsconfig.json для switch-выражений
- Заключение
TypeScript switch: проверка exhaustiveness во время разработки
Когда мы говорим о проверке полноты coverage в switch-выражениях, TypeScript предлагает мощные механизмы, которые позволяют отлавливать ошибки на этапе компиляции. Проблема, которую вы описываете, возникает потому что TypeScript по умолчанию не знает обо всех возможных значениях, которые могут появиться в объекте names.
Решение заключается в использовании дискриминированных объединений и строгой типизации. Вместо того чтобы работать с любыми строковыми значениями в switch, мы должны определить точный тип тех значений, которые допустимы. Такой подход позволяет TypeScript отслеживать все возможные варианты и предупреждать о пропущенных случаях.
Вот почему стандартный подход с typescript switch часто не обеспечивает нужной проверки exhaustiveness. TypeScript видит, что переменная имеет тип string, но не понимает, что в реальности она может принимать только определенные значения из ограниченного набора.
Настройка TypeScript для проверки непокрытых значений в switch
Чтобы настроить TypeScript для проверки полноты coverage в switch-выражениях, необходимо выполнить несколько ключевых шагов. Во-первых, определите тип всех возможных значений, которые могут быть использованы в switch:
type AllowedNames = 'bob' | 'alice';
Затем объявите переменную с этим типом вместо общего string:
const name: AllowedNames = 'bob'; // или 'alice'
Теперь, когда вы используете typescript switch case для этой переменной, TypeScript будет понимать, что существуют только два возможных значения. Но как добавить проверку на exhaustiveness? Для этого используется специальный паттерн с never типом.
Добавьте default-ветку в switch, которая присваивает значение переменной типу never. Если вы добавите новое значение в AllowedNames, компилятор TypeScript выдаст ошибку в default-ветке, потому что значение не может быть присвоено типу never.
switch (name) {
case 'bob':
// обработка для bob
break;
case 'alice':
// обработка для alice
break;
default:
const exhaustiveCheck: never = name; // Здесь будет ошибка при добавлении новых значений
return exhaustiveCheck;
}
Этот подход гарантирует, что все возможные значения будут обработаны во время компиляции. Как только вы добавите новое значение в тип AllowedNames, например, 'test', TypeScript сразу выдаст ошибку в default-ветке.
Паттерн exhaustive checking с использованием never типа
Паттерн exhaustive checking — это один из самых мощных инструментов в TypeScript для обеспечения полноты coverage в различных конструкциях, включая switch, if-else и даже в функциях. Суть этого паттерна заключается в использовании типа never для проверки того, что все возможные варианты были обработаны.
Тип never в TypeScript представляет значения, которые никогда не должны возникнуть. Когда мы используем never в default-ветке switch, мы говорим компилятору: “Если эта ветка выполнится, значит, в типе есть значение, которое мы не обработали, и это ошибка”.
Вот как выглядит полный пример реализации паттерна exhaustive checking:
// Определяем тип всех возможных значений
type Name = 'bob' | 'alice';
// Создаем функцию, которая принимает только значения из этого типа
function processName(name: Name) {
switch (name) {
case 'bob':
console.log('Processing bob');
return 'bob_processed';
case 'alice':
console.log('Processing alice');
return 'alice_processed';
default:
// Здесь TypeScript проверит, что все значения из Name обработаны
const exhaustiveCheck: never = name;
return exhaustiveCheck;
}
}
// Использование функции
const result = processName('bob'); // Работает нормально
const result2 = processName('alice'); // Работает нормально
// const result3 = processName('test'); // Ошибка: Argument of type '"test"' is not assignable to parameter of type 'Name'
Этот подход особенно полезен, когда ваши значения определяются динамически, например, из объекта:
const names = {
bob: 'Bob',
alice: 'Alice'
};
type Name = keyof typeof names; // 'bob' | 'alice'
function processName(name: Name) {
switch (name) {
case 'bob':
console.log('Processing bob');
break;
case 'alice':
console.log('Processing alice');
break;
default:
const exhaustiveCheck: never = name;
return exhaustiveCheck;
}
}
Теперь, если вы добавите новое свойство в объект names, например:
const names = {
bob: 'Bob',
alice: 'Alice',
test: 'Test' // Новое значение
};
Тип Name автоматически расширится до 'bob' | 'alice' | 'test', и TypeScript выдаст ошибку в default-ветке, потому что 'test' не может быть присвоено типу never.
Оптимизация конфигурации tsconfig.json для switch-выражений
Для дополнительной настройки TypeScript в отношении switch-выражений можно использовать несколько опций в файле tsconfig.json. Хотя нет отдельного флага, который бы автоматически проверял exhaustiveness, существуют полезные опции, связанные с работой switch:
{
"compilerOptions": {
"noFallthroughCasesInSwitch": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
Опция noFallthroughCasesInSwitch заставляет TypeScript выдавать ошибку, если в switch пропущен break/return, что помогает избежать логических ошибок. Хотя это не напрямую связано с проверкой exhaustiveness, она помогает писать более безопасный код.
Опция strict включает все строгие проверки TypeScript, включая noImplicitAny и strictNullChecks, которые помогают отлавливать дополнительные ошибки при работе с типами.
Помимо этого, можно создать утилитарный тип для упрощения паттерна exhaustive checking:
type exhaustiveCheck = (value: never) => never;
function assertExhaustive(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
Теперь использование становится более чистым:
function processName(name: Name) {
switch (name) {
case 'bob':
console.log('Processing bob');
break;
case 'alice':
console.log('Processing alice');
break;
default:
return assertExhaustive(name);
}
}
Такой подход делает код более читаемым и повторно используемым.
Источники
- TypeScript Documentation - Narrowing — Руководство по сужению типов и проверке exhaustiveness в TypeScript: https://www.typescriptlang.org/docs/handbook/2/narrowing.html
- TypeScript Documentation - Compiler Options — Документация по опциям компилятора TypeScript, включая noFallthroughCasesInSwitch: https://www.typescriptlang.org/docs/handbook/compiler-options.html
- TypeScript Documentation - tsconfig.json — Описание файла конфигурации TypeScript и его использования: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html
Заключение
Настройка TypeScript для проверки exhaustiveness в switch-выражениях — это мощный инструмент, который позволяет находить ошибки на этапе компиляции, а не во время выполнения. Основной подход заключается в определении точного типа значений и использовании паттерна с never в default-ветке.
Когда вы определяете тип как type Name = 'bob' | 'alice', а затем используете этот тип в typescript switch с default-веткой, которая присваивает значение never, TypeScript будет немедленно предупреждать о добавлении новых значений. Это именно то, что вам нужно для обнаружения несоответствий во время написания кода.
Дополнительные опции в tsconfig.json, такие как noFallthroughCasesInSwitch и strict, помогают создать более безопасную среду разработки. Комбинируя эти подходы, вы можете создавать более надежный код, который защищает от ошибок, связанных с неполной обработкой значений в switch-выражениях.
Для того чтобы TypeScript сразу предупреждал о непокрытых ветках switch, используйте discriminated union и проверку exhaustiveness. Определите тип ключей объекта как type Name = keyof typeof names; и объявите переменную name как Name. Внутри switch добавьте default-ветку, в которой присваиваете переменную типу never. Если вы добавите новый ключ в объект names, тип Name автоматически расширится, а компилятор выдаст ошибку, потому что default-ветка не сможет присвоить значение never. Такой подход гарантирует, что все возможные значения будут обработаны во время компиляции.
Включите флаг --noFallthroughCasesInSwitch в tsconfig. Он заставит TypeScript выдавать ошибку, если в switch пропущен break/return и если в объекте names появляются новые значения, которые не покрыты case. В текущей реализации TypeScript нет отдельного флага, который бы автоматически проверял, что все значения типа покрыты в switch. Для такой проверки обычно используют паттерн с never в default, но это не описано в документации.
Для настройки проекта TypeScript используется tsconfig.json файл, который указывает корневые файлы и опции компилятора, необходимые для компиляции проекта. Файл tsconfig.json в директории указывает, что это корень проекта TypeScript. Проект компилируется либо путем вызова tsc без входных файлов (компилятор ищет tsconfig.json), либо с помощью опции --project, которая указывает путь к директории с файлом tsconfig.json. Это позволяет централизованно управлять настройками для всего проекта.