Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin): [switch-exhaustiveness-check] add an option to warn against a default case on an already exhaustive switch #7539

Merged
merged 50 commits into from Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
af5eda0
feat: switch-exhaustiveness-check checks for dangerous default case
Zamiell Aug 26, 2023
12a7635
fix: spelling
Zamiell Aug 26, 2023
729378f
fix: comment
Zamiell Aug 26, 2023
329c99e
fix: docs
Zamiell Aug 26, 2023
7fc2823
Merge branch 'main' into switch
Zamiell Sep 4, 2023
4a5002e
Merge branch 'main' into switch
Zamiell Sep 8, 2023
1c2f06c
Merge branch 'main' into switch
Zamiell Oct 12, 2023
0e5afe5
feat: allowDefaultCase option
Zamiell Oct 12, 2023
99ef806
fix: tests
Zamiell Oct 12, 2023
c7ca234
fix: lint
Zamiell Oct 12, 2023
5709fe3
fix: prettier
Zamiell Oct 12, 2023
074905f
Merge branch 'main' into switch
Zamiell Nov 13, 2023
81b4400
Merge branch 'main' into switch
Zamiell Dec 6, 2023
7729224
refactor: finish merge
Zamiell Dec 6, 2023
592de1d
fix: format
Zamiell Dec 6, 2023
26a9919
fix: lint
Zamiell Dec 6, 2023
e865d6b
chore: update docs
Zamiell Dec 6, 2023
5be7b48
chore: update docs
Zamiell Dec 6, 2023
d18f0f1
chore: format
Zamiell Dec 6, 2023
4091daf
fix: test
Zamiell Dec 6, 2023
d68f7b7
fix: tests
Zamiell Dec 6, 2023
1fa4494
fix: tests
Zamiell Dec 6, 2023
22c1503
fix: tests
Zamiell Dec 6, 2023
c928b18
fix: test
Zamiell Dec 6, 2023
c32204e
fix: test
Zamiell Dec 6, 2023
6ea1b32
fix: tests
Zamiell Dec 6, 2023
090a737
Update packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts
Zamiell Dec 6, 2023
b93f501
fix: double options in docs
Zamiell Dec 6, 2023
0fe1cd9
Update packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md
Zamiell Dec 6, 2023
cab680a
feat: simplify code flow
Zamiell Dec 6, 2023
8ad5037
Update packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts
Zamiell Dec 6, 2023
4aa247b
fix: grammar
Zamiell Dec 6, 2023
9a63489
Update packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts
Zamiell Dec 6, 2023
30b6695
fix: wording on option
Zamiell Dec 6, 2023
5a3bf3c
Update packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md
Zamiell Dec 6, 2023
e1e8554
docs: add playground link
Zamiell Dec 6, 2023
7dbafe5
Update packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts
Zamiell Dec 6, 2023
d0611cb
chore: add punctuation
Zamiell Dec 6, 2023
2c6dfb4
Update packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts
Zamiell Dec 6, 2023
279aa9c
chore: remove comment
Zamiell Dec 6, 2023
29284b2
refactor: rename option
Zamiell Dec 6, 2023
8e24cfb
fix: prettier
Zamiell Dec 6, 2023
9fda18d
fix: lint
Zamiell Dec 6, 2023
24327c4
fix: tests
Zamiell Dec 6, 2023
c4a7646
Merge branch 'main' into switch
Zamiell Dec 7, 2023
e5f0587
refactor: better metadata
Zamiell Dec 7, 2023
75e3015
fix: tests
Zamiell Dec 7, 2023
6e427b4
refactor: rename interface
Zamiell Dec 12, 2023
8d8bba6
refactor: make interface readonly
Zamiell Dec 12, 2023
fbc3f3e
Merge branch 'main' into switch
Zamiell Dec 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 41 additions & 23 deletions packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md
Expand Up @@ -6,12 +6,51 @@ description: 'Require switch-case statements to be exhaustive.'
>
> See **https://typescript-eslint.io/rules/switch-exhaustiveness-check** for documentation.

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 constituent (possible type in the union or the enum).
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.

There is also an option to check the exhaustiveness of switches on non-union types by requiring a default clause.
## Options
Zamiell marked this conversation as resolved.
Show resolved Hide resolved

### `"allowDefaultCaseForExhaustiveSwitch"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Docs] What do you think about providing a correct / incorrect tab here? I believe most options these days do, and they're useful for users.

I separately posted #8014 (comment) that we might eventually lint/test to enforce this practice. So no worries if you don't have the time or a good example isn't quick to write. Non-blocking IMO 🙂

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed #8154 as a followup 👍


Defaults to 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, 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](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1) 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](https://eslint.org/docs/latest/rules/default-case) along with [a `satisfies never` check](https://www.typescriptlang.org/play?#code/C4TwDgpgBAYgTgVwJbCgXigcgIZjAGwkygB8sAjbAO2u0wG4AoRgMwSoGNgkB7KqBAGcI8ZMAAULRCgBcsacACUcwcDhIqAcygBvRlCiCA7ig4ALKJIWLd+g1A7ZhWXASJy99+3AjAEcfhw8QgApZA4iJi8AX2YvR2dMShoaTA87Lx8-AIpaGjCkCIYMqFiSgBMIFmwEfGB0rwMpMUNsbkEWJAhBKCoIADcIOCjGrP9A9gBrKh4jKgKikYNY5cZYoA).

### `requireDefaultForNonUnion`

Defaults to false. It 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 }`:

```ts option='{ "requireDefaultForNonUnion": true }' showPlaygroundButton
const value: number = Math.floor(Math.random() * 3);

switch (value) {
case 0:
return 0;
case 1:
return 1;
}
```

Since `value` is a non-union type it requires the switch case to have a default clause only with `requireDefaultForNonUnion` enabled.

<!--/tabs-->

## Examples

Expand Down Expand Up @@ -181,27 +220,6 @@ switch (fruit) {

<!--/tabs-->

## Options

### `requireDefaultForNonUnion`

Examples of additional **incorrect** code for this rule with `{ requireDefaultForNonUnion: true }`:

```ts option='{ "requireDefaultForNonUnion": true }' showPlaygroundButton
const value: number = Math.floor(Math.random() * 3);

switch (value) {
case 0:
return 0;
case 1:
return 1;
}
```

Since `value` is a non-union type it requires the switch case to have a default clause only with `requireDefaultForNonUnion` enabled.

<!--/tabs-->

## When 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.