From 8d8b7fc45233ebbcb9ea589c9d07e16ddf93f2e1 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Sat, 26 Aug 2023 14:30:32 -0400 Subject: [PATCH 01/10] feat: no-unsafe-enum-comparison handles switch cases --- .../docs/rules/no-unsafe-enum-comparison.md | 22 ++-- .../src/rules/no-unsafe-enum-comparison.ts | 107 +++++++++++------- .../rules/no-unsafe-enum-comparison.test.ts | 98 ++++++++++++++++ 3 files changed, 177 insertions(+), 50 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md b/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md index f57058260739..14e274f69e03 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md @@ -6,21 +6,23 @@ description: 'Disallow comparing an enum value with a non-enum value.' > > See **https://typescript-eslint.io/rules/no-unsafe-enum-comparison** for documentation. -The TypeScript compiler can be surprisingly lenient when working with enums. -For example, it will allow you to compare enum values against numbers even though they might not have any type overlap: +The TypeScript compiler can be surprisingly lenient when working with enums. String enums are widely considered to be safer than number enums, but even string enums have some pitfalls. For example, it is allowed to compare enum values against literals: ```ts -enum Fruit { - Apple, - Banana, +enum Vegetable { + Asparagus = 'asparagus', } -declare let fruit: Fruit; +declare const vegetable: Vegetable; -fruit === 999; // No error +vegetable === 'asparagus'; // No error ``` -This rule flags when an enum typed value is compared to a non-enum `number`. +The above code snippet should instead be written as `vegetable === Vegetable.Asparagus`. Allowing literals in comparisons subverts the point of using enums in the first place. By enforcing comparisons with properly typed enums: + +- It makes a codebase more resilient to enum members swapping values. +- It allows for code IDE's to use the "Rename Symbol" feature to quickly rename an enum throughout an entire codebase without anything breaking. +- It makes enums more intuitive, similar to how they work in other strongly-typed languages like Rust. ## Examples @@ -35,7 +37,7 @@ enum Fruit { declare let fruit: Fruit; -fruit === 999; +fruit === 0; ``` ```ts @@ -57,7 +59,7 @@ enum Fruit { declare let fruit: Fruit; -fruit === Fruit.Banana; +fruit === Fruit.Apple; ``` ```ts diff --git a/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts b/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts index 888924e3bc88..f9743f008e0e 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts @@ -1,4 +1,4 @@ -import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; @@ -48,6 +48,8 @@ export default util.createRule({ messages: { mismatched: 'The two values in this comparison do not have a shared enum type.', + mismatchedCase: + 'The case statement does not have a shared enum type with the switch predicate.', }, schema: [], }, @@ -56,6 +58,55 @@ export default util.createRule({ const parserServices = util.getParserServices(context); const typeChecker = parserServices.program.getTypeChecker(); + function isMismatchedComparison( + leftNode: TSESTree.Node, + rightNode: TSESTree.Node, + ): boolean { + const left = getTypeFromNode(leftNode); + const right = getTypeFromNode(rightNode); + + // Allow comparisons that don't have anything to do with enums: + // + // ```ts + // 1 === 2; + // ``` + const leftEnumTypes = getEnumTypes(typeChecker, left); + const rightEnumTypes = new Set(getEnumTypes(typeChecker, right)); + if (leftEnumTypes.length === 0 && rightEnumTypes.size === 0) { + return false; + } + + // Allow comparisons that share an enum type: + // + // ```ts + // Fruit.Apple === Fruit.Banana; + // ``` + for (const leftEnumType of leftEnumTypes) { + if (rightEnumTypes.has(leftEnumType)) { + return false; + } + } + + const leftTypeParts = tsutils.unionTypeParts(left); + const rightTypeParts = tsutils.unionTypeParts(right); + + // If a type exists in both sides, we consider this comparison safe: + // + // ```ts + // declare const fruit: Fruit.Apple | 0; + // fruit === 0; + // ``` + for (const leftTypePart of leftTypeParts) { + if (rightTypeParts.includes(leftTypePart)) { + return false; + } + } + + return ( + typeViolates(leftTypeParts, right) || typeViolates(rightTypeParts, left) + ); + } + function getTypeFromNode(node: TSESTree.Node): ts.Type { return typeChecker.getTypeAtLocation( parserServices.esTreeNodeToTSNodeMap.get(node), @@ -66,52 +117,28 @@ export default util.createRule({ 'BinaryExpression[operator=/^[<>!=]?={0,2}$/]'( node: TSESTree.BinaryExpression, ): void { - const left = getTypeFromNode(node.left); - const right = getTypeFromNode(node.right); - - // Allow comparisons that don't have anything to do with enums: - // - // ```ts - // 1 === 2; - // ``` - const leftEnumTypes = getEnumTypes(typeChecker, left); - const rightEnumTypes = new Set(getEnumTypes(typeChecker, right)); - if (leftEnumTypes.length === 0 && rightEnumTypes.size === 0) { - return; + if (isMismatchedComparison(node.left, node.right)) { + context.report({ + messageId: 'mismatched', + node, + }); } + }, - // Allow comparisons that share an enum type: - // - // ```ts - // Fruit.Apple === Fruit.Banana; - // ``` - for (const leftEnumType of leftEnumTypes) { - if (rightEnumTypes.has(leftEnumType)) { - return; - } + SwitchCase(node): void { + // Ignore `default` cases. + if (node.test == null) { + return; } - const leftTypeParts = tsutils.unionTypeParts(left); - const rightTypeParts = tsutils.unionTypeParts(right); - - // If a type exists in both sides, we consider this comparison safe: - // - // ```ts - // declare const fruit: Fruit.Apple | 0; - // fruit === 0; - // ``` - for (const leftTypePart of leftTypeParts) { - if (rightTypeParts.includes(leftTypePart)) { - return; - } + const { parent } = node; + if (parent.type !== AST_NODE_TYPES.SwitchStatement) { + return; } - if ( - typeViolates(leftTypeParts, right) || - typeViolates(rightTypeParts, left) - ) { + if (isMismatchedComparison(parent.discriminant, node.test)) { context.report({ - messageId: 'mismatched', + messageId: 'mismatchedCase', node, }); } diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-enum-comparison.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-enum-comparison.test.ts index c31b742160ce..e5bc0ad99697 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-enum-comparison.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-enum-comparison.test.ts @@ -306,6 +306,32 @@ ruleTester.run('strict-enums-comparison', rule, { const bitShift = 1 >> Fruit.Apple; `, + ` + enum Fruit { + Apple, + } + + declare const fruit: Fruit; + + switch (fruit) { + case Fruit.Apple: { + break; + } + } + `, + ` + enum Vegetable { + Asparagus = 'asparagus', + } + + declare const vegetable: Vegetable; + + switch (vegetable) { + case Vegetable.Asparagus: { + break; + } + } + `, ], invalid: [ { @@ -565,5 +591,77 @@ ruleTester.run('strict-enums-comparison', rule, { `, errors: [{ messageId: 'mismatched' }], }, + { + code: ` + enum Fruit { + Apple, + } + + declare const fruit: Fruit; + + switch (fruit) { + case 0: { + break; + } + } + `, + errors: [{ messageId: 'mismatchedCase' }], + }, + { + code: ` + enum Fruit { + Apple, + Banana, + } + + declare const fruit: Fruit; + + switch (fruit) { + case Fruit.Apple: { + break; + } + case 1: { + break; + } + } + `, + errors: [{ messageId: 'mismatchedCase' }], + }, + { + code: ` + enum Vegetable { + Asparagus = 'asparagus', + } + + declare const vegetable: Vegetable; + + switch (vegetable) { + case 'asparagus': { + break; + } + } + `, + errors: [{ messageId: 'mismatchedCase' }], + }, + { + code: ` + enum Vegetable { + Asparagus = 'asparagus', + Beet = 'beet', + } + + declare const vegetable: Vegetable; + + switch (vegetable) { + case Vegetable.Asparagus: { + break; + } + case 'beet': { + break; + } + } + `, + errors: [{ messageId: 'mismatchedCase' }], + }, ], }); From 5bec54725ec6ee45e49c42dca991bc16028afcce Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Sat, 26 Aug 2023 15:06:19 -0400 Subject: [PATCH 02/10] fix: lint --- .../eslint-plugin/src/rules/no-unnecessary-type-constraint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-constraint.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-constraint.ts index 7ca1106f7174..19ec6edda0cd 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-constraint.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-constraint.ts @@ -43,7 +43,7 @@ export default util.createRule({ function checkRequiresGenericDeclarationDisambiguation( filename: string, ): boolean { - const pathExt = extname(filename).toLocaleLowerCase(); + const pathExt = extname(filename).toLocaleLowerCase() as ts.Extension; switch (pathExt) { case ts.Extension.Cts: case ts.Extension.Mts: From 0e4c7c68a9417f401baff33551697f3b108e1724 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Thu, 14 Sep 2023 21:58:39 -0400 Subject: [PATCH 03/10] Update packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md b/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md index 14e274f69e03..c482a45984a3 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md @@ -20,7 +20,7 @@ vegetable === 'asparagus'; // No error The above code snippet should instead be written as `vegetable === Vegetable.Asparagus`. Allowing literals in comparisons subverts the point of using enums in the first place. By enforcing comparisons with properly typed enums: -- It makes a codebase more resilient to enum members swapping values. +- It makes a codebase more resilient to enum members changing values. - It allows for code IDE's to use the "Rename Symbol" feature to quickly rename an enum throughout an entire codebase without anything breaking. - It makes enums more intuitive, similar to how they work in other strongly-typed languages like Rust. From 5d0e6fd3705031b2307f049d0a09463499dbad8f Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Thu, 14 Sep 2023 21:58:58 -0400 Subject: [PATCH 04/10] Update packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md b/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md index c482a45984a3..76991740fbcd 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md @@ -21,7 +21,7 @@ vegetable === 'asparagus'; // No error The above code snippet should instead be written as `vegetable === Vegetable.Asparagus`. Allowing literals in comparisons subverts the point of using enums in the first place. By enforcing comparisons with properly typed enums: - It makes a codebase more resilient to enum members changing values. -- It allows for code IDE's to use the "Rename Symbol" feature to quickly rename an enum throughout an entire codebase without anything breaking. +- It allows for code IDEs to use the "Rename Symbol" feature to quickly rename an enum. - It makes enums more intuitive, similar to how they work in other strongly-typed languages like Rust. ## Examples From 38cade564fcdd123ddff7ef3479c3ad42d981971 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Fri, 15 Sep 2023 09:44:22 -0400 Subject: [PATCH 05/10] refactor: mismatched --> mismatchedCondition --- .../src/rules/no-unsafe-enum-comparison.ts | 4 +- .../rules/no-unsafe-enum-comparison.test.ts | 48 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts b/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts index f9743f008e0e..a6bc510087d3 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts @@ -46,7 +46,7 @@ export default util.createRule({ requiresTypeChecking: true, }, messages: { - mismatched: + mismatchedCondition: 'The two values in this comparison do not have a shared enum type.', mismatchedCase: 'The case statement does not have a shared enum type with the switch predicate.', @@ -119,7 +119,7 @@ export default util.createRule({ ): void { if (isMismatchedComparison(node.left, node.right)) { context.report({ - messageId: 'mismatched', + messageId: 'mismatchedCondition', node, }); } diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-enum-comparison.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-enum-comparison.test.ts index e5bc0ad99697..b5685f7fecd2 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-enum-comparison.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-enum-comparison.test.ts @@ -341,7 +341,7 @@ ruleTester.run('strict-enums-comparison', rule, { } Fruit.Apple < 1; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -350,7 +350,7 @@ ruleTester.run('strict-enums-comparison', rule, { } Fruit.Apple > 1; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -359,7 +359,7 @@ ruleTester.run('strict-enums-comparison', rule, { } Fruit.Apple == 1; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -368,7 +368,7 @@ ruleTester.run('strict-enums-comparison', rule, { } Fruit.Apple === 1; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -377,7 +377,7 @@ ruleTester.run('strict-enums-comparison', rule, { } Fruit.Apple != 1; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -386,7 +386,7 @@ ruleTester.run('strict-enums-comparison', rule, { } Fruit.Apple !== 1; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -396,7 +396,7 @@ ruleTester.run('strict-enums-comparison', rule, { } Fruit.Apple === 0; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -406,7 +406,7 @@ ruleTester.run('strict-enums-comparison', rule, { } Fruit.Banana === ''; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -417,7 +417,7 @@ ruleTester.run('strict-enums-comparison', rule, { } Vegetable.Asparagus === 'beet'; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -428,7 +428,7 @@ ruleTester.run('strict-enums-comparison', rule, { } 1 === Fruit.Apple; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -439,7 +439,7 @@ ruleTester.run('strict-enums-comparison', rule, { } 'beet' === Vegetable.Asparagus; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -451,7 +451,7 @@ ruleTester.run('strict-enums-comparison', rule, { const fruit = Fruit.Apple; fruit === 1; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -463,7 +463,7 @@ ruleTester.run('strict-enums-comparison', rule, { const vegetable = Vegetable.Asparagus; vegetable === 'beet'; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -475,7 +475,7 @@ ruleTester.run('strict-enums-comparison', rule, { const fruit = Fruit.Apple; 1 === fruit; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -487,7 +487,7 @@ ruleTester.run('strict-enums-comparison', rule, { const vegetable = Vegetable.Asparagus; 'beet' === vegetable; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: @@ -499,7 +499,7 @@ ruleTester.run('strict-enums-comparison', rule, { } Fruit.Apple === Fruit2.Apple2; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -515,7 +515,7 @@ ruleTester.run('strict-enums-comparison', rule, { } Vegetable.Asparagus === Vegetable2.Asparagus2; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: @@ -528,7 +528,7 @@ ruleTester.run('strict-enums-comparison', rule, { const fruit = Fruit.Apple; fruit === Fruit2.Apple2; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -545,7 +545,7 @@ ruleTester.run('strict-enums-comparison', rule, { const vegetable = Vegetable.Asparagus; vegetable === Vegetable2.Asparagus2; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` @@ -571,10 +571,10 @@ ruleTester.run('strict-enums-comparison', rule, { mixed === 1; `, errors: [ - { messageId: 'mismatched' }, - { messageId: 'mismatched' }, - { messageId: 'mismatched' }, - { messageId: 'mismatched' }, + { messageId: 'mismatchedCondition' }, + { messageId: 'mismatchedCondition' }, + { messageId: 'mismatchedCondition' }, + { messageId: 'mismatchedCondition' }, ], }, { @@ -589,7 +589,7 @@ ruleTester.run('strict-enums-comparison', rule, { declare const weirdString: __String; weirdString === 'someArbitraryValue'; `, - errors: [{ messageId: 'mismatched' }], + errors: [{ messageId: 'mismatchedCondition' }], }, { code: ` From 3ec9b9aed1658de4ebb5b8b0165c0ffd3970b351 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Fri, 15 Sep 2023 09:47:35 -0400 Subject: [PATCH 06/10] refactor: add more tests --- .../rules/no-unsafe-enum-comparison.test.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-enum-comparison.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-enum-comparison.test.ts index b5685f7fecd2..a87605def761 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-enum-comparison.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-enum-comparison.test.ts @@ -332,6 +332,19 @@ ruleTester.run('strict-enums-comparison', rule, { } } `, + ` + enum Vegetable { + Asparagus = 'asparagus', + } + + declare const vegetable: Vegetable; + + switch (vegetable) { + default: { + break; + } + } + `, ], invalid: [ { @@ -663,5 +676,28 @@ ruleTester.run('strict-enums-comparison', rule, { `, errors: [{ messageId: 'mismatchedCase' }], }, + { + code: ` + enum Vegetable { + Asparagus = 'asparagus', + Beet = 'beet', + } + + declare const vegetable: Vegetable; + + switch (vegetable) { + case Vegetable.Asparagus: { + break; + } + case 'beet': { + break; + } + default: { + break; + } + } + `, + errors: [{ messageId: 'mismatchedCase' }], + }, ], }); From fda70cff5452e3aa8239d3b6867724833efa0391 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Fri, 15 Sep 2023 09:50:07 -0400 Subject: [PATCH 07/10] Update packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Josh Goldberg ✨ --- packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md b/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md index 76991740fbcd..5db56d69b32d 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-enum-comparison.md @@ -22,7 +22,7 @@ The above code snippet should instead be written as `vegetable === Vegetable.Asp - It makes a codebase more resilient to enum members changing values. - It allows for code IDEs to use the "Rename Symbol" feature to quickly rename an enum. -- It makes enums more intuitive, similar to how they work in other strongly-typed languages like Rust. +- It aligns code to the proper enum semantics of referring to them by name and treating their values as implementation details. ## Examples From bd6db44ad6efac883e42db5fbd84bfcf6a22e1ba Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:11:03 -0400 Subject: [PATCH 08/10] fix: assertion --- .../src/rules/no-unsafe-enum-comparison.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts b/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts index 70c3a13d1bed..3fdc885289a8 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES, type TSESTree } from '@typescript-eslint/utils'; +import type { TSESTree } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; @@ -126,11 +126,13 @@ export default util.createRule({ } const { parent } = node; - if (parent.type !== AST_NODE_TYPES.SwitchStatement) { - return; - } - if (isMismatchedComparison(parent.discriminant, node.test)) { + /** + * @see https://github.com/typescript-eslint/typescript-eslint/issues/6225 + */ + const switchStatement = parent as TSESTree.SwitchStatement; + + if (isMismatchedComparison(switchStatement.discriminant, node.test)) { context.report({ messageId: 'mismatchedCase', node, From 344ebd318e2cbb8e92492af9b4a8a42414f90854 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:12:54 -0400 Subject: [PATCH 09/10] fix: compiler error --- .../src/rules/no-unsafe-enum-comparison.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts b/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts index 3fdc885289a8..344161a66e2b 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts @@ -62,16 +62,16 @@ export default util.createRule({ leftNode: TSESTree.Node, rightNode: TSESTree.Node, ): boolean { - const left = parserServices.getTypeAtLocation(node.left); - const right = parserServices.getTypeAtLocation(node.right); + const leftType = parserServices.getTypeAtLocation(leftNode); + const rightType = parserServices.getTypeAtLocation(rightNode); // Allow comparisons that don't have anything to do with enums: // // ```ts // 1 === 2; // ``` - const leftEnumTypes = getEnumTypes(typeChecker, left); - const rightEnumTypes = new Set(getEnumTypes(typeChecker, right)); + const leftEnumTypes = getEnumTypes(typeChecker, leftType); + const rightEnumTypes = new Set(getEnumTypes(typeChecker, rightType)); if (leftEnumTypes.length === 0 && rightEnumTypes.size === 0) { return false; } @@ -87,8 +87,8 @@ export default util.createRule({ } } - const leftTypeParts = tsutils.unionTypeParts(left); - const rightTypeParts = tsutils.unionTypeParts(right); + const leftTypeParts = tsutils.unionTypeParts(leftType); + const rightTypeParts = tsutils.unionTypeParts(rightType); // If a type exists in both sides, we consider this comparison safe: // @@ -103,7 +103,7 @@ export default util.createRule({ } return ( - typeViolates(leftTypeParts, right) || typeViolates(rightTypeParts, left) + typeViolates(leftTypeParts, rightType) || typeViolates(rightTypeParts, leftType) ); } From ec4c2e0a92b9420ccbf7757d755edf03cedcd926 Mon Sep 17 00:00:00 2001 From: James <5511220+Zamiell@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:19:02 -0400 Subject: [PATCH 10/10] fix: prettier --- packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts b/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts index 344161a66e2b..f5e237738c41 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-enum-comparison.ts @@ -103,7 +103,8 @@ export default util.createRule({ } return ( - typeViolates(leftTypeParts, rightType) || typeViolates(rightTypeParts, leftType) + typeViolates(leftTypeParts, rightType) || + typeViolates(rightTypeParts, leftType) ); }