diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e0d8d8ec5b8d6..1ec99b6f01a0f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -403,6 +403,7 @@ namespace ts { }, getParameterType: getTypeAtPosition, getPromisedTypeOfPromise, + getAwaitedType: type => getAwaitedType(type), getReturnTypeOfSignature, isNullableType, getNullableType, @@ -30354,7 +30355,7 @@ namespace ts { if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) { // The promise type was not a valid type reference to the global promise type, so we // report an error and return the unknown type. - error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type); + error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, typeToString(getAwaitedType(returnType) || voidType)); return; } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index c591e030fe46d..1b1624aa73260 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -211,7 +211,7 @@ "category": "Error", "code": 1063 }, - "The return type of an async function or method must be the global Promise type.": { + "The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise<{0}>'?": { "category": "Error", "code": 1064 }, @@ -5141,6 +5141,14 @@ "category": "Message", "code": 90035 }, + "Replace '{0}' with 'Promise<{1}>'": { + "category": "Message", + "code": 90036 + }, + "Fix all incorrect return type of an async functions": { + "category": "Message", + "code": 90037 + }, "Declare a private field named '{0}'.": { "category": "Message", "code": 90053 diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 978baed990dff..f9124dfe809dc 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3421,6 +3421,7 @@ namespace ts { getWidenedType(type: Type): Type; /* @internal */ getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined; + getAwaitedType(type: Type): Type | undefined; getReturnTypeOfSignature(signature: Signature): Type; /** * Gets the type of a parameter at a given position in a signature. diff --git a/src/services/codefixes/fixReturnTypeInAsyncFunction.ts b/src/services/codefixes/fixReturnTypeInAsyncFunction.ts new file mode 100644 index 0000000000000..698a77b7c56af --- /dev/null +++ b/src/services/codefixes/fixReturnTypeInAsyncFunction.ts @@ -0,0 +1,59 @@ +/* @internal */ +namespace ts.codefix { + const fixId = "fixReturnTypeInAsyncFunction"; + const errorCodes = [ + Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0.code, + ]; + + registerCodeFix({ + errorCodes, + fixIds: [fixId], + getCodeActions: context => { + const { sourceFile, program, span } = context; + const checker = program.getTypeChecker(); + const info = getInfo(sourceFile, program.getTypeChecker(), span.start); + if (!info) { + return undefined; + } + const { returnTypeNode, returnType, promisedTypeNode, promisedType } = info; + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, returnTypeNode, promisedTypeNode)); + return [createCodeFixAction(fixId, changes, [Diagnostics.Replace_0_with_Promise_1, checker.typeToString(returnType), checker.typeToString(promisedType)], fixId, Diagnostics.Fix_all_incorrect_return_type_of_an_async_functions)]; + }, + getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => { + const info = getInfo(diag.file, context.program.getTypeChecker(), diag.start); + if (info) { + doChange(changes, diag.file, info.returnTypeNode, info.promisedTypeNode); + } + }) + }); + + function getInfo(sourceFile: SourceFile, checker: TypeChecker, pos: number) { + const returnTypeNode = getReturnTypeNode(sourceFile, pos); + if (!returnTypeNode) { + return undefined; + } + + const returnType = checker.getTypeFromTypeNode(returnTypeNode); + const promisedType = checker.getAwaitedType(returnType) || checker.getVoidType(); + const promisedTypeNode = checker.typeToTypeNode(promisedType); + if (promisedTypeNode) { + return { returnTypeNode, returnType, promisedTypeNode, promisedType }; + } + } + + function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, returnTypeNode: TypeNode, promisedTypeNode: TypeNode): void { + changes.replaceNode(sourceFile, returnTypeNode, createTypeReferenceNode("Promise", [promisedTypeNode])); + } + + function getReturnTypeNode(sourceFile: SourceFile, pos: number): TypeNode | undefined { + if (isInJSFile(sourceFile)) { + return undefined; + } + + const token = getTokenAtPosition(sourceFile, pos); + const parent = findAncestor(token, isFunctionLikeDeclaration); + if (parent?.type) { + return parent.type; + } + } +} diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 9d1b8b44052fd..06fb18d7e4f9a 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -84,6 +84,7 @@ "codefixes/fixMissingCallParentheses.ts", "codefixes/fixAwaitInSyncFunction.ts", "codefixes/inferFromUsage.ts", + "codefixes/fixReturnTypeInAsyncFunction.ts", "codefixes/disableJsDiagnostics.ts", "codefixes/helpers.ts", "codefixes/fixInvalidImportSyntax.ts", diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 545fac4f8a159..b46a54caff229 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2035,6 +2035,7 @@ declare namespace ts { getBaseTypes(type: InterfaceType): BaseType[]; getBaseTypeOfLiteralType(type: Type): Type; getWidenedType(type: Type): Type; + getAwaitedType(type: Type): Type | undefined; getReturnTypeOfSignature(signature: Signature): Type; getNullableType(type: Type, flags: TypeFlags): Type; getNonNullableType(type: Type): Type; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 45bb3f9913c8d..819f6ad785151 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2035,6 +2035,7 @@ declare namespace ts { getBaseTypes(type: InterfaceType): BaseType[]; getBaseTypeOfLiteralType(type: Type): Type; getWidenedType(type: Type): Type; + getAwaitedType(type: Type): Type | undefined; getReturnTypeOfSignature(signature: Signature): Type; getNullableType(type: Type, flags: TypeFlags): Type; getNonNullableType(type: Type): Type; diff --git a/tests/baselines/reference/asyncFunctionDeclaration15_es6.errors.txt b/tests/baselines/reference/asyncFunctionDeclaration15_es6.errors.txt index 5ff67861350fd..5b054a616893a 100644 --- a/tests/baselines/reference/asyncFunctionDeclaration15_es6.errors.txt +++ b/tests/baselines/reference/asyncFunctionDeclaration15_es6.errors.txt @@ -1,10 +1,10 @@ -tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(6,23): error TS1064: The return type of an async function or method must be the global Promise type. +tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(6,23): error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise<{}>'? tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(6,23): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. -tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(7,23): error TS1064: The return type of an async function or method must be the global Promise type. -tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(8,23): error TS1064: The return type of an async function or method must be the global Promise type. +tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(7,23): error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise'? +tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(8,23): error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise'? tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(8,23): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. -tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(9,23): error TS1064: The return type of an async function or method must be the global Promise type. -tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(10,23): error TS1064: The return type of an async function or method must be the global Promise type. +tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(9,23): error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise'? +tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(10,23): error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise'? tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(17,16): error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member. tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(23,25): error TS1320: Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member. @@ -17,23 +17,23 @@ tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration1 async function fn1() { } // valid: Promise async function fn2(): { } { } // error ~~~ -!!! error TS1064: The return type of an async function or method must be the global Promise type. +!!! error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise<{}>'? ~~~ !!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. async function fn3(): any { } // error ~~~ -!!! error TS1064: The return type of an async function or method must be the global Promise type. +!!! error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise'? async function fn4(): number { } // error ~~~~~~ -!!! error TS1064: The return type of an async function or method must be the global Promise type. +!!! error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise'? ~~~~~~ !!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. async function fn5(): PromiseLike { } // error ~~~~~~~~~~~~~~~~~ -!!! error TS1064: The return type of an async function or method must be the global Promise type. +!!! error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise'? async function fn6(): Thenable { } // error ~~~~~~~~ -!!! error TS1064: The return type of an async function or method must be the global Promise type. +!!! error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise'? async function fn7() { return; } // valid: Promise async function fn8() { return 1; } // valid: Promise async function fn9() { return null; } // valid: Promise diff --git a/tests/baselines/reference/asyncImportedPromise_es6.errors.txt b/tests/baselines/reference/asyncImportedPromise_es6.errors.txt index 43b454826447c..402470313be51 100644 --- a/tests/baselines/reference/asyncImportedPromise_es6.errors.txt +++ b/tests/baselines/reference/asyncImportedPromise_es6.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/async/es6/test.ts(3,25): error TS1064: The return type of an async function or method must be the global Promise type. +tests/cases/conformance/async/es6/test.ts(3,25): error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise'? ==== tests/cases/conformance/async/es6/task.ts (0 errors) ==== @@ -9,5 +9,5 @@ tests/cases/conformance/async/es6/test.ts(3,25): error TS1064: The return type o class Test { async example(): Task { return; } ~~~~~~~ -!!! error TS1064: The return type of an async function or method must be the global Promise type. +!!! error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise'? } \ No newline at end of file diff --git a/tests/baselines/reference/asyncQualifiedReturnType_es6.errors.txt b/tests/baselines/reference/asyncQualifiedReturnType_es6.errors.txt index 205ea01673631..023579f6dcf51 100644 --- a/tests/baselines/reference/asyncQualifiedReturnType_es6.errors.txt +++ b/tests/baselines/reference/asyncQualifiedReturnType_es6.errors.txt @@ -1,4 +1,4 @@ -tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts(6,21): error TS1064: The return type of an async function or method must be the global Promise type. +tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts(6,21): error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise'? ==== tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts (1 errors) ==== @@ -9,5 +9,5 @@ tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts(6,21): error T async function f(): X.MyPromise { ~~~~~~~~~~~~~~~~~ -!!! error TS1064: The return type of an async function or method must be the global Promise type. +!!! error TS1064: The return type of an async function or method must be the global Promise type. Did you mean to write 'Promise'? } \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction.ts b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction.ts new file mode 100644 index 0000000000000..e0ed0d913d906 --- /dev/null +++ b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction.ts @@ -0,0 +1,10 @@ +/// + +// @target: es2015 +////async function foo(): number {} + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"], + newFileContent: `async function foo(): Promise {}` +}); diff --git a/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction1.ts b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction1.ts new file mode 100644 index 0000000000000..b25ba3c00bfa3 --- /dev/null +++ b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction1.ts @@ -0,0 +1,15 @@ +/// + +// @target: es2015 +////async function a(): number {} +////async function b(): string {} +////async function c(): string {} + +verify.codeFixAll({ + fixId: "fixReturnTypeInAsyncFunction", + fixAllDescription: ts.Diagnostics.Fix_all_incorrect_return_type_of_an_async_functions.message, + newFileContent: +`async function a(): Promise {} +async function b(): Promise {} +async function c(): Promise {}` +}); diff --git a/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction2.ts b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction2.ts new file mode 100644 index 0000000000000..082d4ab2fe54f --- /dev/null +++ b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction2.ts @@ -0,0 +1,10 @@ +/// + +// @target: es2015 +////const foo = async (): number => {} + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"], + newFileContent: `const foo = async (): Promise => {}` +}); diff --git a/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction3.ts b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction3.ts new file mode 100644 index 0000000000000..ff0f610574883 --- /dev/null +++ b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction3.ts @@ -0,0 +1,10 @@ +/// + +// @target: es2015 +////const foo = async function (): number {} + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"], + newFileContent: `const foo = async function (): Promise {}` +}); diff --git a/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction4.ts b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction4.ts new file mode 100644 index 0000000000000..e0430490b5044 --- /dev/null +++ b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction4.ts @@ -0,0 +1,15 @@ +/// + +// @target: es2015 +////class A { +//// async foo(): number {} +////} + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"], + newFileContent: +`class A { + async foo(): Promise {} +}` +}); diff --git a/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction5.ts b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction5.ts new file mode 100644 index 0000000000000..1287a04128011 --- /dev/null +++ b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction5.ts @@ -0,0 +1,13 @@ +/// + +// @target: es2015 +////interface Foo {} +////async function foo(): Foo {} + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "Foo", "Foo"], + newFileContent: +`interface Foo {} +async function foo(): Promise> {}` +}); diff --git a/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction6.ts b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction6.ts new file mode 100644 index 0000000000000..3eeb5bd13d8e7 --- /dev/null +++ b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction6.ts @@ -0,0 +1,10 @@ +/// + +// @target: es2015 +////async function foo(): PromiseLike {} + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "PromiseLike", "void"], + newFileContent: `async function foo(): Promise {}` +}); diff --git a/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction7.ts b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction7.ts new file mode 100644 index 0000000000000..bf530b37933c1 --- /dev/null +++ b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction7.ts @@ -0,0 +1,10 @@ +/// + +// @target: es2015 +////async function foo(): {} {} + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "{}", "{}"], + newFileContent: `async function foo(): Promise<{}> {}` +}); diff --git a/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction8.ts b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction8.ts new file mode 100644 index 0000000000000..a32f68b7b6821 --- /dev/null +++ b/tests/cases/fourslash/codeFixReturnTypeInAsyncFunction8.ts @@ -0,0 +1,13 @@ +/// + +// @target: es2015 +////declare class Thenable { then(): void; } +////async function foo(): Thenable {} + +verify.codeFix({ + index: 0, + description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "Thenable", "void"], + newFileContent: +`declare class Thenable { then(): void; } +async function foo(): Promise {}` +});