From 71ebb2d6c2fd247ff55f930ecf02cec6c8b2f8b7 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 7 Aug 2023 15:09:27 -0400 Subject: [PATCH 1/2] feat(eslint-plugin): added suggestion fixer to await-thenable --- .../eslint-plugin/src/rules/await-thenable.ts | 14 ++ .../tests/rules/await-thenable.test.ts | 182 +++++++++++++----- 2 files changed, 143 insertions(+), 53 deletions(-) diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index fca9fd83de00..a7982292bc87 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -1,3 +1,4 @@ +import type { TSESLint } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as util from '../util'; @@ -10,8 +11,10 @@ export default util.createRule({ recommended: 'recommended', requiresTypeChecking: true, }, + hasSuggestions: true, messages: { await: 'Unexpected `await` of a non-Promise (non-"Thenable") value.', + removeAwait: 'Remove unnecessary `await`.', }, schema: [], type: 'problem', @@ -35,6 +38,17 @@ export default util.createRule({ context.report({ messageId: 'await', node, + suggest: [ + { + messageId: 'removeAwait', + fix(fixer): TSESLint.RuleFix { + return fixer.removeRange([ + node.range[0], + node.range[0] + 'await'.length, + ]); + }, + }, + ], }); } }, diff --git a/packages/eslint-plugin/tests/rules/await-thenable.test.ts b/packages/eslint-plugin/tests/rules/await-thenable.test.ts index 4b51a75ac8bb..27e4309092e8 100644 --- a/packages/eslint-plugin/tests/rules/await-thenable.test.ts +++ b/packages/eslint-plugin/tests/rules/await-thenable.test.ts @@ -1,4 +1,4 @@ -import { RuleTester } from '@typescript-eslint/rule-tester'; +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/await-thenable'; import { getFixturesRootDir } from '../RuleTester'; @@ -202,33 +202,83 @@ const doSomething = async ( invalid: [ { - code: ` -async function test() { - await 0; - await 'value'; - - await (Math.random() > 0.5 ? '' : 0); - - class NonPromise extends Array {} - await new NonPromise(); -} - `, + code: 'await 0;', errors: [ { - line: 3, + line: 1, + messageId, + suggestions: [ + { + messageId: 'removeAwait', + output: ' 0;', + }, + ], + }, + ], + }, + { + code: "await 'value';", + errors: [ + { + line: 1, messageId, + suggestions: [ + { + messageId: 'removeAwait', + output: " 'value';", + }, + ], }, + ], + }, + { + code: "async () => await (Math.random() > 0.5 ? '' : 0);", + errors: [ { - line: 4, + line: 1, messageId, + suggestions: [ + { + messageId: 'removeAwait', + output: "async () => (Math.random() > 0.5 ? '' : 0);", + }, + ], }, + ], + }, + { + code: noFormat`async () => await(Math.random() > 0.5 ? '' : 0);`, + errors: [ { - line: 6, + line: 1, messageId, + suggestions: [ + { + messageId: 'removeAwait', + output: "async () => (Math.random() > 0.5 ? '' : 0);", + }, + ], }, + ], + }, + { + code: ` +class NonPromise extends Array {} +await new NonPromise(); + `, + errors: [ { - line: 9, + line: 3, messageId, + suggestions: [ + { + messageId: 'removeAwait', + output: ` +class NonPromise extends Array {} + new NonPromise(); + `, + }, + ], }, ], }, @@ -247,58 +297,84 @@ async function test() { { line: 8, messageId, + suggestions: [ + { + messageId: 'removeAwait', + output: ` +async function test() { + class IncorrectThenable { + then() {} + } + const thenable = new IncorrectThenable(); + + thenable; +} + `, + }, + ], }, ], }, { code: ` -const doSomething = async ( - obj1: { a?: { b?: { c?: () => void } } }, - obj2: { a?: { b?: { c: () => void } } }, - obj3: { a?: { b: { c?: () => void } } }, - obj4: { a: { b: { c?: () => void } } }, - obj5: { a?: () => { b?: { c?: () => void } } }, - obj6?: { a: { b: { c?: () => void } } }, - callback?: () => void, -): Promise => { - await obj1.a?.b?.c?.(); - await obj2.a?.b?.c(); - await obj3.a?.b.c?.(); - await obj4.a.b.c?.(); - await obj5.a?.().b?.c?.(); - await obj6?.a.b.c?.(); - - await callback?.(); -}; +declare const callback: (() => void) | undefined; +await callback?.(); `, errors: [ { - line: 11, - messageId, - }, - { - line: 12, - messageId, - }, - { - line: 13, - messageId, - }, - { - line: 14, - messageId, - }, - { - line: 15, + line: 3, messageId, + suggestions: [ + { + messageId: 'removeAwait', + output: ` +declare const callback: (() => void) | undefined; + callback?.(); + `, + }, + ], }, + ], + }, + { + code: ` +declare const obj: { a?: { b?: () => void } }; +await obj.a?.b?.(); + `, + errors: [ { - line: 16, + line: 3, messageId, + suggestions: [ + { + messageId: 'removeAwait', + output: ` +declare const obj: { a?: { b?: () => void } }; + obj.a?.b?.(); + `, + }, + ], }, + ], + }, + { + code: ` +declare const obj: { a: { b: { c?: () => void } } } | undefined; +await obj?.a.b.c?.(); + `, + errors: [ { - line: 18, + line: 3, messageId, + suggestions: [ + { + messageId: 'removeAwait', + output: ` +declare const obj: { a: { b: { c?: () => void } } } | undefined; + obj?.a.b.c?.(); + `, + }, + ], }, ], }, From 73d4a8c167d058062e919b45c22f9c0f61a60cdd Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 2 Sep 2023 23:29:50 -0400 Subject: [PATCH 2/2] Aha, getFirstToken --- packages/eslint-plugin/src/rules/await-thenable.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index a7982292bc87..f5932dd7f259 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -42,10 +42,16 @@ export default util.createRule({ { messageId: 'removeAwait', fix(fixer): TSESLint.RuleFix { - return fixer.removeRange([ - node.range[0], - node.range[0] + 'await'.length, - ]); + const sourceCode = context.getSourceCode(); + const awaitKeyword = util.nullThrows( + sourceCode.getFirstToken(node, util.isAwaitKeyword), + util.NullThrowsReasons.MissingToken( + 'await', + 'await expression', + ), + ); + + return fixer.remove(awaitKeyword); }, }, ],