From 6d0448b07edd6e366fb2c430aaab557a7e3c51cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Sun, 8 Sep 2024 15:26:50 +0900 Subject: [PATCH 01/15] feat: add option --- packages/eslint-plugin/package.json | 2 +- .../src/rules/switch-exhaustiveness-check.ts | 38 +++++- .../rules/switch-exhaustiveness-check.test.ts | 117 ++++++++++++++++++ 3 files changed, 152 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index a5d0676c8baa..fd49c189a925 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -54,7 +54,7 @@ "generate:breaking-changes": "tsx tools/generate-breaking-changes.mts", "generate:configs": "npx nx generate-configs repo", "lint": "npx nx lint", - "test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules\" jest --coverage --logHeapUsage", + "test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules\" jest tests/rules/switch-exhaustiveness-check.test.ts --coverage --logHeapUsage", "test-single": "cross-env NODE_OPTIONS=\"--experimental-vm-modules\" jest --no-coverage", "typecheck": "tsc --noEmit" }, diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index 1452d7de6cdb..a860ff1df2a1 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -36,6 +36,13 @@ type Options = [ * @default false */ requireDefaultForNonUnion?: boolean; + + /** + * If `true`, the `default` clause is used to determine whether the switch statement is Exhaustive for union type. + * + * @default true + */ + allowDefaultCaseMatchUnionMember?: boolean; }, ]; @@ -65,6 +72,10 @@ export default createRule({ description: `If 'true', require a 'default' clause for switches on non-union types.`, type: 'boolean', }, + allowDefaultCaseMatchUnionMember: { + description: `If 'true', the 'default' clause is used to determine whether the switch statement is Exhaustive for union type`, + type: 'boolean', + }, }, additionalProperties: false, }, @@ -81,11 +92,18 @@ export default createRule({ { allowDefaultCaseForExhaustiveSwitch: true, requireDefaultForNonUnion: false, + allowDefaultCaseMatchUnionMember: true, }, ], create( context, - [{ allowDefaultCaseForExhaustiveSwitch, requireDefaultForNonUnion }], + [ + { + allowDefaultCaseForExhaustiveSwitch, + requireDefaultForNonUnion, + allowDefaultCaseMatchUnionMember, + }, + ], ) { const services = getParserServices(context); const checker = services.program.getTypeChecker(); @@ -155,10 +173,13 @@ export default createRule({ const { missingLiteralBranchTypes, symbolName, defaultCase } = switchMetadata; - // We only trigger the rule if a `default` case does not exist, since that - // would disqualify the switch statement from having cases that exactly + // We only trigger the rule if a `default` case does not exist when allowDefaultCaseMatchUnionMember option + // is `true`. because that would disqualify the switch statement from having cases that exactly // match the members of a union. - if (missingLiteralBranchTypes.length > 0 && defaultCase === undefined) { + if ( + missingLiteralBranchTypes.length > 0 && + (allowDefaultCaseMatchUnionMember ? defaultCase === undefined : true) + ) { context.report({ node: node.discriminant, messageId: 'switchIsNotExhaustive', @@ -243,6 +264,15 @@ export default createRule({ .join('\n'); if (lastCase) { + const isLastCaseDefaultCase = lastCase.test === null; + if (isLastCaseDefaultCase) { + const beforeFixString = missingCases + .map(code => `${code}\n${caseIndent}`) + .join(''); + + return fixer.insertTextBefore(lastCase, beforeFixString); + } + return fixer.insertTextAfter(lastCase, `\n${fixString}`); } 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 7d912ddc0690..f83056bc192b 100644 --- a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts +++ b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts @@ -811,6 +811,50 @@ switch (value) { }, ], }, + { + code: ` + declare const literal: "a" | "b"; + switch (literal) { + case "a": break; + case "b": break; + } + `, + options: [ + { + allowDefaultCaseMatchUnionMember: true, + }, + ], + }, + { + code: ` + declare const literal: "a" | "b"; + switch (literal) { + case "a": break; + case "b": break; + default: break; + } + `, + options: [ + { + allowDefaultCaseMatchUnionMember: true, + }, + ], + }, + { + code: ` + declare const literal: "a" | "b"; + switch (literal) { + case "a": break; + case "b": break; + } + `, + options: [ + { + allowDefaultCaseMatchUnionMember: true, + allowDefaultCaseForExhaustiveSwitch: false, + }, + ], + }, ], invalid: [ { @@ -2374,5 +2418,78 @@ switch (myValue) { }, ], }, + { + code: ` +declare const literal: "a" | "b"; + +switch (literal) { + case "a": break; + default: break; +} + `, + options: [ + { + allowDefaultCaseMatchUnionMember: false, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 4, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const literal: "a" | "b"; + +switch (literal) { + case "a": break; + case "b": { throw new Error('Not implemented yet: "b" case') } + default: break; +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const literal: "a" | "b" | "c"; + +switch (literal) { + case "a": break; + default: break; +} + `, + options: [ + { + allowDefaultCaseMatchUnionMember: false, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 4, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const literal: "a" | "b" | "c"; + +switch (literal) { + case "a": break; + case "b": { throw new Error('Not implemented yet: "b" case') } + case "c": { throw new Error('Not implemented yet: "c" case') } + default: break; +} + `, + }, + ], + }, + ], + }, ], }); From 82f7a10b684529b879160450714e81d84a283870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Sun, 8 Sep 2024 23:56:58 +0900 Subject: [PATCH 02/15] fix: test error --- .../rules/switch-exhaustiveness-check.mdx | 21 +++++ packages/eslint-plugin/package.json | 2 +- .../switch-exhaustiveness-check.shot | 24 ++++-- .../rules/switch-exhaustiveness-check.test.ts | 85 +++++++++++-------- .../switch-exhaustiveness-check.shot | 6 ++ 5 files changed, 97 insertions(+), 41 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx index f629d8ee53ac..1a1fb76496dd 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx @@ -53,6 +53,27 @@ switch (value) { Since `value` is a non-union type it requires the switch case to have a default clause only with `requireDefaultForNonUnion` enabled. +### `allowDefaultCaseMatchUnionMember` + +Defaults to true. If set to `true`, the `default` clause is used to determine whether the switch statement is Exhaustive for union type. + +In a switch case, if you enter the default statement, it can be considered that Exhaustive is satisfied, but if you think that entering the default statement did not solve the exhaustive problem of the union type, please set to option `true`. + +Examples of additional **incorrect** code for this rule with `{ allowDefaultCaseMatchUnionMember: true }`: + +```ts option='{ "allowDefaultCaseMatchUnionMember": true }' showPlaygroundButton +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; + default: + break; +} +``` + +Since `value` is a non-union type it requires the switch case to have a default clause only with `requireDefaultForNonUnion` enabled. + ## Examples When the switch doesn't have exhaustive cases, either filling them all out or adding a default will correct the rule's complaint. diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index fd49c189a925..a5d0676c8baa 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -54,7 +54,7 @@ "generate:breaking-changes": "tsx tools/generate-breaking-changes.mts", "generate:configs": "npx nx generate-configs repo", "lint": "npx nx lint", - "test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules\" jest tests/rules/switch-exhaustiveness-check.test.ts --coverage --logHeapUsage", + "test": "cross-env NODE_OPTIONS=\"--experimental-vm-modules\" jest --coverage --logHeapUsage", "test-single": "cross-env NODE_OPTIONS=\"--experimental-vm-modules\" jest --no-coverage", "typecheck": "tsc --noEmit" }, diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot index 1b2fd682199f..1f5331ba4526 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot @@ -16,6 +16,20 @@ switch (value) { `; exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 2`] = ` +"Options: { "allowDefaultCaseMatchUnionMember": true } + +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; + default: + break; +} +" +`; + +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 3`] = ` "Incorrect type Day = @@ -39,7 +53,7 @@ switch (day) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 3`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 4`] = ` "Correct type Day = @@ -80,7 +94,7 @@ switch (day) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 4`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 5`] = ` "Correct type Day = @@ -105,7 +119,7 @@ switch (day) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 5`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 6`] = ` "Incorrect enum Fruit { @@ -125,7 +139,7 @@ switch (fruit) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 6`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 7`] = ` "Correct enum Fruit { @@ -152,7 +166,7 @@ switch (fruit) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 7`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 8`] = ` "Correct enum Fruit { 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 f83056bc192b..ad1966fbdfa1 100644 --- a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts +++ b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts @@ -813,12 +813,14 @@ switch (value) { }, { code: ` - declare const literal: "a" | "b"; - switch (literal) { - case "a": break; - case "b": break; - } - `, +declare const literal: 'a' | 'b'; +switch (literal) { + case 'a': + break; + case 'b': + break; +} + `, options: [ { allowDefaultCaseMatchUnionMember: true, @@ -827,13 +829,16 @@ switch (value) { }, { code: ` - declare const literal: "a" | "b"; - switch (literal) { - case "a": break; - case "b": break; - default: break; - } - `, +declare const literal: 'a' | 'b'; +switch (literal) { + case 'a': + break; + case 'b': + break; + default: + break; +} + `, options: [ { allowDefaultCaseMatchUnionMember: true, @@ -842,12 +847,14 @@ switch (value) { }, { code: ` - declare const literal: "a" | "b"; - switch (literal) { - case "a": break; - case "b": break; - } - `, +declare const literal: 'a' | 'b'; +switch (literal) { + case 'a': + break; + case 'b': + break; +} + `, options: [ { allowDefaultCaseMatchUnionMember: true, @@ -2420,13 +2427,15 @@ switch (myValue) { }, { code: ` -declare const literal: "a" | "b"; +declare const literal: 'a' | 'b'; switch (literal) { - case "a": break; - default: break; + case 'a': + break; + default: + break; } - `, + `, options: [ { allowDefaultCaseMatchUnionMember: false, @@ -2441,14 +2450,16 @@ switch (literal) { { messageId: 'addMissingCases', output: ` -declare const literal: "a" | "b"; +declare const literal: 'a' | 'b'; switch (literal) { - case "a": break; + case 'a': + break; case "b": { throw new Error('Not implemented yet: "b" case') } - default: break; + default: + break; } - `, + `, }, ], }, @@ -2456,13 +2467,15 @@ switch (literal) { }, { code: ` -declare const literal: "a" | "b" | "c"; +declare const literal: 'a' | 'b' | 'c'; switch (literal) { - case "a": break; - default: break; + case 'a': + break; + default: + break; } - `, + `, options: [ { allowDefaultCaseMatchUnionMember: false, @@ -2477,15 +2490,17 @@ switch (literal) { { messageId: 'addMissingCases', output: ` -declare const literal: "a" | "b" | "c"; +declare const literal: 'a' | 'b' | 'c'; switch (literal) { - case "a": break; + case 'a': + break; case "b": { throw new Error('Not implemented yet: "b" case') } case "c": { throw new Error('Not implemented yet: "c" case') } - default: break; + default: + break; } - `, + `, }, ], }, diff --git a/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot b/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot index 6146dadc128b..6f74ed0eb0ec 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot @@ -12,6 +12,10 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "description": "If 'true', allow 'default' cases on switch statements with exhaustive cases.", "type": "boolean" }, + "allowDefaultCaseMatchUnionMember": { + "description": "If 'true', the 'default' clause is used to determine whether the switch statement is Exhaustive for union type", + "type": "boolean" + }, "requireDefaultForNonUnion": { "description": "If 'true', require a 'default' clause for switches on non-union types.", "type": "boolean" @@ -28,6 +32,8 @@ type Options = [ { /** If 'true', allow 'default' cases on switch statements with exhaustive cases. */ allowDefaultCaseForExhaustiveSwitch?: boolean; + /** If 'true', the 'default' clause is used to determine whether the switch statement is Exhaustive for union type */ + allowDefaultCaseMatchUnionMember?: boolean; /** If 'true', require a 'default' clause for switches on non-union types. */ requireDefaultForNonUnion?: boolean; }, From 5ac803cc40794b9692e541ee282fd843e0dc931f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Mon, 9 Sep 2024 21:30:46 +0900 Subject: [PATCH 03/15] fix: lint error --- packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index a860ff1df2a1..7a5a1dc06c65 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -264,7 +264,7 @@ export default createRule({ .join('\n'); if (lastCase) { - const isLastCaseDefaultCase = lastCase.test === null; + const isLastCaseDefaultCase = lastCase.test == null; if (isLastCaseDefaultCase) { const beforeFixString = missingCases .map(code => `${code}\n${caseIndent}`) From fb1406aec35b926af77cfcea0d9ea7414a7fecfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Thu, 3 Oct 2024 21:15:55 +0900 Subject: [PATCH 04/15] fix: apply code reivew --- .../rules/switch-exhaustiveness-check.mdx | 6 +- .../src/rules/switch-exhaustiveness-check.ts | 16 +- .../rules/switch-exhaustiveness-check.test.ts | 149 +++++++++++++++++- 3 files changed, 156 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx index 1a1fb76496dd..c84c8efdb358 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx @@ -55,9 +55,11 @@ Since `value` is a non-union type it requires the switch case to have a default ### `allowDefaultCaseMatchUnionMember` -Defaults to true. If set to `true`, the `default` clause is used to determine whether the switch statement is Exhaustive for union type. +Defaults to true. Whether a `default` clause will be considered as making a switch statement over a union type exhaustive. -In a switch case, if you enter the default statement, it can be considered that Exhaustive is satisfied, but if you think that entering the default statement did not solve the exhaustive problem of the union type, please set to option `true`. +By default, if a `switch` statement over a union type includes a `default` case, then it's considered exhaustive. +`allowDefaultCaseMatchUnionMember` enforces explicitly handling every constituent of the union type with their own explicit `case`. +This can be useful if you want to make sure every value added to the union receives explicit handling, with the `default` case reserved for reporting an error. Examples of additional **incorrect** code for this rule with `{ allowDefaultCaseMatchUnionMember: true }`: diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index 7a5a1dc06c65..a72ece2796fe 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -38,11 +38,11 @@ type Options = [ requireDefaultForNonUnion?: boolean; /** - * If `true`, the `default` clause is used to determine whether the switch statement is Exhaustive for union type. + * If `true`, the `default` clause is used to determine whether the switch statement is exhaustive for union types. * - * @default true + * @default false */ - allowDefaultCaseMatchUnionMember?: boolean; + requireDefaultCaseForUnions?: boolean; }, ]; @@ -72,7 +72,7 @@ export default createRule({ description: `If 'true', require a 'default' clause for switches on non-union types.`, type: 'boolean', }, - allowDefaultCaseMatchUnionMember: { + requireDefaultCaseForUnions: { description: `If 'true', the 'default' clause is used to determine whether the switch statement is Exhaustive for union type`, type: 'boolean', }, @@ -92,7 +92,7 @@ export default createRule({ { allowDefaultCaseForExhaustiveSwitch: true, requireDefaultForNonUnion: false, - allowDefaultCaseMatchUnionMember: true, + requireDefaultCaseForUnions: false, }, ], create( @@ -101,7 +101,7 @@ export default createRule({ { allowDefaultCaseForExhaustiveSwitch, requireDefaultForNonUnion, - allowDefaultCaseMatchUnionMember, + requireDefaultCaseForUnions, }, ], ) { @@ -173,12 +173,12 @@ export default createRule({ const { missingLiteralBranchTypes, symbolName, defaultCase } = switchMetadata; - // We only trigger the rule if a `default` case does not exist when allowDefaultCaseMatchUnionMember option + // We only trigger the rule if a `default` case does not exist when requireDefaultCaseForUnions option // is `true`. because that would disqualify the switch statement from having cases that exactly // match the members of a union. if ( missingLiteralBranchTypes.length > 0 && - (allowDefaultCaseMatchUnionMember ? defaultCase === undefined : true) + (!requireDefaultCaseForUnions ? defaultCase === undefined : true) ) { context.report({ node: node.discriminant, 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 ad1966fbdfa1..04b71de3d2f4 100644 --- a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts +++ b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts @@ -823,7 +823,7 @@ switch (literal) { `, options: [ { - allowDefaultCaseMatchUnionMember: true, + requireDefaultCaseForUnions: true, }, ], }, @@ -841,7 +841,7 @@ switch (literal) { `, options: [ { - allowDefaultCaseMatchUnionMember: true, + requireDefaultCaseForUnions: true, }, ], }, @@ -857,11 +857,58 @@ switch (literal) { `, options: [ { - allowDefaultCaseMatchUnionMember: true, + requireDefaultCaseForUnions: true, allowDefaultCaseForExhaustiveSwitch: false, }, ], }, + { + code: ` +enum MyEnum { + Foo = 'Foo', + Bar = 'Bar', + Baz = 'Baz', +} + +declare const myEnum: MyEnum; + +switch (myEnum) { + case MyEnum.Foo: + break; + case MyEnum.Bar: + break; + case MyEnum.Baz: + break; + default: { + break; + } +} + `, + options: [ + { + requireDefaultCaseForUnions: true, + }, + ], + }, + { + code: ` +declare const value: boolean; +switch (value) { + case false: + break; + case true: + break; + default: { + break; + } +} + `, + options: [ + { + requireDefaultCaseForUnions: true, + }, + ], + }, ], invalid: [ { @@ -2438,7 +2485,7 @@ switch (literal) { `, options: [ { - allowDefaultCaseMatchUnionMember: false, + requireDefaultCaseForUnions: true, }, ], errors: [ @@ -2478,7 +2525,7 @@ switch (literal) { `, options: [ { - allowDefaultCaseMatchUnionMember: false, + requireDefaultCaseForUnions: true, }, ], errors: [ @@ -2499,6 +2546,98 @@ switch (literal) { case "c": { throw new Error('Not implemented yet: "c" case') } default: break; +} + `, + }, + ], + }, + ], + }, + { + code: ` +enum MyEnum { + Foo = 'Foo', + Bar = 'Bar', + Baz = 'Baz', +} + +declare const myEnum: MyEnum; + +switch (myEnum) { + case MyEnum.Foo: + break; + default: { + break; + } +} + `, + options: [ + { + requireDefaultCaseForUnions: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 10, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +enum MyEnum { + Foo = 'Foo', + Bar = 'Bar', + Baz = 'Baz', +} + +declare const myEnum: MyEnum; + +switch (myEnum) { + case MyEnum.Foo: + break; + case MyEnum.Bar: { throw new Error('Not implemented yet: MyEnum.Bar case') } + case MyEnum.Baz: { throw new Error('Not implemented yet: MyEnum.Baz case') } + default: { + break; + } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: boolean; +switch (value) { + default: { + break; + } +} + `, + options: [ + { + requireDefaultCaseForUnions: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: boolean; +switch (value) { + case false: { throw new Error('Not implemented yet: false case') } + case true: { throw new Error('Not implemented yet: true case') } + default: { + break; + } } `, }, From bef98233decf71a40e83dbf0fabcfb3bedb8915f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Fri, 11 Oct 2024 01:36:40 +0900 Subject: [PATCH 05/15] fix: reflect code review --- .../rules/switch-exhaustiveness-check.mdx | 3 +- .../src/rules/switch-exhaustiveness-check.ts | 24 +++--- .../rules/switch-exhaustiveness-check.test.ts | 74 +++++++++++++++++++ 3 files changed, 88 insertions(+), 13 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx index c84c8efdb358..90cb6d08979c 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx @@ -22,6 +22,7 @@ Defaults to true. If set to false, this rule will also report when a `switch` st 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. +If you want `default` to prevent `switch-exhaustiveness-check` when new values ​​are added to the union type, try using the `requireDefaultCaseForUnions` option #### `allowDefaultCaseForExhaustiveSwitch` Caveats @@ -55,7 +56,7 @@ Since `value` is a non-union type it requires the switch case to have a default ### `allowDefaultCaseMatchUnionMember` -Defaults to true. Whether a `default` clause will be considered as making a switch statement over a union type exhaustive. +Defaults to false. Whether a `default` clause will be considered as making a switch statement over a union type exhaustive. By default, if a `switch` statement over a union type includes a `default` case, then it's considered exhaustive. `allowDefaultCaseMatchUnionMember` enforces explicitly handling every constituent of the union type with their own explicit `case`. diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index a72ece2796fe..ba2648b2b8dd 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -73,7 +73,7 @@ export default createRule({ type: 'boolean', }, requireDefaultCaseForUnions: { - description: `If 'true', the 'default' clause is used to determine whether the switch statement is Exhaustive for union type`, + description: `If 'true', the 'default' clause is used to determine whether the switch statement is exhaustive for union type`, type: 'boolean', }, }, @@ -173,13 +173,13 @@ export default createRule({ const { missingLiteralBranchTypes, symbolName, defaultCase } = switchMetadata; - // We only trigger the rule if a `default` case does not exist when requireDefaultCaseForUnions option - // is `true`. because that would disqualify the switch statement from having cases that exactly - // match the members of a union. - if ( - missingLiteralBranchTypes.length > 0 && - (!requireDefaultCaseForUnions ? defaultCase === undefined : true) - ) { + // Unless requireDefaultCaseForUnions is enabled, the presence of a default case + // always makes the switch exhaustive. + if (!requireDefaultCaseForUnions && defaultCase != null) { + return; + } + + if (missingLiteralBranchTypes.length > 0) { context.report({ node: node.discriminant, messageId: 'switchIsNotExhaustive', @@ -217,6 +217,8 @@ export default createRule({ ): TSESLint.RuleFix { const lastCase = node.cases.length > 0 ? node.cases[node.cases.length - 1] : null; + const defaultCase = node.cases.find(caseEl => caseEl.test == null); + const caseIndent = lastCase ? ' '.repeat(lastCase.loc.start.column) : // If there are no cases, use indentation of the switch statement and @@ -264,15 +266,13 @@ export default createRule({ .join('\n'); if (lastCase) { - const isLastCaseDefaultCase = lastCase.test == null; - if (isLastCaseDefaultCase) { + if (defaultCase) { const beforeFixString = missingCases .map(code => `${code}\n${caseIndent}`) .join(''); - return fixer.insertTextBefore(lastCase, beforeFixString); + return fixer.insertTextBefore(defaultCase, beforeFixString); } - return fixer.insertTextAfter(lastCase, `\n${fixString}`); } 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 04b71de3d2f4..8aca9dc92023 100644 --- a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts +++ b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts @@ -2514,6 +2514,80 @@ switch (literal) { }, { code: ` +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; +} + `, + options: [ + { + requireDefaultCaseForUnions: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 4, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; + case "b": { throw new Error('Not implemented yet: "b" case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const literal: 'a' | 'b'; + +switch (literal) { + default: + case 'a': + break; +} + `, + options: [ + { + requireDefaultCaseForUnions: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 4, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const literal: 'a' | 'b'; + +switch (literal) { + case "b": { throw new Error('Not implemented yet: "b" case') } + default: + case 'a': + break; +} + `, + }, + ], + }, + ], + }, + { + code: ` declare const literal: 'a' | 'b' | 'c'; switch (literal) { From c76867cbe44e9be2497a68ec3fed8e1d4fab5757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Tue, 15 Oct 2024 22:49:51 +0900 Subject: [PATCH 06/15] docs: Remove unnecessary phrases --- .../eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx index 90cb6d08979c..582e65fafd51 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx @@ -75,8 +75,6 @@ switch (literal) { } ``` -Since `value` is a non-union type it requires the switch case to have a default clause only with `requireDefaultForNonUnion` enabled. - ## Examples When the switch doesn't have exhaustive cases, either filling them all out or adding a default will correct the rule's complaint. From e0c892eb3826bbf4705330261d93d53bf7d2bb8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Tue, 15 Oct 2024 23:05:32 +0900 Subject: [PATCH 07/15] docs: change option name --- .../rules/switch-exhaustiveness-check.mdx | 11 +++++----- .../src/rules/switch-exhaustiveness-check.ts | 12 +++++----- .../switch-exhaustiveness-check.shot | 2 +- .../rules/switch-exhaustiveness-check.test.ts | 22 +++++++++---------- .../switch-exhaustiveness-check.shot | 4 ++-- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx index 582e65fafd51..da62edf84fda 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx @@ -21,8 +21,7 @@ This rule reports when a `switch` statement over a value typed as a union of lit 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. -If you want `default` to prevent `switch-exhaustiveness-check` when new values ​​are added to the union type, try using the `requireDefaultCaseForUnions` option +Additionally, if a new value is added to the union type and you're using `[considerDefaultExhaustiveForUnions](#considerDefaultExhaustiveForUnions)`, a `default` would prevent the `switch-exhaustiveness-check` rule from reporting on the new case not being handled in the `switch` statement. #### `allowDefaultCaseForExhaustiveSwitch` Caveats @@ -54,17 +53,17 @@ switch (value) { Since `value` is a non-union type it requires the switch case to have a default clause only with `requireDefaultForNonUnion` enabled. -### `allowDefaultCaseMatchUnionMember` +### `considerDefaultExhaustiveForUnions` Defaults to false. Whether a `default` clause will be considered as making a switch statement over a union type exhaustive. By default, if a `switch` statement over a union type includes a `default` case, then it's considered exhaustive. -`allowDefaultCaseMatchUnionMember` enforces explicitly handling every constituent of the union type with their own explicit `case`. +`considerDefaultExhaustiveForUnions` enforces explicitly handling every constituent of the union type with their own explicit `case`. This can be useful if you want to make sure every value added to the union receives explicit handling, with the `default` case reserved for reporting an error. -Examples of additional **incorrect** code for this rule with `{ allowDefaultCaseMatchUnionMember: true }`: +Examples of additional **incorrect** code for this rule with `{ considerDefaultExhaustiveForUnions: true }`: -```ts option='{ "allowDefaultCaseMatchUnionMember": true }' showPlaygroundButton +```ts option='{ "considerDefaultExhaustiveForUnions": true }' showPlaygroundButton declare const literal: 'a' | 'b'; switch (literal) { diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index ba2648b2b8dd..93567e94a7e2 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -42,7 +42,7 @@ type Options = [ * * @default false */ - requireDefaultCaseForUnions?: boolean; + considerDefaultExhaustiveForUnions?: boolean; }, ]; @@ -72,7 +72,7 @@ export default createRule({ description: `If 'true', require a 'default' clause for switches on non-union types.`, type: 'boolean', }, - requireDefaultCaseForUnions: { + considerDefaultExhaustiveForUnions: { description: `If 'true', the 'default' clause is used to determine whether the switch statement is exhaustive for union type`, type: 'boolean', }, @@ -92,7 +92,7 @@ export default createRule({ { allowDefaultCaseForExhaustiveSwitch: true, requireDefaultForNonUnion: false, - requireDefaultCaseForUnions: false, + considerDefaultExhaustiveForUnions: false, }, ], create( @@ -101,7 +101,7 @@ export default createRule({ { allowDefaultCaseForExhaustiveSwitch, requireDefaultForNonUnion, - requireDefaultCaseForUnions, + considerDefaultExhaustiveForUnions, }, ], ) { @@ -173,9 +173,9 @@ export default createRule({ const { missingLiteralBranchTypes, symbolName, defaultCase } = switchMetadata; - // Unless requireDefaultCaseForUnions is enabled, the presence of a default case + // Unless considerDefaultExhaustiveForUnions is enabled, the presence of a default case // always makes the switch exhaustive. - if (!requireDefaultCaseForUnions && defaultCase != null) { + if (!considerDefaultExhaustiveForUnions && defaultCase != null) { return; } diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot index 1f5331ba4526..687080bdacbf 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot @@ -16,7 +16,7 @@ switch (value) { `; exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 2`] = ` -"Options: { "allowDefaultCaseMatchUnionMember": true } +"Options: { "considerDefaultExhaustiveForUnions": true } declare const literal: 'a' | 'b'; 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 8aca9dc92023..84849b579d84 100644 --- a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts +++ b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts @@ -823,7 +823,7 @@ switch (literal) { `, options: [ { - requireDefaultCaseForUnions: true, + considerDefaultExhaustiveForUnions: true, }, ], }, @@ -841,7 +841,7 @@ switch (literal) { `, options: [ { - requireDefaultCaseForUnions: true, + considerDefaultExhaustiveForUnions: true, }, ], }, @@ -857,7 +857,7 @@ switch (literal) { `, options: [ { - requireDefaultCaseForUnions: true, + considerDefaultExhaustiveForUnions: true, allowDefaultCaseForExhaustiveSwitch: false, }, ], @@ -886,7 +886,7 @@ switch (myEnum) { `, options: [ { - requireDefaultCaseForUnions: true, + considerDefaultExhaustiveForUnions: true, }, ], }, @@ -905,7 +905,7 @@ switch (value) { `, options: [ { - requireDefaultCaseForUnions: true, + considerDefaultExhaustiveForUnions: true, }, ], }, @@ -2485,7 +2485,7 @@ switch (literal) { `, options: [ { - requireDefaultCaseForUnions: true, + considerDefaultExhaustiveForUnions: true, }, ], errors: [ @@ -2523,7 +2523,7 @@ switch (literal) { `, options: [ { - requireDefaultCaseForUnions: true, + considerDefaultExhaustiveForUnions: true, }, ], errors: [ @@ -2560,7 +2560,7 @@ switch (literal) { `, options: [ { - requireDefaultCaseForUnions: true, + considerDefaultExhaustiveForUnions: true, }, ], errors: [ @@ -2599,7 +2599,7 @@ switch (literal) { `, options: [ { - requireDefaultCaseForUnions: true, + considerDefaultExhaustiveForUnions: true, }, ], errors: [ @@ -2647,7 +2647,7 @@ switch (myEnum) { `, options: [ { - requireDefaultCaseForUnions: true, + considerDefaultExhaustiveForUnions: true, }, ], errors: [ @@ -2693,7 +2693,7 @@ switch (value) { `, options: [ { - requireDefaultCaseForUnions: true, + considerDefaultExhaustiveForUnions: true, }, ], errors: [ diff --git a/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot b/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot index 6f74ed0eb0ec..ee81329c0527 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot @@ -12,7 +12,7 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "description": "If 'true', allow 'default' cases on switch statements with exhaustive cases.", "type": "boolean" }, - "allowDefaultCaseMatchUnionMember": { + "considerDefaultExhaustiveForUnions": { "description": "If 'true', the 'default' clause is used to determine whether the switch statement is Exhaustive for union type", "type": "boolean" }, @@ -33,7 +33,7 @@ type Options = [ /** If 'true', allow 'default' cases on switch statements with exhaustive cases. */ allowDefaultCaseForExhaustiveSwitch?: boolean; /** If 'true', the 'default' clause is used to determine whether the switch statement is Exhaustive for union type */ - allowDefaultCaseMatchUnionMember?: boolean; + considerDefaultExhaustiveForUnions?: boolean; /** If 'true', require a 'default' clause for switches on non-union types. */ requireDefaultForNonUnion?: boolean; }, From 55f761f09a3046aeb3ed04f9fabbdd8ee7bb2a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Wed, 16 Oct 2024 10:04:35 +0900 Subject: [PATCH 08/15] docs: considerDefaultExhaustiveForUnions option description edit --- .../eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx index da62edf84fda..528026238940 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx @@ -57,7 +57,7 @@ Since `value` is a non-union type it requires the switch case to have a default Defaults to false. Whether a `default` clause will be considered as making a switch statement over a union type exhaustive. -By default, if a `switch` statement over a union type includes a `default` case, then it's considered exhaustive. +If set to true, `switch` statement over a union type includes a `default` case is considered exhaustive. `considerDefaultExhaustiveForUnions` enforces explicitly handling every constituent of the union type with their own explicit `case`. This can be useful if you want to make sure every value added to the union receives explicit handling, with the `default` case reserved for reporting an error. From d795c4f4cee2c7edef1fc5d47629887311ae7175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Sat, 19 Oct 2024 22:03:27 +0900 Subject: [PATCH 09/15] docs: apply code reivew --- .../rules/switch-exhaustiveness-check.mdx | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx index 528026238940..a5b5542a316e 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx @@ -57,9 +57,35 @@ Since `value` is a non-union type it requires the switch case to have a default Defaults to false. Whether a `default` clause will be considered as making a switch statement over a union type exhaustive. -If set to true, `switch` statement over a union type includes a `default` case is considered exhaustive. -`considerDefaultExhaustiveForUnions` enforces explicitly handling every constituent of the union type with their own explicit `case`. -This can be useful if you want to make sure every value added to the union receives explicit handling, with the `default` case reserved for reporting an error. +If set to true, a `switch` statement over a union type that includes a `default` case is considered exhaustive. +Otherwise, the rule enforces explicitly handling every constituent of the union type with their own explicit `case`. +Keeping this option disabled can be useful if you want to make sure every value added to the union receives explicit handling, with the `default` case reserved for reporting an error. + +Examples of additional **correct** code for this rule with `{ considerDefaultExhaustiveForUnions: true }`: + +```ts option='{ "considerDefaultExhaustiveForUnions": true }' showPlaygroundButton +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; + case 'b': + break; + default: + break; +} +``` + +```ts option='{ "considerDefaultExhaustiveForUnions": true }' showPlaygroundButton +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; + case 'b': + break; +} +``` Examples of additional **incorrect** code for this rule with `{ considerDefaultExhaustiveForUnions: true }`: From 5b00672a5b506cf323305b2771d2c1b7f4a4ad07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Sat, 19 Oct 2024 23:56:47 +0900 Subject: [PATCH 10/15] fix: test and lint error --- .../src/rules/switch-exhaustiveness-check.ts | 12 ++-- .../switch-exhaustiveness-check.shot | 41 +++++++++-- .../rules/switch-exhaustiveness-check.test.ts | 72 +++++++++---------- .../switch-exhaustiveness-check.shot | 4 +- 4 files changed, 80 insertions(+), 49 deletions(-) diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index b675ed2178b5..6aa6f9f78fdb 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -77,13 +77,13 @@ export default createRule({ type: 'boolean', description: `If 'true', allow 'default' cases on switch statements with exhaustive cases.`, }, - requireDefaultForNonUnion: { - type: 'boolean', - description: `If 'true', require a 'default' clause for switches on non-union types.`, - }, considerDefaultExhaustiveForUnions: { + type: 'boolean', description: `If 'true', the 'default' clause is used to determine whether the switch statement is exhaustive for union type`, + }, + requireDefaultForNonUnion: { type: 'boolean', + description: `If 'true', require a 'default' clause for switches on non-union types.`, }, }, }, @@ -92,8 +92,8 @@ export default createRule({ defaultOptions: [ { allowDefaultCaseForExhaustiveSwitch: true, - requireDefaultForNonUnion: false, considerDefaultExhaustiveForUnions: false, + requireDefaultForNonUnion: false, }, ], create( @@ -101,8 +101,8 @@ export default createRule({ [ { allowDefaultCaseForExhaustiveSwitch, - requireDefaultForNonUnion, considerDefaultExhaustiveForUnions, + requireDefaultForNonUnion, }, ], ) { diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot index 687080bdacbf..e923a4feb9ff 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot @@ -23,6 +23,8 @@ declare const literal: 'a' | 'b'; switch (literal) { case 'a': break; + case 'b': + break; default: break; } @@ -30,6 +32,35 @@ switch (literal) { `; exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 3`] = ` +"Options: { "considerDefaultExhaustiveForUnions": true } + +declare const literal: 'a' | 'b'; + +switch (literal) { + case 'a': + break; + case 'b': + break; +} +" +`; + +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 4`] = ` +"Options: { "considerDefaultExhaustiveForUnions": true } + +declare const literal: 'a' | 'b'; + +switch (literal) { + ~~~~~~~ Switch is not exhaustive. Cases not matched: "b" + case 'a': + break; + default: + break; +} +" +`; + +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 5`] = ` "Incorrect type Day = @@ -53,7 +84,7 @@ switch (day) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 4`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 6`] = ` "Correct type Day = @@ -94,7 +125,7 @@ switch (day) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 5`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 7`] = ` "Correct type Day = @@ -119,7 +150,7 @@ switch (day) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 6`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 8`] = ` "Incorrect enum Fruit { @@ -139,7 +170,7 @@ switch (fruit) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 7`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 9`] = ` "Correct enum Fruit { @@ -166,7 +197,7 @@ switch (fruit) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 8`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 10`] = ` "Correct enum Fruit { 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 f64cc3e5d161..58af4f2ea90f 100644 --- a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts +++ b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts @@ -856,8 +856,8 @@ switch (literal) { `, options: [ { - considerDefaultExhaustiveForUnions: true, allowDefaultCaseForExhaustiveSwitch: false, + considerDefaultExhaustiveForUnions: true, }, ], }, @@ -2482,16 +2482,11 @@ switch (literal) { break; } `, - options: [ - { - considerDefaultExhaustiveForUnions: true, - }, - ], errors: [ { - messageId: 'switchIsNotExhaustive', - line: 4, column: 9, + line: 4, + messageId: 'switchIsNotExhaustive', suggestions: [ { messageId: 'addMissingCases', @@ -2510,6 +2505,11 @@ switch (literal) { ], }, ], + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], }, { code: ` @@ -2520,16 +2520,11 @@ switch (literal) { break; } `, - options: [ - { - considerDefaultExhaustiveForUnions: true, - }, - ], errors: [ { - messageId: 'switchIsNotExhaustive', - line: 4, column: 9, + line: 4, + messageId: 'switchIsNotExhaustive', suggestions: [ { messageId: 'addMissingCases', @@ -2546,6 +2541,11 @@ switch (literal) { ], }, ], + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], }, { code: ` @@ -2557,16 +2557,11 @@ switch (literal) { break; } `, - options: [ - { - considerDefaultExhaustiveForUnions: true, - }, - ], errors: [ { - messageId: 'switchIsNotExhaustive', - line: 4, column: 9, + line: 4, + messageId: 'switchIsNotExhaustive', suggestions: [ { messageId: 'addMissingCases', @@ -2584,6 +2579,11 @@ switch (literal) { ], }, ], + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], }, { code: ` @@ -2644,16 +2644,11 @@ switch (myEnum) { } } `, - options: [ - { - considerDefaultExhaustiveForUnions: true, - }, - ], errors: [ { - messageId: 'switchIsNotExhaustive', - line: 10, column: 9, + line: 10, + messageId: 'switchIsNotExhaustive', suggestions: [ { messageId: 'addMissingCases', @@ -2680,6 +2675,11 @@ switch (myEnum) { ], }, ], + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], }, { code: ` @@ -2690,16 +2690,11 @@ switch (value) { } } `, - options: [ - { - considerDefaultExhaustiveForUnions: true, - }, - ], errors: [ { - messageId: 'switchIsNotExhaustive', - line: 3, column: 9, + line: 3, + messageId: 'switchIsNotExhaustive', suggestions: [ { messageId: 'addMissingCases', @@ -2717,6 +2712,11 @@ switch (value) { ], }, ], + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], }, ], }); diff --git a/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot b/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot index ee81329c0527..05cbeedd6162 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/switch-exhaustiveness-check.shot @@ -13,7 +13,7 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "type": "boolean" }, "considerDefaultExhaustiveForUnions": { - "description": "If 'true', the 'default' clause is used to determine whether the switch statement is Exhaustive for union type", + "description": "If 'true', the 'default' clause is used to determine whether the switch statement is exhaustive for union type", "type": "boolean" }, "requireDefaultForNonUnion": { @@ -32,7 +32,7 @@ type Options = [ { /** If 'true', allow 'default' cases on switch statements with exhaustive cases. */ allowDefaultCaseForExhaustiveSwitch?: boolean; - /** If 'true', the 'default' clause is used to determine whether the switch statement is Exhaustive for union type */ + /** If 'true', the 'default' clause is used to determine whether the switch statement is exhaustive for union type */ considerDefaultExhaustiveForUnions?: boolean; /** If 'true', require a 'default' clause for switches on non-union types. */ requireDefaultForNonUnion?: boolean; From d594720699e006f42d3c2a410b54677d85f00845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Sun, 20 Oct 2024 00:05:42 +0900 Subject: [PATCH 11/15] fix:lint error --- .../rules/switch-exhaustiveness-check.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 58af4f2ea90f..3bcbd37a491a 100644 --- a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts +++ b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts @@ -2596,16 +2596,11 @@ switch (literal) { break; } `, - options: [ - { - considerDefaultExhaustiveForUnions: true, - }, - ], errors: [ { - messageId: 'switchIsNotExhaustive', - line: 4, column: 9, + line: 4, + messageId: 'switchIsNotExhaustive', suggestions: [ { messageId: 'addMissingCases', @@ -2625,6 +2620,11 @@ switch (literal) { ], }, ], + options: [ + { + considerDefaultExhaustiveForUnions: true, + }, + ], }, { code: ` From 718c6bb79121ec7740021fd41006e63828a05fb2 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sun, 27 Oct 2024 00:50:54 -0600 Subject: [PATCH 12/15] fix heading issue --- .../eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx index abe9f7b4d1ee..018dc01b3a28 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx @@ -59,7 +59,7 @@ Since `value` is a non-union type it requires the switch case to have a default ### `considerDefaultExhaustiveForUnions` -Defaults to false. Whether a `default` clause will be considered as making a switch statement over a union type exhaustive. +{/* insert option description */} If set to true, a `switch` statement over a union type that includes a `default` case is considered exhaustive. Otherwise, the rule enforces explicitly handling every constituent of the union type with their own explicit `case`. From 1203a7ce5226dd1592c6d1e3daa5a40b299250cb Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sun, 27 Oct 2024 00:55:25 -0600 Subject: [PATCH 13/15] refactor to tabs --- .../rules/switch-exhaustiveness-check.mdx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx index 018dc01b3a28..309179b8bc93 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx @@ -65,7 +65,10 @@ If set to true, a `switch` statement over a union type that includes a `default` Otherwise, the rule enforces explicitly handling every constituent of the union type with their own explicit `case`. Keeping this option disabled can be useful if you want to make sure every value added to the union receives explicit handling, with the `default` case reserved for reporting an error. -Examples of additional **correct** code for this rule with `{ considerDefaultExhaustiveForUnions: true }`: +Examples of code with `{ considerDefaultExhaustiveForUnions: true }`: + + + ```ts option='{ "considerDefaultExhaustiveForUnions": true }' showPlaygroundButton declare const literal: 'a' | 'b'; @@ -73,13 +76,15 @@ declare const literal: 'a' | 'b'; switch (literal) { case 'a': break; - case 'b': - break; default: break; } ``` + + + + ```ts option='{ "considerDefaultExhaustiveForUnions": true }' showPlaygroundButton declare const literal: 'a' | 'b'; @@ -88,22 +93,21 @@ switch (literal) { break; case 'b': break; + default: + break; } -``` - -Examples of additional **incorrect** code for this rule with `{ considerDefaultExhaustiveForUnions: true }`: - -```ts option='{ "considerDefaultExhaustiveForUnions": true }' showPlaygroundButton -declare const literal: 'a' | 'b'; switch (literal) { case 'a': break; - default: + case 'b': break; } ``` + + + ## Examples When the switch doesn't have exhaustive cases, either filling them all out or adding a default will correct the rule's complaint. From 3e1a24e121cb8b02169c4b8028549a6a3c8be1c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Sun, 27 Oct 2024 11:00:08 -0400 Subject: [PATCH 14/15] Update packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx --- .../eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx index 309179b8bc93..e35aaed0d072 100644 --- a/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx +++ b/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.mdx @@ -23,7 +23,7 @@ This rule reports when a `switch` statement over a value typed as a union of lit 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 and you're using `[considerDefaultExhaustiveForUnions](#considerDefaultExhaustiveForUnions)`, a `default` would prevent the `switch-exhaustiveness-check` rule from reporting on the new case not being handled in the `switch` statement. +Additionally, if a new value is added to the union type and you're using [`considerDefaultExhaustiveForUnions`](#considerDefaultExhaustiveForUnions), a `default` would prevent the `switch-exhaustiveness-check` rule from reporting on the new case not being handled in the `switch` statement. #### `allowDefaultCaseForExhaustiveSwitch` Caveats From 76c8716fe81931eb836818544e9fbf3ad6dd3cd3 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 27 Oct 2024 11:02:53 -0400 Subject: [PATCH 15/15] fix text snapshots --- .../switch-exhaustiveness-check.shot | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot index e923a4feb9ff..f763ba7e0ed7 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/switch-exhaustiveness-check.shot @@ -16,15 +16,15 @@ switch (value) { `; exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 2`] = ` -"Options: { "considerDefaultExhaustiveForUnions": true } +"Incorrect +Options: { "considerDefaultExhaustiveForUnions": true } declare const literal: 'a' | 'b'; switch (literal) { + ~~~~~~~ Switch is not exhaustive. Cases not matched: "b" case 'a': break; - case 'b': - break; default: break; } @@ -32,7 +32,8 @@ switch (literal) { `; exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 3`] = ` -"Options: { "considerDefaultExhaustiveForUnions": true } +"Correct +Options: { "considerDefaultExhaustiveForUnions": true } declare const literal: 'a' | 'b'; @@ -41,26 +42,20 @@ switch (literal) { break; case 'b': break; + default: + break; } -" -`; - -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 4`] = ` -"Options: { "considerDefaultExhaustiveForUnions": true } - -declare const literal: 'a' | 'b'; switch (literal) { - ~~~~~~~ Switch is not exhaustive. Cases not matched: "b" case 'a': break; - default: + case 'b': break; } " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 5`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 4`] = ` "Incorrect type Day = @@ -84,7 +79,7 @@ switch (day) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 6`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 5`] = ` "Correct type Day = @@ -125,7 +120,7 @@ switch (day) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 7`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 6`] = ` "Correct type Day = @@ -150,7 +145,7 @@ switch (day) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 8`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 7`] = ` "Incorrect enum Fruit { @@ -170,7 +165,7 @@ switch (fruit) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 9`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 8`] = ` "Correct enum Fruit { @@ -197,7 +192,7 @@ switch (fruit) { " `; -exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 10`] = ` +exports[`Validating rule docs switch-exhaustiveness-check.mdx code examples ESLint output 9`] = ` "Correct enum Fruit {