Skip to content

feat(36266): async function needs Promise<T> return type quick fix #36654

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ namespace ts {
},
getParameterType: getTypeAtPosition,
getPromisedTypeOfPromise,
getAwaitedType: type => getAwaitedType(type),
getReturnTypeOfSignature,
isNullableType,
getNullableType,
Expand Down Expand Up @@ -30782,7 +30783,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;
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@
"category": "Error",
"code": 1063
},
"The return type of an async function or method must be the global Promise<T> type.": {
"The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<{0}>'?": {
"category": "Error",
"code": 1064
},
Expand Down Expand Up @@ -5193,6 +5193,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
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3421,6 +3421,8 @@ namespace ts {
getWidenedType(type: Type): Type;
/* @internal */
getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined;
/* @internal */
getAwaitedType(type: Type): Type | undefined;
getReturnTypeOfSignature(signature: Signature): Type;
/**
* Gets the type of a parameter at a given position in a signature.
Expand Down
70 changes: 70 additions & 0 deletions src/services/codefixes/fixReturnTypeInAsyncFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* @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,
];

interface Info {
readonly returnTypeNode: TypeNode;
readonly returnType: Type;
readonly promisedTypeNode: TypeNode;
readonly promisedType: Type;
}

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): Info | undefined {
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;
}
}
}
1 change: 1 addition & 0 deletions src/services/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"codefixes/fixMissingCallParentheses.ts",
"codefixes/fixAwaitInSyncFunction.ts",
"codefixes/inferFromUsage.ts",
"codefixes/fixReturnTypeInAsyncFunction.ts",
"codefixes/disableJsDiagnostics.ts",
"codefixes/helpers.ts",
"codefixes/fixInvalidImportSyntax.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(3,17): error TS2322: Type '0' is not assignable to type 'string'.
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(6,24): error TS1064: The return type of an async function or method must be the global Promise<T> type.
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(6,24): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<string>'?
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(7,23): error TS2322: Type '0' is not assignable to type 'string'.
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(10,24): error TS1064: The return type of an async function or method must be the global Promise<T> type.
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(10,24): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<string>'?
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(12,2): error TS2322: Type '0' is not assignable to type 'string'.
tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(19,3): error TS2345: Argument of type '() => Promise<number>' is not assignable to parameter of type '() => string'.
Type 'Promise<number>' is not assignable to type 'string'.
Expand All @@ -17,15 +17,15 @@ tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(19,3): error TS2
// Error (good)
/** @type {function(): string} */
~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<string>'?
const b = async () => 0
~
!!! error TS2322: Type '0' is not assignable to type 'string'.

// No error (bad)
/** @type {function(): string} */
~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<string>'?
const c = async () => {
return 0
~~~~~~~~
Expand Down
20 changes: 10 additions & 10 deletions tests/baselines/reference/asyncFunctionDeclaration15_es6.errors.txt
Original file line number Diff line number Diff line change
@@ -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<T> 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<T> 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<T> 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<T> 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<T> type. Did you mean to write 'Promise<any>'?
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<T> type. Did you mean to write 'Promise<number>'?
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<T> 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<T> 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<T> type. Did you mean to write 'Promise<void>'?
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<T> type. Did you mean to write 'Promise<void>'?
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.

Expand All @@ -17,23 +17,23 @@ tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration1
async function fn1() { } // valid: Promise<void>
async function fn2(): { } { } // error
~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! error TS1064: The return type of an async function or method must be the global Promise<T> 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<T> type.
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<any>'?
async function fn4(): number { } // error
~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?
~~~~~~
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
async function fn5(): PromiseLike<void> { } // error
~~~~~~~~~~~~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<void>'?
async function fn6(): Thenable { } // error
~~~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<void>'?
async function fn7() { return; } // valid: Promise<void>
async function fn8() { return 1; } // valid: Promise<number>
async function fn9() { return null; } // valid: Promise<any>
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/asyncImportedPromise_es6.errors.txt
Original file line number Diff line number Diff line change
@@ -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<T> 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<T> type. Did you mean to write 'Promise<T>'?


==== tests/cases/conformance/async/es6/task.ts (0 errors) ====
Expand All @@ -9,5 +9,5 @@ tests/cases/conformance/async/es6/test.ts(3,25): error TS1064: The return type o
class Test {
async example<T>(): Task<T> { return; }
~~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<T>'?
}
Original file line number Diff line number Diff line change
@@ -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<T> 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<T> type. Did you mean to write 'Promise<void>'?


==== tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts (1 errors) ====
Expand All @@ -9,5 +9,5 @@ tests/cases/conformance/async/es6/asyncQualifiedReturnType_es6.ts(6,21): error T

async function f(): X.MyPromise<void> {
~~~~~~~~~~~~~~~~~
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type.
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<void>'?
}
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): number {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "number", "number"],
newFileContent: `async function fn(): Promise<number> {}`
});
13 changes: 13 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction10.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////type Foo = "a" | "b";
////async function fn(): Foo {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "Foo", "Foo"],
newFileContent:
`type Foo = "a" | "b";
async function fn(): Promise<Foo> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction11.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): PromiseLike<string> {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "PromiseLike<string>", "string"],
newFileContent: `async function fn(): Promise<string> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction12.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): PromiseLike<void> {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "PromiseLike<void>", "void"],
newFileContent: `async function fn(): Promise<void> {}`
});
13 changes: 13 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction13.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////declare class Thenable { then(): void; }
////async function fn(): Thenable {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "Thenable", "void"],
newFileContent:
`declare class Thenable { then(): void; }
async function fn(): Promise<void> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction14.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): string | symbol {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "string | symbol", "string | symbol"],
newFileContent: `async function fn(): Promise<string | symbol> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction15.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @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<number> {}`
});
15 changes: 15 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction16.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />

// @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<number> {}
}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction17.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @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<number> => {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): boolean {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "boolean", "boolean"],
newFileContent: `async function fn(): Promise<boolean> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): string {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "string", "string"],
newFileContent: `async function fn(): Promise<string> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): void {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "void", "void"],
newFileContent: `async function fn(): Promise<void> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): null {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "null", "null"],
newFileContent: `async function fn(): Promise<null> {}`
});
10 changes: 10 additions & 0 deletions tests/cases/fourslash/codeFixReturnTypeInAsyncFunction6.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts' />

// @target: es2015
////async function fn(): undefined {}

verify.codeFix({
index: 0,
description: [ts.Diagnostics.Replace_0_with_Promise_1.message, "undefined", "undefined"],
newFileContent: `async function fn(): Promise<undefined> {}`
});
Loading