diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index d9e14bcf331..d8a7750efba 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -32,6 +32,25 @@ export default createRule({ const sourceCode = context.getSourceCode(); const service = getParserServices(context); const checker = service.program.getTypeChecker(); + const compilerOptions = service.program.getCompilerOptions(); + + function requiresQuoting(name: string): boolean { + if (name.length === 0) { + return true; + } + + if (!ts.isIdentifierStart(name.charCodeAt(0), compilerOptions.target)) { + return true; + } + + for (let i = 1; i < name.length; i += 1) { + if (!ts.isIdentifierPart(name.charCodeAt(i), compilerOptions.target)) { + return true; + } + } + + return false; + } function getNodeType(node: TSESTree.Node): ts.Type { const tsNode = service.esTreeNodeToTSNodeMap.get(node); @@ -42,6 +61,7 @@ export default createRule({ fixer: TSESLint.RuleFixer, node: TSESTree.SwitchStatement, missingBranchTypes: Array, + symbolName?: string, ): TSESLint.RuleFix | null { const lastCase = node.cases.length > 0 ? node.cases[node.cases.length - 1] : null; @@ -67,7 +87,17 @@ export default createRule({ continue; } - const caseTest = checker.typeToString(missingBranchType); + const missingBranchName = missingBranchType.getSymbol()?.escapedName; + let caseTest = checker.typeToString(missingBranchType); + + if ( + symbolName && + (missingBranchName || missingBranchName === '') && + requiresQuoting(missingBranchName.toString()) + ) { + caseTest = `${symbolName}['${missingBranchName}']`; + } + const errorMessage = `Not implemented yet: ${caseTest} case`; missingCases.push( @@ -101,6 +131,7 @@ export default createRule({ function checkSwitchExhaustive(node: TSESTree.SwitchStatement): void { const discriminantType = getNodeType(node.discriminant); + const symbolName = discriminantType.getSymbol()?.escapedName; if (discriminantType.isUnion()) { const unionTypes = unionTypeParts(discriminantType); @@ -139,7 +170,12 @@ export default createRule({ { messageId: 'addMissingCases', fix(fixer): TSESLint.RuleFix | null { - return fixSwitch(fixer, node, missingBranchTypes); + return fixSwitch( + fixer, + node, + missingBranchTypes, + symbolName?.toString(), + ); }, }, ], 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 3eb47fb6eb9..415afc20fc1 100644 --- a/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts +++ b/packages/eslint-plugin/tests/rules/switch-exhaustiveness-check.test.ts @@ -483,6 +483,117 @@ function test(value: T): number { case 1: { throw new Error('Not implemented yet: 1 case') } case 2: { throw new Error('Not implemented yet: 2 case') } } +} + `.trimRight(), + }, + ], + }, + ], + }, + { + // keys include special characters + code: ` +export enum Enum { + 'test-test' = 'test-test', + 'test' = 'test', +} + +function test(arg: Enum): string { + switch (arg) { + } +} + `.trimRight(), + errors: [ + { + messageId: 'switchIsNotExhaustive', + suggestions: [ + { + messageId: 'addMissingCases', + output: noFormat` +export enum Enum { + 'test-test' = 'test-test', + 'test' = 'test', +} + +function test(arg: Enum): string { + switch (arg) { + 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') } + } +} + `.trimRight(), + }, + ], + }, + ], + }, + { + // keys include empty string + code: ` +export enum Enum { + '' = 'test-test', + 'test' = 'test', +} + +function test(arg: Enum): string { + switch (arg) { + } +} + `.trimRight(), + errors: [ + { + messageId: 'switchIsNotExhaustive', + suggestions: [ + { + messageId: 'addMissingCases', + output: noFormat` +export enum Enum { + '' = 'test-test', + 'test' = 'test', +} + +function test(arg: Enum): string { + switch (arg) { + case Enum['']: { throw new Error('Not implemented yet: Enum[''] case') } + case Enum.test: { throw new Error('Not implemented yet: Enum.test case') } + } +} + `.trimRight(), + }, + ], + }, + ], + }, + { + // keys include number as first character + code: ` +export enum Enum { + '9test' = 'test-test', + 'test' = 'test', +} + +function test(arg: Enum): string { + switch (arg) { + } +} + `.trimRight(), + errors: [ + { + messageId: 'switchIsNotExhaustive', + suggestions: [ + { + messageId: 'addMissingCases', + output: noFormat` +export enum Enum { + '9test' = 'test-test', + 'test' = 'test', +} + +function test(arg: Enum): string { + switch (arg) { + case Enum['9test']: { throw new Error('Not implemented yet: Enum['9test'] case') } + case Enum.test: { throw new Error('Not implemented yet: Enum.test case') } + } } `.trimRight(), },