From 79d4c9d08d687c91d61249e4c0b15b8a8f9c5b68 Mon Sep 17 00:00:00 2001 From: sena-anny Date: Wed, 9 Nov 2022 02:05:59 +0900 Subject: [PATCH 1/3] fix(eslint-plugin):[no-floating-promises] Add suggestion fixer to add an 'await' --- .../src/rules/no-floating-promises.ts | 21 ++++++++++- .../tests/rules/no-floating-promises.test.ts | 36 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 05f0954e305..a92c77a4f2e 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -12,7 +12,11 @@ type Options = [ }, ]; -type MessageId = 'floating' | 'floatingVoid' | 'floatingFixVoid'; +type MessageId = + | 'floating' + | 'floatingVoid' + | 'floatingFixVoid' + | 'floatingFixAwait'; export default util.createRule({ name: 'no-floating-promises', @@ -27,6 +31,7 @@ export default util.createRule({ messages: { floating: 'Promises must be awaited, end with a call to .catch, or end with a call to .then with a rejection handler.', + floatingFixAwait: 'Add await operator.', floatingVoid: 'Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler' + ' or be explicitly marked as ignored with the `void` operator.', @@ -95,6 +100,20 @@ export default util.createRule({ context.report({ node, messageId: 'floating', + suggest: [ + { + messageId: 'floatingFixAwait', + fix(fixer): TSESLint.RuleFix { + let code = sourceCode.getText(node); + code = `await ${ + code.startsWith('void') + ? code.replace('void', '').trimStart() + : code + }`; + return fixer.replaceText(node, code); + }, + }, + ], }); } } 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 17ec0bb6198..a8af73a77bc 100644 --- a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts @@ -656,6 +656,42 @@ async function test() { { line: 3, messageId: 'floating', + suggestions: [ + { + messageId: 'floatingFixAwait', + output: ` +async function test() { + await Promise.resolve(); +} + `, + }, + ], + }, + ], + }, + { + code: ` +async function test() { + const promise = new Promise((resolve, reject) => resolve('value')); + promise; +} + `, + options: [{ ignoreVoid: false }], + errors: [ + { + line: 4, + messageId: 'floating', + suggestions: [ + { + messageId: 'floatingFixAwait', + output: ` +async function test() { + const promise = new Promise((resolve, reject) => resolve('value')); + await promise; +} + `, + }, + ], }, ], }, From dcfa4bb05240409f7ceb21b3e4bc05ed1d9c9cbc Mon Sep 17 00:00:00 2001 From: sena-anny Date: Thu, 17 Nov 2022 02:26:37 +0900 Subject: [PATCH 2/3] fix(eslint-plugin):[no-floating-promises] Change in the method of judging void operator --- .../src/rules/no-floating-promises.ts | 17 +++++++----- .../tests/rules/no-floating-promises.test.ts | 26 +++++++++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index a92c77a4f2e..d05d6f38a9d 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -104,13 +104,16 @@ export default util.createRule({ { messageId: 'floatingFixAwait', fix(fixer): TSESLint.RuleFix { - let code = sourceCode.getText(node); - code = `await ${ - code.startsWith('void') - ? code.replace('void', '').trimStart() - : code - }`; - return fixer.replaceText(node, code); + if ( + expression.type === AST_NODE_TYPES.UnaryExpression && + expression.operator === 'void' + ) { + return fixer.replaceTextRange( + [expression.range[0], expression.range[0] + 4], + 'await', + ); + } + return fixer.insertTextBefore(node, 'await '); }, }, ], 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 a8af73a77bc..83220d20d88 100644 --- a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts @@ -697,6 +697,32 @@ async function test() { }, { code: ` +async function returnsPromise() { + return 'value'; +} +void returnsPromise(); + `, + options: [{ ignoreVoid: false }], + errors: [ + { + line: 5, + messageId: 'floating', + suggestions: [ + { + messageId: 'floatingFixAwait', + output: ` +async function returnsPromise() { + return 'value'; +} +await returnsPromise(); + `, + }, + ], + }, + ], + }, + { + code: ` async function test() { const obj = { foo: Promise.resolve() }; obj.foo; From 1ddb23b5f3e731808b3928836d1db30ad9965c5d Mon Sep 17 00:00:00 2001 From: sena-anny Date: Sat, 19 Nov 2022 01:10:29 +0900 Subject: [PATCH 3/3] feat(eslint-plugin):[no-floating-promises] check the precedence of the node --- .../src/rules/no-floating-promises.ts | 31 +++++++- .../tests/rules/no-floating-promises.test.ts | 79 +++++++++++++++++++ 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index d05d6f38a9d..61829743e81 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -1,7 +1,7 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'tsutils'; -import type * as ts from 'typescript'; +import * as ts from 'typescript'; import * as util from '../util'; @@ -103,7 +103,7 @@ export default util.createRule({ suggest: [ { messageId: 'floatingFixAwait', - fix(fixer): TSESLint.RuleFix { + fix(fixer): TSESLint.RuleFix | TSESLint.RuleFix[] { if ( expression.type === AST_NODE_TYPES.UnaryExpression && expression.operator === 'void' @@ -113,7 +113,20 @@ export default util.createRule({ 'await', ); } - return fixer.insertTextBefore(node, 'await '); + const tsNode = parserServices.esTreeNodeToTSNodeMap.get( + node.expression, + ); + if (isHigherPrecedenceThanAwait(tsNode)) { + return fixer.insertTextBefore(node, 'await '); + } else { + return [ + fixer.insertTextBefore(node, 'await ('), + fixer.insertTextAfterRange( + [expression.range[1], expression.range[1]], + ')', + ), + ]; + } }, }, ], @@ -123,6 +136,18 @@ export default util.createRule({ }, }; + function isHigherPrecedenceThanAwait(node: ts.Node): boolean { + const operator = tsutils.isBinaryExpression(node) + ? node.operatorToken.kind + : ts.SyntaxKind.Unknown; + const nodePrecedence = util.getOperatorPrecedence(node.kind, operator); + const awaitPrecedence = util.getOperatorPrecedence( + ts.SyntaxKind.AwaitExpression, + ts.SyntaxKind.Unknown, + ); + return nodePrecedence > awaitPrecedence; + } + function isAsyncIife(node: TSESTree.ExpressionStatement): boolean { if (node.expression.type !== AST_NODE_TYPES.CallExpression) { return false; 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 83220d20d88..a80acab989a 100644 --- a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts @@ -721,6 +721,85 @@ await returnsPromise(); }, ], }, + { + // eslint-disable-next-line @typescript-eslint/internal/plugin-test-formatting + code: ` +async function returnsPromise() { + return 'value'; +} +void /* ... */ returnsPromise(); + `, + options: [{ ignoreVoid: false }], + errors: [ + { + line: 5, + messageId: 'floating', + suggestions: [ + { + messageId: 'floatingFixAwait', + output: ` +async function returnsPromise() { + return 'value'; +} +await /* ... */ returnsPromise(); + `, + }, + ], + }, + ], + }, + { + code: ` +async function returnsPromise() { + return 'value'; +} +1, returnsPromise(); + `, + options: [{ ignoreVoid: false }], + errors: [ + { + line: 5, + messageId: 'floating', + suggestions: [ + { + messageId: 'floatingFixAwait', + output: ` +async function returnsPromise() { + return 'value'; +} +await (1, returnsPromise()); + `, + }, + ], + }, + ], + }, + { + code: ` +async function returnsPromise() { + return 'value'; +} +bool ? returnsPromise() : null; + `, + options: [{ ignoreVoid: false }], + errors: [ + { + line: 5, + messageId: 'floating', + suggestions: [ + { + messageId: 'floatingFixAwait', + output: ` +async function returnsPromise() { + return 'value'; +} +await (bool ? returnsPromise() : null); + `, + }, + ], + }, + ], + }, { code: ` async function test() {