@typescript-eslint/switch-exhaustiveness-check
Require a switch on a union or an enum to have a case for every member
Also called typescript-eslint/switch-exhaustiveness-check, typescript/switch-exhaustiveness-check
When you add a member to a union or an enum, every switch on it needs a new case. TypeScript doesn't tell you which ones. The new value falls through the switch and nothing happens, or it lands in a default that was written for something else.
This rule reports a switch on a union of literals or an enum that doesn't list every member, and names the ones that are missing. By default a default clause doesn't count as covering them.
Configure#
Off by default. This rule needs types, so turning it on makes bun lint type check your code first.
[lint.rules]
"@typescript-eslint/switch-exhaustiveness-check" = "error" # or "warn"Every option, set to its default. The ones that are commented out are unset by default.
[lint.rules]
"@typescript-eslint/switch-exhaustiveness-check" = ["error", {
considerDefaultExhaustiveForUnions = false,
allowDefaultCaseForExhaustiveSwitch = true,
requireDefaultForNonUnion = false,
# defaultCaseCommentPattern = <string>,
}]Examples#
type Status = "open" | "closed" | "merged";
function label(status: Status) {
switch (statusSwitch is not exhaustive. Cases not matched: merged) {
case "open":
return "Open";
case "closed":
return "Closed";
}
}type Status = "open" | "closed" | "merged";
function label(status: Status) {
switch (status) {
case "open":
return "Open";
case "closed":
return "Closed";
case "merged":
return "Merged";
}
}Options#
| Option | Default | What it does |
|---|---|---|
considerDefaultExhaustiveForUnions | false | Let a default clause cover the members that have no case. |
allowDefaultCaseForExhaustiveSwitch | true | With false, report a default clause on a switch that already lists every member. |
requireDefaultForNonUnion | false | Require a default clause when the value isn't a union of literals, such as a string or a number. |
defaultCaseCommentPattern | ^no default$, ignoring case | A regular expression. A comment after the last case that matches it counts as a default clause. |
By default "closed" and "merged" are reported as missing here:
function isOpen(status: "open" | "closed" | "merged") {
switch (statusSwitch is not exhaustive. Cases not matched: closed | merged) {
case "open":
return true;
default:
return false;
}
}With considerDefaultExhaustiveForUnions, the default covers them:
{ "considerDefaultExhaustiveForUnions": true }No problemsfunction isOpen(status: "open" | "closed" | "merged") {
switch (status) {
case "open":
return true;
default:
return false;
}
}A default on a complete switch hides the next member you add. With allowDefaultCaseForExhaustiveSwitch off, it's reported:
{ "allowDefaultCaseForExhaustiveSwitch": false }1 problemfunction isOpen(status: "open" | "closed") {
switch (status) {
case "open":
return true;
case "closed":
return false;
default:The switch statement is exhaustive, so the default case is unnecessary.
throw new Error("unknown status");
}
}With requireDefaultForNonUnion, a switch on a number needs a default:
{ "requireDefaultForNonUnion": true }1 problemfunction describe(code: number) {
switch (codeSwitch is not exhaustive. Cases not matched: default) {
case 404:
return "Not found";
}
}A // no default comment says that you left it out on purpose:
{ "requireDefaultForNonUnion": true }No problemsfunction log(code: number) {
switch (code) {
case 404:
console.log("Not found");
// no default
}
}