From a034d0a3856aa07bd2d52b557fa33c7a88e9e511 Mon Sep 17 00:00:00 2001 From: StyleShit <32631382+StyleShit@users.noreply.github.com> Date: Thu, 16 Nov 2023 16:53:45 +0200 Subject: [PATCH] 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 --- .../docs/rules/switch-exhaustiveness-check.md | 94 +++++++++++++++++-- .../src/rules/switch-exhaustiveness-check.ts | 12 ++- .../rules/switch-exhaustiveness-check.test.ts | 83 +++++++++++++++- 3 files changed, 174 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md index 9320624924b..11998bfc5f2 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md @@ -1,18 +1,22 @@ --- -description: 'Require switch-case statements to be exhaustive with union type.' +description: 'Require switch-case statements to be exhaustive with union types and enums.' --- > 🛑 This file is source code, not the primary documentation location! 🛑 > > See **https://typescript-eslint.io/rules/switch-exhaustiveness-check** for documentation. -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). -However, if the union type changes, it's easy to forget to modify the cases to account for any new types. +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). +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 is missing a case for any of those literal types and does not have a `default` clause. +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. ## Examples +When the switch doesn't have exhaustive cases, either filling them all out or adding a default will correct the rule's complaint. + +Here are some examples of code working with a union of literals: + ### ❌ Incorrect @@ -27,7 +31,7 @@ type Day = | 'Saturday' | 'Sunday'; -const day = 'Monday' as Day; +declare const day: Day; let result = 0; switch (day) { @@ -49,7 +53,7 @@ type Day = | 'Saturday' | 'Sunday'; -const day = 'Monday' as Day; +declare const day: Day; let result = 0; switch (day) { @@ -89,7 +93,7 @@ type Day = | 'Saturday' | 'Sunday'; -const day = 'Monday' as Day; +declare const day: Day; let result = 0; switch (day) { @@ -101,6 +105,80 @@ switch (day) { } ``` + + +Likewise, here are some examples of code working with an enum: + + + +### ❌ Incorrect + +```ts +enum Fruit { + Apple, + Banana, + Cherry, +} + +declare const fruit: Fruit; + +switch (fruit) { + case Fruit.Apple: + console.log('an apple'); + break; +} +``` + +### ✅ Correct + +```ts +enum 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; +} +``` + +### ✅ Correct + +```ts +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; +} +``` + + + ## When Not To Use It -If you don't frequently `switch` over union types with many parts, or intentionally wish to leave out some parts. +If you don't frequently `switch` over union types or enums with many parts, or intentionally wish to leave out some parts. diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index f4dce14fbde..91ca9b555a7 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -18,7 +18,7 @@ export default createRule({ type: 'suggestion', docs: { description: - 'Require switch-case statements to be exhaustive with union type', + 'Require switch-case statements to be exhaustive with union types and enums', requiresTypeChecking: true, }, hasSuggestions: true, @@ -74,13 +74,19 @@ export default createRule({ (missingBranchName || missingBranchName === '') && requiresQuoting(missingBranchName.toString(), compilerOptions.target) ) { - caseTest = `${symbolName}['${missingBranchName}']`; + const escapedBranchName = missingBranchName + .replace(/'/g, "\\'") + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r'); + + caseTest = `${symbolName}['${escapedBranchName}']`; } const errorMessage = `Not implemented yet: ${caseTest} case`; + const escapedErrorMessage = errorMessage.replace(/'/g, "\\'"); missingCases.push( - `case ${caseTest}: { throw new Error('${errorMessage}') }`, + `case ${caseTest}: { throw new Error('${escapedErrorMessage}') }`, ); } diff --git a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts index b250a09e2bb..9852e0d543d 100644 --- a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts +++ b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts @@ -1,4 +1,4 @@ -import { RuleTester } from '@typescript-eslint/rule-tester'; +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import path from 'path'; import switchExhaustivenessCheck from '../../src/rules/switch-exhaustiveness-check'; @@ -518,7 +518,7 @@ export enum Enum { function test(arg: Enum): string { switch (arg) { - case Enum['test-test']: { throw new Error('Not implemented yet: Enum['test-test'] case') } + case Enum['test-test']: { throw new Error('Not implemented yet: Enum[\\'test-test\\'] case') } case Enum.test: { throw new Error('Not implemented yet: Enum.test case') } } } @@ -555,7 +555,7 @@ export enum Enum { function test(arg: Enum): string { switch (arg) { - case Enum['']: { throw new Error('Not implemented yet: Enum[''] case') } + case Enum['']: { throw new Error('Not implemented yet: Enum[\\'\\'] case') } case Enum.test: { throw new Error('Not implemented yet: Enum.test case') } } } @@ -592,7 +592,7 @@ export enum Enum { function test(arg: Enum): string { switch (arg) { - case Enum['9test']: { throw new Error('Not implemented yet: Enum['9test'] case') } + case Enum['9test']: { throw new Error('Not implemented yet: Enum[\\'9test\\'] case') } case Enum.test: { throw new Error('Not implemented yet: Enum.test case') } } } @@ -602,5 +602,80 @@ function test(arg: Enum): string { }, ], }, + { + code: ` + enum Enum { + 'a' = 1, + [\`key-with + + new-line\`] = 2, + } + + declare const a: Enum; + + switch (a) { + } + `, + errors: [ + { + messageId: 'switchIsNotExhaustive', + suggestions: [ + { + messageId: 'addMissingCases', + output: ` + enum Enum { + 'a' = 1, + [\`key-with + + new-line\`] = 2, + } + + declare const a: Enum; + + switch (a) { + case Enum.a: { throw new Error('Not implemented yet: Enum.a case') } + case Enum['key-with\\n\\n new-line']: { throw new Error('Not implemented yet: Enum[\\'key-with\\n\\n new-line\\'] case') } + } + `, + }, + ], + }, + ], + }, + { + code: noFormat` + enum Enum { + 'a' = 1, + "'a' \`b\` \\"c\\"" = 2, + } + + declare const a: Enum; + + switch (a) {} + `, + errors: [ + { + messageId: 'switchIsNotExhaustive', + suggestions: [ + { + messageId: 'addMissingCases', + output: ` + enum Enum { + 'a' = 1, + "'a' \`b\` \\"c\\"" = 2, + } + + declare const a: Enum; + + switch (a) { + case Enum.a: { throw new Error('Not implemented yet: Enum.a case') } + case Enum['\\'a\\' \`b\` "c"']: { throw new Error('Not implemented yet: Enum[\\'\\\\'a\\\\' \`b\` "c"\\'] case') } + } + `, + }, + ], + }, + ], + }, ], });