Skip to content

Commit 98ab010

Browse files
authored
fix(eslint-plugin): [switch-exhaustiveness-check] handle special characters in enum keys (typescript-eslint#2207)
1 parent 742b679 commit 98ab010

File tree

2 files changed

+149
-2
lines changed

2 files changed

+149
-2
lines changed

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

+38-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@ export default createRule({
3232
const sourceCode = context.getSourceCode();
3333
const service = getParserServices(context);
3434
const checker = service.program.getTypeChecker();
35+
const compilerOptions = service.program.getCompilerOptions();
36+
37+
function requiresQuoting(name: string): boolean {
38+
if (name.length === 0) {
39+
return true;
40+
}
41+
42+
if (!ts.isIdentifierStart(name.charCodeAt(0), compilerOptions.target)) {
43+
return true;
44+
}
45+
46+
for (let i = 1; i < name.length; i += 1) {
47+
if (!ts.isIdentifierPart(name.charCodeAt(i), compilerOptions.target)) {
48+
return true;
49+
}
50+
}
51+
52+
return false;
53+
}
3554

3655
function getNodeType(node: TSESTree.Node): ts.Type {
3756
const tsNode = service.esTreeNodeToTSNodeMap.get(node);
@@ -42,6 +61,7 @@ export default createRule({
4261
fixer: TSESLint.RuleFixer,
4362
node: TSESTree.SwitchStatement,
4463
missingBranchTypes: Array<ts.Type>,
64+
symbolName?: string,
4565
): TSESLint.RuleFix | null {
4666
const lastCase =
4767
node.cases.length > 0 ? node.cases[node.cases.length - 1] : null;
@@ -67,7 +87,17 @@ export default createRule({
6787
continue;
6888
}
6989

70-
const caseTest = checker.typeToString(missingBranchType);
90+
const missingBranchName = missingBranchType.getSymbol()?.escapedName;
91+
let caseTest = checker.typeToString(missingBranchType);
92+
93+
if (
94+
symbolName &&
95+
(missingBranchName || missingBranchName === '') &&
96+
requiresQuoting(missingBranchName.toString())
97+
) {
98+
caseTest = `${symbolName}['${missingBranchName}']`;
99+
}
100+
71101
const errorMessage = `Not implemented yet: ${caseTest} case`;
72102

73103
missingCases.push(
@@ -101,6 +131,7 @@ export default createRule({
101131

102132
function checkSwitchExhaustive(node: TSESTree.SwitchStatement): void {
103133
const discriminantType = getNodeType(node.discriminant);
134+
const symbolName = discriminantType.getSymbol()?.escapedName;
104135

105136
if (discriminantType.isUnion()) {
106137
const unionTypes = unionTypeParts(discriminantType);
@@ -139,7 +170,12 @@ export default createRule({
139170
{
140171
messageId: 'addMissingCases',
141172
fix(fixer): TSESLint.RuleFix | null {
142-
return fixSwitch(fixer, node, missingBranchTypes);
173+
return fixSwitch(
174+
fixer,
175+
node,
176+
missingBranchTypes,
177+
symbolName?.toString(),
178+
);
143179
},
144180
},
145181
],

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

+111
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,117 @@ function test(value: T): number {
483483
case 1: { throw new Error('Not implemented yet: 1 case') }
484484
case 2: { throw new Error('Not implemented yet: 2 case') }
485485
}
486+
}
487+
`.trimRight(),
488+
},
489+
],
490+
},
491+
],
492+
},
493+
{
494+
// keys include special characters
495+
code: `
496+
export enum Enum {
497+
'test-test' = 'test-test',
498+
'test' = 'test',
499+
}
500+
501+
function test(arg: Enum): string {
502+
switch (arg) {
503+
}
504+
}
505+
`.trimRight(),
506+
errors: [
507+
{
508+
messageId: 'switchIsNotExhaustive',
509+
suggestions: [
510+
{
511+
messageId: 'addMissingCases',
512+
output: noFormat`
513+
export enum Enum {
514+
'test-test' = 'test-test',
515+
'test' = 'test',
516+
}
517+
518+
function test(arg: Enum): string {
519+
switch (arg) {
520+
case Enum['test-test']: { throw new Error('Not implemented yet: Enum['test-test'] case') }
521+
case Enum.test: { throw new Error('Not implemented yet: Enum.test case') }
522+
}
523+
}
524+
`.trimRight(),
525+
},
526+
],
527+
},
528+
],
529+
},
530+
{
531+
// keys include empty string
532+
code: `
533+
export enum Enum {
534+
'' = 'test-test',
535+
'test' = 'test',
536+
}
537+
538+
function test(arg: Enum): string {
539+
switch (arg) {
540+
}
541+
}
542+
`.trimRight(),
543+
errors: [
544+
{
545+
messageId: 'switchIsNotExhaustive',
546+
suggestions: [
547+
{
548+
messageId: 'addMissingCases',
549+
output: noFormat`
550+
export enum Enum {
551+
'' = 'test-test',
552+
'test' = 'test',
553+
}
554+
555+
function test(arg: Enum): string {
556+
switch (arg) {
557+
case Enum['']: { throw new Error('Not implemented yet: Enum[''] case') }
558+
case Enum.test: { throw new Error('Not implemented yet: Enum.test case') }
559+
}
560+
}
561+
`.trimRight(),
562+
},
563+
],
564+
},
565+
],
566+
},
567+
{
568+
// keys include number as first character
569+
code: `
570+
export enum Enum {
571+
'9test' = 'test-test',
572+
'test' = 'test',
573+
}
574+
575+
function test(arg: Enum): string {
576+
switch (arg) {
577+
}
578+
}
579+
`.trimRight(),
580+
errors: [
581+
{
582+
messageId: 'switchIsNotExhaustive',
583+
suggestions: [
584+
{
585+
messageId: 'addMissingCases',
586+
output: noFormat`
587+
export enum Enum {
588+
'9test' = 'test-test',
589+
'test' = 'test',
590+
}
591+
592+
function test(arg: Enum): string {
593+
switch (arg) {
594+
case Enum['9test']: { throw new Error('Not implemented yet: Enum['9test'] case') }
595+
case Enum.test: { throw new Error('Not implemented yet: Enum.test case') }
596+
}
486597
}
487598
`.trimRight(),
488599
},

0 commit comments

Comments
 (0)