@typescript-eslint/switch-exhaustiveness-check

Require a switch on a union or an enum to have a case for every member

has suggestionsneeds types

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.

bunfig.toml
[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.

bunfig.toml
[lint.rules]
"@typescript-eslint/switch-exhaustiveness-check" = ["error", {
  considerDefaultExhaustiveForUnions = false,
  allowDefaultCaseForExhaustiveSwitch = true,
  requireDefaultForNonUnion = false,
  # defaultCaseCommentPattern = <string>,
}]

Examples#

example.ts1 problem
type Status = "open" | "closed" | "merged";

function label(status: Status) {
  switch (status) {
    case "open":
      return "Open";
    case "closed":
      return "Closed";
  }
}
example.tsNo problems
type Status = "open" | "closed" | "merged";

function label(status: Status) {
  switch (status) {
    case "open":
      return "Open";
    case "closed":
      return "Closed";
    case "merged":
      return "Merged";
  }
}

Options#

OptionDefaultWhat it does
considerDefaultExhaustiveForUnionsfalseLet a default clause cover the members that have no case.
allowDefaultCaseForExhaustiveSwitchtrueWith false, report a default clause on a switch that already lists every member.
requireDefaultForNonUnionfalseRequire a default clause when the value isn't a union of literals, such as a string or a number.
defaultCaseCommentPattern^no default$, ignoring caseA 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:

example.ts1 problem
function isOpen(status: "open" | "closed" | "merged") {
  switch (status) {
    case "open":
      return true;
    default:
      return false;
  }
}

With considerDefaultExhaustiveForUnions, the default covers them:

example.tswith { "considerDefaultExhaustiveForUnions": true }No problems
function 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:

example.tswith { "allowDefaultCaseForExhaustiveSwitch": false }1 problem
function isOpen(status: "open" | "closed") {
  switch (status) {
    case "open":
      return true;
    case "closed":
      return false;
    default:
      throw new Error("unknown status");
  }
}

With requireDefaultForNonUnion, a switch on a number needs a default:

example.tswith { "requireDefaultForNonUnion": true }1 problem
function describe(code: number) {
  switch (code) {
    case 404:
      return "Not found";
  }
}

A // no default comment says that you left it out on purpose:

example.tswith { "requireDefaultForNonUnion": true }No problems
function log(code: number) {
  switch (code) {
    case 404:
      console.log("Not found");
    // no default
  }
}