diff --git a/.cspell.json b/.cspell.json index ffe51471848e..91dc7f910d0f 100644 --- a/.cspell.json +++ b/.cspell.json @@ -150,6 +150,7 @@ "oxlint", "packagespecifier", "parameterised", + "parenthesization", "performant", "pluggable", "postprocess", diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 32ecbe741681..b040d1c82122 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -1,16 +1,17 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import type * as ts from 'typescript'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; -import * as ts from 'typescript'; import type { TypeOrValueSpecifier } from '../util'; import { createRule, - getOperatorPrecedence, + getOperatorPrecedenceForNode, getParserServices, isBuiltinSymbolLike, + isParenthesized, OperatorPrecedence, readonlynessOptionsDefaults, readonlynessOptionsSchema, @@ -168,10 +169,11 @@ export default createRule({ { messageId: 'floatingFixVoid', fix(fixer): TSESLint.RuleFix | TSESLint.RuleFix[] { - const tsNode = services.esTreeNodeToTSNodeMap.get( - node.expression, - ); - if (isHigherPrecedenceThanUnary(tsNode)) { + if ( + isParenthesized(expression, context.sourceCode) || + getOperatorPrecedenceForNode(expression) > + OperatorPrecedence.Unary + ) { return fixer.insertTextBefore(node, 'void '); } return [ @@ -223,8 +225,10 @@ export default createRule({ 'await', ); } - const tsNode = services.esTreeNodeToTSNodeMap.get(node.expression); - if (isHigherPrecedenceThanUnary(tsNode)) { + if ( + isParenthesized(expression, context.sourceCode) || + getOperatorPrecedenceForNode(expression) > OperatorPrecedence.Unary + ) { return fixer.insertTextBefore(node, 'await '); } return [ @@ -261,14 +265,6 @@ export default createRule({ ); } - function isHigherPrecedenceThanUnary(node: ts.Node): boolean { - const operator = ts.isBinaryExpression(node) - ? node.operatorToken.kind - : ts.SyntaxKind.Unknown; - const nodePrecedence = getOperatorPrecedence(node.kind, operator); - return nodePrecedence > OperatorPrecedence.Unary; - } - function isAsyncIife(node: TSESTree.ExpressionStatement): boolean { if (node.expression.type !== AST_NODE_TYPES.CallExpression) { return false; diff --git a/packages/eslint-plugin/src/util/getOperatorPrecedence.ts b/packages/eslint-plugin/src/util/getOperatorPrecedence.ts index 950708015d89..54b40af26e28 100644 --- a/packages/eslint-plugin/src/util/getOperatorPrecedence.ts +++ b/packages/eslint-plugin/src/util/getOperatorPrecedence.ts @@ -198,6 +198,10 @@ export enum OperatorPrecedence { Invalid = -1, } +/** + * Note that this does not take into account parenthesization. You should check + * for parenthesization separately if it's relevant to your usage. + */ export function getOperatorPrecedenceForNode( node: TSESTree.Node, ): OperatorPrecedence { diff --git a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts index e2a272f6f3dc..87ead2f2b19d 100644 --- a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts @@ -2044,7 +2044,7 @@ async function test() { messageId: 'floatingFixVoid', output: ` async function test() { - void ((Promise.resolve(), 123)); + void (Promise.resolve(), 123); (123, Promise.resolve()); (123, Promise.resolve(), 123); } @@ -2054,7 +2054,7 @@ async function test() { messageId: 'floatingFixAwait', output: ` async function test() { - await ((Promise.resolve(), 123)); + await (Promise.resolve(), 123); (123, Promise.resolve()); (123, Promise.resolve(), 123); } @@ -2071,7 +2071,7 @@ async function test() { output: ` async function test() { (Promise.resolve(), 123); - void ((123, Promise.resolve())); + void (123, Promise.resolve()); (123, Promise.resolve(), 123); } `, @@ -2081,7 +2081,7 @@ async function test() { output: ` async function test() { (Promise.resolve(), 123); - await ((123, Promise.resolve())); + await (123, Promise.resolve()); (123, Promise.resolve(), 123); } `, @@ -2098,7 +2098,7 @@ async function test() { async function test() { (Promise.resolve(), 123); (123, Promise.resolve()); - void ((123, Promise.resolve(), 123)); + void (123, Promise.resolve(), 123); } `, }, @@ -2108,7 +2108,7 @@ async function test() { async function test() { (Promise.resolve(), 123); (123, Promise.resolve()); - await ((123, Promise.resolve(), 123)); + await (123, Promise.resolve(), 123); } `, }, @@ -2237,7 +2237,7 @@ async function returnsPromise() { async function returnsPromise() { return 'value'; } -await ((1, returnsPromise())); +await (1, returnsPromise()); `, }, ], @@ -4556,13 +4556,13 @@ await promiseIntersection.finally(() => {}); { messageId: 'floatingFixVoid', output: ` -void ((Promise.resolve().finally(() => {}), 123)); +void (Promise.resolve().finally(() => {}), 123); `, }, { messageId: 'floatingFixAwait', output: ` -await ((Promise.resolve().finally(() => {}), 123)); +await (Promise.resolve().finally(() => {}), 123); `, }, ],