diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b761606fcb3ef..a79a02cba0796 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26403,7 +26403,11 @@ namespace ts { * The runtime behavior of the `await` keyword. */ function checkAwaitedType(type: Type, errorNode: Node, diagnosticMessage: DiagnosticMessage, arg0?: string | number): Type { - return getAwaitedType(type, errorNode, diagnosticMessage, arg0) || errorType; + const awaitedType = getAwaitedType(type, errorNode, diagnosticMessage, arg0); + if (awaitedType === type && !(type.flags & TypeFlags.AnyOrUnknown)) { + addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(errorNode, Diagnostics.await_has_no_effect_on_the_type_of_this_expression)); + } + return awaitedType || errorType; } function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 7fc2fdd4237e1..7a6b798e2aee3 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4635,6 +4635,11 @@ "category": "Suggestion", "code": 80006 }, + "'await' has no effect on the type of this expression.": { + "category": "Suggestion", + "code": 80007 + }, + "Add missing 'super()' call": { "category": "Message", "code": 90001 @@ -5095,6 +5100,14 @@ "category": "Message", "code": 95085 }, + "Remove unnecessary 'await'": { + "category": "Message", + "code": 95086 + }, + "Remove all unnecessary uses of 'await'": { + "category": "Message", + "code": 95087 + }, "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { "category": "Error", diff --git a/src/services/codefixes/removeUnnecessaryAwait.ts b/src/services/codefixes/removeUnnecessaryAwait.ts new file mode 100644 index 0000000000000..c235d028efbce --- /dev/null +++ b/src/services/codefixes/removeUnnecessaryAwait.ts @@ -0,0 +1,33 @@ +/* @internal */ +namespace ts.codefix { + const fixId = "removeUnnecessaryAwait"; + const errorCodes = [ + Diagnostics.await_has_no_effect_on_the_type_of_this_expression.code, + ]; + + registerCodeFix({ + errorCodes, + getCodeActions: (context) => { + const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span)); + if (changes.length > 0) { + return [createCodeFixAction(fixId, changes, Diagnostics.Remove_unnecessary_await, fixId, Diagnostics.Remove_all_unnecessary_uses_of_await)]; + } + }, + fixIds: [fixId], + getAllCodeActions: context => { + return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag)); + }, + }); + + function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, span: TextSpan) { + const awaitKeyword = tryCast(getTokenAtPosition(sourceFile, span.start), (node): node is AwaitKeywordToken => node.kind === SyntaxKind.AwaitKeyword); + const awaitExpression = awaitKeyword && tryCast(awaitKeyword.parent, isAwaitExpression); + if (!awaitExpression) { + return; + } + + const parenthesizedExpression = tryCast(awaitExpression.parent, isParenthesizedExpression); + const removeParens = parenthesizedExpression && (isIdentifier(awaitExpression.expression) || isCallExpression(awaitExpression.expression)); + changeTracker.replaceNode(sourceFile, removeParens ? parenthesizedExpression || awaitExpression : awaitExpression, awaitExpression.expression); + } +} diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 05c1b0b482ea0..c2a6946b5ed77 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -80,6 +80,7 @@ "codefixes/useDefaultImport.ts", "codefixes/fixAddModuleReferTypeMissingTypeof.ts", "codefixes/convertToMappedObjectType.ts", + "codefixes/removeUnnecessaryAwait.ts", "refactors/convertExport.ts", "refactors/convertImport.ts", "refactors/extractSymbol.ts", diff --git a/tests/cases/fourslash/codeFixRemoveUnnecessaryAwait.ts b/tests/cases/fourslash/codeFixRemoveUnnecessaryAwait.ts new file mode 100644 index 0000000000000..b8e1de4605e27 --- /dev/null +++ b/tests/cases/fourslash/codeFixRemoveUnnecessaryAwait.ts @@ -0,0 +1,40 @@ +/// +////declare class C { foo(): void } +////declare function foo(): string; +////async function f() { +//// await ""; +//// await 0; +//// (await foo()).toLowerCase(); +//// (await 0).toFixed(); +//// (await new C).foo(); +////} + +verify.codeFix({ + description: ts.Diagnostics.Remove_unnecessary_await.message, + index: 0, + newFileContent: +`declare class C { foo(): void } +declare function foo(): string; +async function f() { + ""; + await 0; + (await foo()).toLowerCase(); + (await 0).toFixed(); + (await new C).foo(); +}` +}); + +verify.codeFixAll({ + fixAllDescription: ts.Diagnostics.Remove_all_unnecessary_uses_of_await.message, + fixId: "removeUnnecessaryAwait", + newFileContent: +`declare class C { foo(): void } +declare function foo(): string; +async function f() { + ""; + 0; + foo().toLowerCase(); + (0).toFixed(); + (new C).foo(); +}` +}); diff --git a/tests/cases/fourslash/convertFunctionToEs6Class_asyncMethods.ts b/tests/cases/fourslash/convertFunctionToEs6Class_asyncMethods.ts index fb3b46763c878..bb8f8c229621f 100644 --- a/tests/cases/fourslash/convertFunctionToEs6Class_asyncMethods.ts +++ b/tests/cases/fourslash/convertFunctionToEs6Class_asyncMethods.ts @@ -2,13 +2,14 @@ // @allowNonTsExtensions: true // @Filename: test123.js +// @lib: es5 ////export function /**/MyClass() { ////} ////MyClass.prototype.foo = async function() { -//// await 2; +//// await Promise.resolve(); ////} ////MyClass.bar = async function() { -//// await 3; +//// await Promise.resolve(); ////} verify.codeFix({ @@ -18,10 +19,10 @@ verify.codeFix({ constructor() { } async foo() { - await 2; + await Promise.resolve(); } static async bar() { - await 3; + await Promise.resolve(); } } `,