Guarding Against Unhandled Cases in TypeScript Switch Blocks
-
Using the
never
type:The
never
type represents values that can never occur. By forcing the switch statement to consider thenever
type in addition to your actual cases, you can trick the compiler into throwing an error if there's a missing case.Here's an example:
enum Color { Red, Green, Blue } function getColorName(color: Color): string { switch (color) { case Color.Red: return "Red"; case Color.Green: return "Green"; // Oops! Missing Blue case default: const unreachable: never = color; // This line forces an error if 'color' isn't handled above return unreachable; // This line is never actually reached } }
In this example, if you forget the
Color.Blue
case, the compiler will throw an error because it can't assign a value of typeColor
to thenever
type variableunreachable
. -
Using the
noImplicitReturns
compiler option:TypeScript normally allows functions to implicitly return
undefined
if there's no explicitreturn
statement. Enabling thenoImplicitReturns
compiler option forces you to explicitly return a value in every code path of your function.Here's how to set the option in your
tsconfig.json
file:{ "compilerOptions": { "noImplicitReturns": true } }
With this option enabled, if your switch block doesn't handle all cases and there's no
default
case to return a value, the compiler will throw an error because the function would implicitly returnundefined
which might not be the intended behavior.
enum Color { Red, Green, Blue }
function getColorName(color: Color): string {
switch (color) {
case Color.Red:
return "Red";
case Color.Green:
return "Green";
// Oops! Missing Blue case
default:
const unreachable: never = color; // This line forces an error if 'color' isn't handled above
return unreachable; // This line is never actually reached
}
}
This approach requires two parts:
- Setting the
noImplicitReturns
option in yourtsconfig.json
file:
{
"compilerOptions": {
"noImplicitReturns": true
}
}
- Code example with a switch statement that might not be exhaustive:
function getDayMessage(day: number): string {
switch (day) {
case 0:
return "Sunday";
case 1:
return "Monday";
// Missing cases for Tuesday, Wednesday, etc.
}
}
-
ESLint Rule:
ESLint is a popular linting tool for JavaScript and TypeScript. There's an ESLint rule specifically designed to check for non-exhaustive switch statements. You can configure ESLint to throw errors or warnings when it encounters a switch block that might be missing cases.
This approach is helpful because it integrates with your existing linting workflow and provides automated checks.
-
Object Lookup Table:
While not strictly a method to check exhaustiveness, you can sometimes rewrite switch statements as object lookup tables. An object lookup table uses the value of the expression as a key to retrieve a corresponding value.
This approach can improve readability and maintainability, especially for simple switch statements with many cases. However, it doesn't enforce exhaustiveness directly.
-
Polymorphism and Inheritance (for Object-Oriented Design):
In object-oriented programming, you might be able to refactor your code to avoid switch statements altogether. By using polymorphism and inheritance, you can define behavior specific to different types of objects.
This approach can lead to more flexible and maintainable code, but it might not be suitable for all situations, especially for simpler scenarios where a switch statement is perfectly adequate.
typescript