switch-exhaustiveness-check
Require switch-case statements to be exhaustive.
Some problems reported by this rule are manually fixable by editor suggestions.
This rule requires type information to run.
When working with union types or enums in TypeScript, it's common to want to write a switch
statement intended to contain a case
for each possible type in the union or the enum.
However, if the union type or the enum changes, it's easy to forget to modify the cases to account for any new types.
This rule reports when a switch
statement over a value typed as a union of literals or as an enum is missing a case for any of those literal types and does not have a default
clause.
- Flat Config
- Legacy Config
export default tseslint.config({
rules: {
"@typescript-eslint/switch-exhaustiveness-check": "error"
}
});
module.exports = {
"rules": {
"@typescript-eslint/switch-exhaustiveness-check": "error"
}
};
Try this rule in the playground ↗
Options
This rule accepts the following options:
type Options = [
{
/** If 'true', allow 'default' cases on switch statements with exhaustive cases. */
allowDefaultCaseForExhaustiveSwitch?: boolean;
/** If 'true', the 'default' clause is used to determine whether the switch statement is exhaustive for union type */
considerDefaultExhaustiveForUnions?: boolean;
/** Regular expression for a comment that can indicate an intentionally omitted default case. */
defaultCaseCommentPattern?: string;
/** If 'true', require a 'default' clause for switches on non-union types. */
requireDefaultForNonUnion?: boolean;
},
];
const defaultOptions: Options = [
{
allowDefaultCaseForExhaustiveSwitch: true,
considerDefaultExhaustiveForUnions: false,
requireDefaultForNonUnion: false,
},
];
allowDefaultCaseForExhaustiveSwitch
If 'true', allow 'default' cases on switch statements with exhaustive cases. Default: true
.
If set to false, this rule will also report when a switch
statement has a case for everything in a union and also contains a default
case. Thus, by setting this option to false, the rule becomes stricter.
When a switch
statement over a union type is exhaustive, a final default
case would be a form of dead code.
Additionally, if a new value is added to the union type and you're using considerDefaultExhaustiveForUnions
, a default
would prevent the switch-exhaustiveness-check
rule from reporting on the new case not being handled in the switch
statement.
allowDefaultCaseForExhaustiveSwitch
Caveats
It can sometimes be useful to include a redundant default
case on an exhaustive switch
statement if it's possible for values to have types not represented by the union type.
For example, in applications that can have version mismatches between clients and servers, it's possible for a server running a newer software version to send a value not recognized by the client's older typings.
If your project has a small number of intentionally redundant default
cases, you might want to use an inline ESLint disable comment for each of them.
If your project has many intentionally redundant default
cases, you may want to disable allowDefaultCaseForExhaustiveSwitch
and use the default-case
core ESLint rule along with a satisfies never
check.
requireDefaultForNonUnion
If 'true', require a 'default' clause for switches on non-union types. Default: false
.
If set to true, this rule will also report when a switch
statement switches over a non-union type (like a number
or string
, for example) and that switch
statement does not have a default
case. Thus, by setting this option to true, the rule becomes stricter.
This is generally desirable so that number
and string
switches will be subject to the same exhaustive checks that your other switches are.
Examples of additional incorrect code for this rule with { requireDefaultForNonUnion: true }
:
const value: number = Math.floor(Math.random() * 3);
switch (value) {
case 0:
return 0;
case 1:
return 1;
}
Open in PlaygroundSince value
is a non-union type it requires the switch case to have a default clause only with requireDefaultForNonUnion
enabled.
considerDefaultExhaustiveForUnions
If 'true', the 'default' clause is used to determine whether the switch statement is exhaustive for union type Default: false
.
If set to true, a switch
statement over a union type that includes a default
case is considered exhaustive.
Otherwise, the rule enforces explicitly handling every constituent of the union type with their own explicit case
.
Keeping this option disabled can be useful if you want to make sure every value added to the union receives explicit handling, with the default
case reserved for reporting an error.
Examples of additional correct code with { considerDefaultExhaustiveForUnions: true }
:
declare const literal: 'a' | 'b';
switch (literal) {
case 'a':
break;
default:
break;
}
Open in PlaygrounddefaultCaseCommentPattern
Regular expression for a comment that can indicate an intentionally omitted default case.
Default: /^no default$/iu
.
It can sometimes be preferable to omit the default case for only some switch statements.
For those situations, this rule can be given a pattern for a comment that's allowed to take the place of a default:
.
Examples of additional correct code with { defaultCaseCommentPattern: "^skip\\sdefault" }
:
declare const value: 'a' | 'b';
switch (value) {
case 'a':
break;
// skip default
}
Open in PlaygroundExamples
When the switch doesn't have exhaustive cases, either filling them all out or adding a default (if you have considerDefaultExhaustiveForUnions
enabled) will address the rule's complaint.
Here are some examples of code working with a union of literals:
- ❌ Incorrect
- ✅ Correct (Exhaustive)
- ✅ Correct (Defaulted)
type Day =
| 'Monday'
| 'Tuesday'
| 'Wednesday'
| 'Thursday'
| 'Friday'
| 'Saturday'
| 'Sunday';
declare const day: Day;
let result = 0;
switch (day) {
case 'Monday':
result = 1;
break;
}
Open in Playgroundtype Day =
| 'Monday'
| 'Tuesday'
| 'Wednesday'
| 'Thursday'
| 'Friday'
| 'Saturday'
| 'Sunday';
declare const day: Day;
let result = 0;
switch (day) {
case 'Monday':
result = 1;
break;
case 'Tuesday':
result = 2;
break;
case 'Wednesday':
result = 3;
break;
case 'Thursday':
result = 4;
break;
case 'Friday':
result = 5;
break;
case 'Saturday':
result = 6;
break;
case 'Sunday':
result = 7;
break;
}
Open in Playground// requires `considerDefaultExhaustiveForUnions` to be set to true
type Day =
| 'Monday'
| 'Tuesday'
| 'Wednesday'
| 'Thursday'
| 'Friday'
| 'Saturday'
| 'Sunday';
declare const day: Day;
let result = 0;
switch (day) {
case 'Monday':
result = 1;
break;
default:
result = 42;
}
Open in PlaygroundLikewise, here are some examples of code working with an enum:
- ❌ Incorrect
- ✅ Correct (Exhaustive)
- ✅ Correct (Defaulted)
enum Fruit {
Apple,
Banana,
Cherry,
}
declare const fruit: Fruit;
switch (fruit) {
case Fruit.Apple:
console.log('an apple');
break;
}
Open in Playgroundenum Fruit {
Apple,
Banana,
Cherry,
}
declare const fruit: Fruit;
switch (fruit) {
case Fruit.Apple:
console.log('an apple');
break;
case Fruit.Banana:
console.log('a banana');
break;
case Fruit.Cherry:
console.log('a cherry');
break;
}
Open in Playground// requires `considerDefaultExhaustiveForUnions` to be set to true
enum Fruit {
Apple,
Banana,
Cherry,
}
declare const fruit: Fruit;
switch (fruit) {
case Fruit.Apple:
console.log('an apple');
break;
default:
console.log('a fruit');
break;
}
Open in PlaygroundWhen Not To Use It
If you don't frequently switch
over union types or enums with many parts, or intentionally wish to leave out some parts, this rule may not be for you.
Type checked lint rules are more powerful than traditional lint rules, but also require configuring type checked linting.
See Troubleshooting > Linting with Type Information > Performance if you experience performance degradations after enabling type checked rules.