diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-conversion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-conversion.ts index 09af31518b11..088b8768b3af 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-conversion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-conversion.ts @@ -57,7 +57,7 @@ export default createRule({ typeFlag: ts.TypeFlags, typeString: 'boolean' | 'number', violation: string, - isDoubleOperator: boolean, // !! or ~~ + isDoubleOperator: boolean, // !! ) { const outerNode = isDoubleOperator ? node.parent : node; const type = services.getTypeAtLocation(node.argument); @@ -351,13 +351,49 @@ export default createRule({ 'UnaryExpression[operator = "~"] > UnaryExpression[operator = "~"]'( node: TSESTree.UnaryExpression, ): void { - handleUnaryOperator( - node, - ts.TypeFlags.NumberLike, - 'number', - 'Using ~~ on a number', - true, - ); + const outerNode = node.parent; + const type = services.getTypeAtLocation(node.argument); + + if ( + tsutils.unionConstituents(type).every(t => { + return ( + isTypeFlagSet(t, ts.TypeFlags.NumberLiteral) && + Number.isInteger((t as ts.NumberLiteralType).value) + ); + }) + ) { + const wrappingFixerParams = { + node: outerNode, + innerNode: [node.argument], + sourceCode: context.sourceCode, + }; + + context.report({ + loc: { + start: outerNode.loc.start, + end: { + column: node.loc.start.column + 1, + line: node.loc.start.line, + }, + }, + messageId: 'unnecessaryTypeConversion', + data: { type: 'number', violation: 'Using ~~ on an integer' }, + suggest: [ + { + messageId: 'suggestRemove', + fix: getWrappingFixer(wrappingFixerParams), + }, + { + messageId: 'suggestSatisfies', + data: { type: 'number' }, + fix: getWrappingFixer({ + ...wrappingFixerParams, + wrap: expr => `${expr} satisfies number`, + }), + }, + ], + }); + } }, }; }, diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-conversion.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-conversion.shot index 1231d273a43f..7d55981cea0f 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-conversion.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-type-conversion.shot @@ -14,7 +14,7 @@ Number(123); +123; ~ Using the unary + operator on a number does not change the type or value of the number. ~~123; -~~ Using ~~ on a number does not change the type or value of the number. +~~ Using ~~ on an integer does not change the type or value of the number. Boolean(true); ~~~~~~~ Passing a boolean to Boolean() does not change the type or value of the boolean. diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-conversion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-conversion.test.ts index e0aaadacee2d..50995fb3a90d 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-conversion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-conversion.test.ts @@ -29,6 +29,10 @@ ruleTester.run('no-unnecessary-type-conversion', rule, { "Number('2');", "+'2';", "~~'2';", + '~~1.1;', + '~~-1.1;', + '~~(1.5 + 2.3);', + '~~(1 / 3);', 'Boolean(0);', '!!0;', 'BigInt(3);', @@ -578,26 +582,6 @@ let str = 'asdf'; }, ], }, - { - code: '2 * ~~(2 + 2);', - errors: [ - { - column: 5, - endColumn: 7, - messageId: 'unnecessaryTypeConversion', - suggestions: [ - { - messageId: 'suggestRemove', - output: '2 * (2 + 2);', - }, - { - messageId: 'suggestSatisfies', - output: '2 * ((2 + 2) satisfies number);', - }, - ], - }, - ], - }, { code: 'false && !!(false || true);', errors: [ @@ -720,5 +704,75 @@ let str = 'asdf'; }, ], }, + { + code: '~~1;', + errors: [ + { + column: 1, + endColumn: 3, + messageId: 'unnecessaryTypeConversion', + suggestions: [ + { + messageId: 'suggestRemove', + output: '1;', + }, + { + messageId: 'suggestSatisfies', + output: '1 satisfies number;', + }, + ], + }, + ], + }, + { + code: '~~-1;', + errors: [ + { + column: 1, + endColumn: 3, + messageId: 'unnecessaryTypeConversion', + suggestions: [ + { + messageId: 'suggestRemove', + output: '(-1);', + }, + { + messageId: 'suggestSatisfies', + output: '(-1) satisfies number;', + }, + ], + }, + ], + }, + { + code: ` + declare const threeOrFour: 3 | 4; + ~~threeOrFour; + `, + errors: [ + { + column: 9, + endColumn: 11, + line: 3, + messageId: 'unnecessaryTypeConversion', + suggestions: [ + { + messageId: 'suggestRemove', + output: ` + declare const threeOrFour: 3 | 4; + threeOrFour; + `, + }, + { + messageId: 'suggestSatisfies', + output: ` + declare const threeOrFour: 3 | 4; + threeOrFour satisfies number; + `, + }, + ], + }, + ], + }, ], });