diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index c6ed86463ca0..3247982dc2ad 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -14,9 +14,8 @@ import { interface SwitchMetadata { readonly symbolName: string | undefined; - readonly missingBranchTypes: ts.Type[]; readonly defaultCase: TSESTree.SwitchCase | undefined; - readonly isUnion: boolean; + readonly missingLiteralBranchTypes: ts.Type[]; readonly containsNonLiteralType: boolean; } @@ -109,16 +108,6 @@ export default createRule({ const containsNonLiteralType = doesTypeContainNonLiteralType(discriminantType); - if (!discriminantType.isUnion()) { - return { - symbolName, - missingBranchTypes: [], - defaultCase, - isUnion: false, - containsNonLiteralType, - }; - } - const caseTypes = new Set(); for (const switchCase of node.cases) { // If the `test` property of the switch case is `null`, then we are on a @@ -134,54 +123,47 @@ export default createRule({ caseTypes.add(caseType); } - const unionTypes = tsutils.unionTypeParts(discriminantType); - const missingBranchTypes = unionTypes.filter( - unionType => !caseTypes.has(unionType), - ); + const missingLiteralBranchTypes: ts.Type[] = []; + + for (const unionPart of tsutils.unionTypeParts(discriminantType)) { + for (const intersectionPart of tsutils.intersectionTypeParts( + unionPart, + )) { + if ( + caseTypes.has(intersectionPart) || + !isTypeLiteralLikeType(intersectionPart) + ) { + continue; + } + + missingLiteralBranchTypes.push(intersectionPart); + } + } return { symbolName, - missingBranchTypes, + missingLiteralBranchTypes, defaultCase, - isUnion: true, containsNonLiteralType, }; } - /** - * For example: - * - * - `"foo" | "bar"` is a type with all literal types. - * - `"foo" | number` is a type that contains non-literal types. - * - * Default cases are never superfluous in switches with non-literal types. - */ - function doesTypeContainNonLiteralType(type: ts.Type): boolean { - const types = tsutils.unionTypeParts(type); - return types.some( - type => - !isFlagSet( - type.getFlags(), - ts.TypeFlags.Literal | ts.TypeFlags.Undefined | ts.TypeFlags.Null, - ), - ); - } - function checkSwitchExhaustive( node: TSESTree.SwitchStatement, switchMetadata: SwitchMetadata, ): void { - const { missingBranchTypes, symbolName, defaultCase } = switchMetadata; + 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 // match the members of a union. - if (missingBranchTypes.length > 0 && defaultCase === undefined) { + if (missingLiteralBranchTypes.length > 0 && defaultCase === undefined) { context.report({ node: node.discriminant, messageId: 'switchIsNotExhaustive', data: { - missingBranches: missingBranchTypes + missingBranches: missingLiteralBranchTypes .map(missingType => tsutils.isTypeFlagSet(missingType, ts.TypeFlags.ESSymbolLike) ? `typeof ${missingType.getSymbol()?.escapedName as string}` @@ -196,7 +178,7 @@ export default createRule({ return fixSwitch( fixer, node, - missingBranchTypes, + missingLiteralBranchTypes, symbolName?.toString(), ); }, @@ -227,24 +209,13 @@ export default createRule({ continue; } - // While running this rule on the "checker.ts" file of TypeScript, the - // the fix introduced a compiler error due to: - // - // ```ts - // type __String = (string & { - // __escapedIdentifier: void; - // }) | (void & { - // __escapedIdentifier: void; - // }) | InternalSymbolName; - // ``` - // - // The following check fixes it. - if (missingBranchType.isIntersection()) { - continue; - } - const missingBranchName = missingBranchType.getSymbol()?.escapedName; - let caseTest = checker.typeToString(missingBranchType); + let caseTest = tsutils.isTypeFlagSet( + missingBranchType, + ts.TypeFlags.ESSymbolLike, + ) + ? missingBranchName! + : checker.typeToString(missingBranchType); if ( symbolName && @@ -298,11 +269,11 @@ export default createRule({ return; } - const { missingBranchTypes, defaultCase, containsNonLiteralType } = + const { missingLiteralBranchTypes, defaultCase, containsNonLiteralType } = switchMetadata; if ( - missingBranchTypes.length === 0 && + missingLiteralBranchTypes.length === 0 && defaultCase !== undefined && !containsNonLiteralType ) { @@ -321,9 +292,9 @@ export default createRule({ return; } - const { isUnion, defaultCase } = switchMetadata; + const { defaultCase, containsNonLiteralType } = switchMetadata; - if (!isUnion && defaultCase === undefined) { + if (containsNonLiteralType && defaultCase === undefined) { context.report({ node: node.discriminant, messageId: 'switchIsNotExhaustive', @@ -354,6 +325,31 @@ export default createRule({ }, }); -function isFlagSet(flags: number, flag: number): boolean { - return (flags & flag) !== 0; +function isTypeLiteralLikeType(type: ts.Type): boolean { + return tsutils.isTypeFlagSet( + type, + ts.TypeFlags.Literal | + ts.TypeFlags.Undefined | + ts.TypeFlags.Null | + ts.TypeFlags.UniqueESSymbol, + ); +} + +/** + * For example: + * + * - `"foo" | "bar"` is a type with all literal types. + * - `"foo" | number` is a type that contains non-literal types. + * - `"foo" & { bar: 1 }` is a type that contains non-literal types. + * + * Default cases are never superfluous in switches with non-literal types. + */ +function doesTypeContainNonLiteralType(type: ts.Type): boolean { + return tsutils + .unionTypeParts(type) + .some(type => + tsutils + .intersectionTypeParts(type) + .every(subType => !isTypeLiteralLikeType(subType)), + ); } 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 5d58d0576052..b7919942e011 100644 --- a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts +++ b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts @@ -333,8 +333,1203 @@ switch (value) { }, ], }, + { + code: ` +declare const value: 'literal'; +switch (value) { + case 'literal': + return 0; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +declare const value: null; +switch (value) { + case null: + return 0; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +declare const value: undefined; +switch (value) { + case undefined: + return 0; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +declare const value: null | undefined; +switch (value) { + case null: + return 0; + case undefined: + return 0; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +declare const value: 'literal' & { _brand: true }; +switch (value) { + case 'literal': + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +declare const value: ('literal' & { _brand: true }) | 1; +switch (value) { + case 'literal': + break; + case 1: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +declare const value: (1 & { _brand: true }) | 'literal' | null; +switch (value) { + case 'literal': + break; + case 1: + break; + case null: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +declare const value: '1' | '2' | number; +switch (value) { + case '1': + break; + case '2': + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +declare const value: '1' | '2' | number; +switch (value) { + case '1': + break; + case '2': + break; + default: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +declare const value: '1' | '2' | number; +switch (value) { + case '1': + break; + case '2': + break; + default: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +declare const value: '1' | '2' | (number & { foo: 'bar' }); +switch (value) { + case '1': + break; + case '2': + break; + default: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +declare const value: '1' | '2' | number; +switch (value) { + case '1': + break; + case '2': + break; + default: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +declare const value: number | null | undefined; +switch (value) { + case null: + break; + case undefined: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +declare const value: '1' | '2' | number; +switch (value) { + case '1': + break; + default: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +declare const value: (string & { foo: 'bar' }) | '1'; +switch (value) { + case '1': + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +const a = Symbol('a'); +declare const value: typeof a | 2; +switch (value) { + case a: + break; + case 2: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +declare const value: string | number; +switch (value) { + case 1: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +declare const value: string | number; +switch (value) { +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +declare const value: string | number; +switch (value) { + default: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +declare const value: number; +declare const a: number; +switch (value) { + case a: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +declare const value: bigint; +switch (value) { + case 10n: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +declare const value: symbol; +const a = Symbol('a'); +switch (value) { + case a: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +declare const value: symbol; +const a = Symbol('a'); +switch (value) { + case a: + break; + default: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +const a = Symbol('a'); +declare const value: typeof a | string; +switch (value) { + case a: + break; + default: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +const a = Symbol('a'); +declare const value: typeof a | string; +switch (value) { + default: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +declare const value: boolean | 1; +switch (value) { + case 1: + break; + default: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + }, + { + code: ` +declare const value: boolean | 1; +switch (value) { + case 1: + break; + case true: + break; + case false: + break; + default: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: false, + }, + ], + }, + { + code: ` +enum Aaa { + Foo, + Bar, +} +declare const value: Aaa | 1; +switch (value) { + case 1: + break; + case Aaa.Foo: + break; + case Aaa.Bar: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: false, + }, + ], + }, ], invalid: [ + { + code: ` +declare const value: 'literal'; +switch (value) { +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: 'literal'; +switch (value) { +case "literal": { throw new Error('Not implemented yet: "literal" case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: 'literal' & { _brand: true }; +switch (value) { +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: 'literal' & { _brand: true }; +switch (value) { +case "literal": { throw new Error('Not implemented yet: "literal" case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: ('literal' & { _brand: true }) | 1; +switch (value) { + case 'literal': + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: ('literal' & { _brand: true }) | 1; +switch (value) { + case 'literal': + break; + case 1: { throw new Error('Not implemented yet: 1 case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: '1' | '2' | number; +switch (value) { + case '1': + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: false, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: '1' | '2' | number; +switch (value) { + case '1': + break; + case "2": { throw new Error('Not implemented yet: "2" case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: '1' | '2' | number; +switch (value) { + case '1': + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: '1' | '2' | number; +switch (value) { + case '1': + break; + case "2": { throw new Error('Not implemented yet: "2" case') } +} + `, + }, + ], + }, + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: '1' | '2' | number; +switch (value) { + case '1': + break; + default: { throw new Error('default case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: (string & { foo: 'bar' }) | '1'; +switch (value) { + case '1': + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: (string & { foo: 'bar' }) | '1'; +switch (value) { + case '1': + break; + default: { throw new Error('default case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: (string & { foo: 'bar' }) | '1' | 1 | null | undefined; +switch (value) { +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: (string & { foo: 'bar' }) | '1' | 1 | null | undefined; +switch (value) { +case undefined: { throw new Error('Not implemented yet: undefined case') } +case null: { throw new Error('Not implemented yet: null case') } +case "1": { throw new Error('Not implemented yet: "1" case') } +case 1: { throw new Error('Not implemented yet: 1 case') } +} + `, + }, + ], + }, + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: (string & { foo: 'bar' }) | '1' | 1 | null | undefined; +switch (value) { +default: { throw new Error('default case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: string | number; +switch (value) { + case 1: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: string | number; +switch (value) { + case 1: + break; + default: { throw new Error('default case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: number; +declare const a: number; +switch (value) { + case a: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 4, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: number; +declare const a: number; +switch (value) { + case a: + break; + default: { throw new Error('default case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: bigint; +switch (value) { + case 10n: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: bigint; +switch (value) { + case 10n: + break; + default: { throw new Error('default case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: symbol; +const a = Symbol('a'); +switch (value) { + case a: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 4, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: symbol; +const a = Symbol('a'); +switch (value) { + case a: + break; + default: { throw new Error('default case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +const a = Symbol('aa'); +const b = Symbol('bb'); +declare const value: typeof a | typeof b | 1; +switch (value) { + case 1: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 5, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +const a = Symbol('aa'); +const b = Symbol('bb'); +declare const value: typeof a | typeof b | 1; +switch (value) { + case 1: + break; + case a: { throw new Error('Not implemented yet: a case') } + case b: { throw new Error('Not implemented yet: b case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +const a = Symbol('a'); +declare const value: typeof a | string; +switch (value) { + case a: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 4, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +const a = Symbol('a'); +declare const value: typeof a | string; +switch (value) { + case a: + break; + default: { throw new Error('default case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: boolean; +switch (value) { +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: false, + }, + ], + 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') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: boolean | 1; +switch (value) { + case false: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: boolean | 1; +switch (value) { + case false: + break; + case true: { throw new Error('Not implemented yet: true case') } + case 1: { throw new Error('Not implemented yet: 1 case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: boolean | number; +switch (value) { + case 1: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: boolean | number; +switch (value) { + case 1: + break; + case false: { throw new Error('Not implemented yet: false case') } + case true: { throw new Error('Not implemented yet: true case') } +} + `, + }, + ], + }, + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: boolean | number; +switch (value) { + case 1: + break; + default: { throw new Error('default case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +declare const value: object; +switch (value) { + case 1: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: false, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 3, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +declare const value: object; +switch (value) { + case 1: + break; + default: { throw new Error('default case') } +} + `, + }, + ], + }, + ], + }, + { + code: ` +enum Aaa { + Foo, + Bar, +} +declare const value: Aaa | 1 | string; +switch (value) { + case 1: + break; + case Aaa.Foo: + break; +} + `, + options: [ + { + allowDefaultCaseForExhaustiveSwitch: true, + requireDefaultForNonUnion: true, + }, + ], + errors: [ + { + messageId: 'switchIsNotExhaustive', + line: 7, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +enum Aaa { + Foo, + Bar, +} +declare const value: Aaa | 1 | string; +switch (value) { + case 1: + break; + case Aaa.Foo: + break; + case Aaa.Bar: { throw new Error('Not implemented yet: Aaa.Bar case') } +} + `, + }, + ], + }, + { + messageId: 'switchIsNotExhaustive', + line: 7, + column: 9, + suggestions: [ + { + messageId: 'addMissingCases', + output: ` +enum Aaa { + Foo, + Bar, +} +declare const value: Aaa | 1 | string; +switch (value) { + case 1: + break; + case Aaa.Foo: + break; + default: { throw new Error('default case') } +} + `, + }, + ], + }, + ], + }, { // Matched only one branch out of seven. code: ` @@ -497,32 +1692,6 @@ switch (day) { }, ], }, - { - // Still complains with union intersection part - code: ` -type FooBar = (string & { foo: void }) | 'bar'; - -const foobar = 'bar' as FooBar; -let result = 0; - -switch (foobar) { - case 'bar': { - result = 42; - break; - } -} - `, - errors: [ - { - messageId: 'switchIsNotExhaustive', - line: 7, - column: 9, - data: { - missingBranches: 'string & { foo: void; }', - }, - }, - ], - }, { code: ` const a = Symbol('a');