Skip to content

Commit a034d0a

Browse files
authored
fix(eslint-plugin): [switch-exhaustiveness-check] enum members with new line or single quotes are not being fixed correctly (#7806)
* feat(eslint-plugin): [switch-exhaustiveness-check] members with new line or single quotes are not being fixed correctly Closes #7768 * cleanup * oops * wip wip * maybe? * idk why it was missing
1 parent c65b9dd commit a034d0a

File tree

3 files changed

+174
-15
lines changed

3 files changed

+174
-15
lines changed

packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
---
2-
description: 'Require switch-case statements to be exhaustive with union type.'
2+
description: 'Require switch-case statements to be exhaustive with union types and enums.'
33
---
44

55
> 🛑 This file is source code, not the primary documentation location! 🛑
66
>
77
> See **https://typescript-eslint.io/rules/switch-exhaustiveness-check** for documentation.
88
9-
When working with union types 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).
10-
However, if the union type changes, it's easy to forget to modify the cases to account for any new types.
9+
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).
10+
However, if the union type or the enum changes, it's easy to forget to modify the cases to account for any new types.
1111

12-
This rule reports when a `switch` statement over a value typed as a union of literals is missing a case for any of those literal types and does not have a `default` clause.
12+
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.
1313

1414
## Examples
1515

16+
When the switch doesn't have exhaustive cases, either filling them all out or adding a default will correct the rule's complaint.
17+
18+
Here are some examples of code working with a union of literals:
19+
1620
<!--tabs-->
1721

1822
### ❌ Incorrect
@@ -27,7 +31,7 @@ type Day =
2731
| 'Saturday'
2832
| 'Sunday';
2933

30-
const day = 'Monday' as Day;
34+
declare const day: Day;
3135
let result = 0;
3236

3337
switch (day) {
@@ -49,7 +53,7 @@ type Day =
4953
| 'Saturday'
5054
| 'Sunday';
5155

52-
const day = 'Monday' as Day;
56+
declare const day: Day;
5357
let result = 0;
5458

5559
switch (day) {
@@ -89,7 +93,7 @@ type Day =
8993
| 'Saturday'
9094
| 'Sunday';
9195

92-
const day = 'Monday' as Day;
96+
declare const day: Day;
9397
let result = 0;
9498

9599
switch (day) {
@@ -101,6 +105,80 @@ switch (day) {
101105
}
102106
```
103107

108+
<!--/tabs-->
109+
110+
Likewise, here are some examples of code working with an enum:
111+
112+
<!--tabs-->
113+
114+
### ❌ Incorrect
115+
116+
```ts
117+
enum Fruit {
118+
Apple,
119+
Banana,
120+
Cherry,
121+
}
122+
123+
declare const fruit: Fruit;
124+
125+
switch (fruit) {
126+
case Fruit.Apple:
127+
console.log('an apple');
128+
break;
129+
}
130+
```
131+
132+
### ✅ Correct
133+
134+
```ts
135+
enum Fruit {
136+
Apple,
137+
Banana,
138+
Cherry,
139+
}
140+
141+
declare const fruit: Fruit;
142+
143+
switch (fruit) {
144+
case Fruit.Apple:
145+
console.log('an apple');
146+
break;
147+
148+
case Fruit.Banana:
149+
console.log('a banana');
150+
break;
151+
152+
case Fruit.Cherry:
153+
console.log('a cherry');
154+
break;
155+
}
156+
```
157+
158+
### ✅ Correct
159+
160+
```ts
161+
enum Fruit {
162+
Apple,
163+
Banana,
164+
Cherry,
165+
}
166+
167+
declare const fruit: Fruit;
168+
169+
switch (fruit) {
170+
case Fruit.Apple:
171+
console.log('an apple');
172+
break;
173+
174+
default:
175+
console.log('a fruit');
176+
break;
177+
}
178+
```
179+
180+
<!--/tabs-->
181+
104182
## When Not To Use It
105183

106-
If you don't frequently `switch` over union types with many parts, or intentionally wish to leave out some parts.
184+
If you don't frequently `switch` over union types or enums with many parts, or intentionally wish to leave out some parts.

packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default createRule({
1818
type: 'suggestion',
1919
docs: {
2020
description:
21-
'Require switch-case statements to be exhaustive with union type',
21+
'Require switch-case statements to be exhaustive with union types and enums',
2222
requiresTypeChecking: true,
2323
},
2424
hasSuggestions: true,
@@ -74,13 +74,19 @@ export default createRule({
7474
(missingBranchName || missingBranchName === '') &&
7575
requiresQuoting(missingBranchName.toString(), compilerOptions.target)
7676
) {
77-
caseTest = `${symbolName}['${missingBranchName}']`;
77+
const escapedBranchName = missingBranchName
78+
.replace(/'/g, "\\'")
79+
.replace(/\n/g, '\\n')
80+
.replace(/\r/g, '\\r');
81+
82+
caseTest = `${symbolName}['${escapedBranchName}']`;
7883
}
7984

8085
const errorMessage = `Not implemented yet: ${caseTest} case`;
86+
const escapedErrorMessage = errorMessage.replace(/'/g, "\\'");
8187

8288
missingCases.push(
83-
`case ${caseTest}: { throw new Error('${errorMessage}') }`,
89+
`case ${caseTest}: { throw new Error('${escapedErrorMessage}') }`,
8490
);
8591
}
8692

packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RuleTester } from '@typescript-eslint/rule-tester';
1+
import { noFormat, RuleTester } from '@typescript-eslint/rule-tester';
22
import path from 'path';
33

44
import switchExhaustivenessCheck from '../../src/rules/switch-exhaustiveness-check';
@@ -518,7 +518,7 @@ export enum Enum {
518518
519519
function test(arg: Enum): string {
520520
switch (arg) {
521-
case Enum['test-test']: { throw new Error('Not implemented yet: Enum['test-test'] case') }
521+
case Enum['test-test']: { throw new Error('Not implemented yet: Enum[\\'test-test\\'] case') }
522522
case Enum.test: { throw new Error('Not implemented yet: Enum.test case') }
523523
}
524524
}
@@ -555,7 +555,7 @@ export enum Enum {
555555
556556
function test(arg: Enum): string {
557557
switch (arg) {
558-
case Enum['']: { throw new Error('Not implemented yet: Enum[''] case') }
558+
case Enum['']: { throw new Error('Not implemented yet: Enum[\\'\\'] case') }
559559
case Enum.test: { throw new Error('Not implemented yet: Enum.test case') }
560560
}
561561
}
@@ -592,7 +592,7 @@ export enum Enum {
592592
593593
function test(arg: Enum): string {
594594
switch (arg) {
595-
case Enum['9test']: { throw new Error('Not implemented yet: Enum['9test'] case') }
595+
case Enum['9test']: { throw new Error('Not implemented yet: Enum[\\'9test\\'] case') }
596596
case Enum.test: { throw new Error('Not implemented yet: Enum.test case') }
597597
}
598598
}
@@ -602,5 +602,80 @@ function test(arg: Enum): string {
602602
},
603603
],
604604
},
605+
{
606+
code: `
607+
enum Enum {
608+
'a' = 1,
609+
[\`key-with
610+
611+
new-line\`] = 2,
612+
}
613+
614+
declare const a: Enum;
615+
616+
switch (a) {
617+
}
618+
`,
619+
errors: [
620+
{
621+
messageId: 'switchIsNotExhaustive',
622+
suggestions: [
623+
{
624+
messageId: 'addMissingCases',
625+
output: `
626+
enum Enum {
627+
'a' = 1,
628+
[\`key-with
629+
630+
new-line\`] = 2,
631+
}
632+
633+
declare const a: Enum;
634+
635+
switch (a) {
636+
case Enum.a: { throw new Error('Not implemented yet: Enum.a case') }
637+
case Enum['key-with\\n\\n new-line']: { throw new Error('Not implemented yet: Enum[\\'key-with\\n\\n new-line\\'] case') }
638+
}
639+
`,
640+
},
641+
],
642+
},
643+
],
644+
},
645+
{
646+
code: noFormat`
647+
enum Enum {
648+
'a' = 1,
649+
"'a' \`b\` \\"c\\"" = 2,
650+
}
651+
652+
declare const a: Enum;
653+
654+
switch (a) {}
655+
`,
656+
errors: [
657+
{
658+
messageId: 'switchIsNotExhaustive',
659+
suggestions: [
660+
{
661+
messageId: 'addMissingCases',
662+
output: `
663+
enum Enum {
664+
'a' = 1,
665+
"'a' \`b\` \\"c\\"" = 2,
666+
}
667+
668+
declare const a: Enum;
669+
670+
switch (a) {
671+
case Enum.a: { throw new Error('Not implemented yet: Enum.a case') }
672+
case Enum['\\'a\\' \`b\` "c"']: { throw new Error('Not implemented yet: Enum[\\'\\\\'a\\\\' \`b\` "c"\\'] case') }
673+
}
674+
`,
675+
},
676+
],
677+
},
678+
],
679+
},
605680
],
606681
});

0 commit comments

Comments
 (0)