Skip to content
Open
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
12 changes: 12 additions & 0 deletions src/compiler/diagnosticMessages.json
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not gonna lie, those messages could use some improvement

Original file line number Diff line number Diff line change
Expand Up @@ -7255,6 +7255,10 @@
"category": "Suggestion",
"code": 80010
},
"This may need `await` keyword. Otherwise the enclosing try statement won't handle this.": {
"category": "Suggestion",
"code": 80011
},

"Add missing 'super()' call": {
"category": "Message",
Expand Down Expand Up @@ -8245,6 +8249,14 @@
"category": "Message",
"code": 95197
},
"Add missing await.": {
"category": "Message",
"code": 95198
},
"Add all missing awaits.": {
"category": "Message",
"code": 95199
},

"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
Expand Down
1 change: 1 addition & 0 deletions src/services/_namespaces/ts.codefix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from "../codefixes/addConvertToUnknownForNonOverlappingTypes.js";
export * from "../codefixes/addEmptyExportDeclaration.js";
export * from "../codefixes/addMissingAsync.js";
export * from "../codefixes/addMissingAwait.js";
export * from "../codefixes/addMissingAwaitInReturn.js";
export * from "../codefixes/addMissingConst.js";
export * from "../codefixes/addMissingDeclareProperty.js";
export * from "../codefixes/addMissingInvocationForDecorator.js";
Expand Down
41 changes: 41 additions & 0 deletions src/services/codefixes/addMissingAwaitInReturn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
codeFixAll,
createCodeFixAction,
registerCodeFix,
} from "../_namespaces/ts.codefix.js";
import {
CodeFixContext,
Debug,
Diagnostics,
factory,
getSynthesizedDeepClone,
getTokenAtPosition,
isReturnStatement,
SourceFile,
textChanges,
} from "../_namespaces/ts.js";

const fixId = "addMissingAwaitInReturn";
const errorCodes = [Diagnostics.This_may_need_await_keyword_Otherwise_the_enclosing_try_statement_won_t_handle_this.code];

registerCodeFix({
errorCodes,
getCodeActions(context: CodeFixContext) {
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start));
return [createCodeFixAction(fixId, changes, Diagnostics.Add_missing_await, fixId, Diagnostics.Add_all_missing_awaits)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag.start)),
});

function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) {
const token = getTokenAtPosition(sourceFile, pos);
Debug.assertNode(token.parent, isReturnStatement);
Debug.assertIsDefined(token.parent.expression);
const expression = token.parent.expression;
changeTracker.replaceNode(
sourceFile,
expression,
factory.createAwaitExpression(getSynthesizedDeepClone(expression, /*includeTrivia*/ true)),
);
}
33 changes: 33 additions & 0 deletions src/services/suggestionDiagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ExpressionStatement,
Extension,
fileExtensionIsOneOf,
findAncestor,
forEachReturnStatement,
FunctionDeclaration,
FunctionExpression,
Expand All @@ -28,19 +29,22 @@ import {
Identifier,
importFromModuleSpecifier,
isAsyncFunction,
isAwaitExpression,
isBinaryExpression,
isBlock,
isCallExpression,
isExportAssignment,
isFunctionDeclaration,
isFunctionExpression,
isFunctionLike,
isFunctionLikeDeclaration,
isIdentifier,
isPropertyAccessExpression,
isRequireCall,
isReturnStatement,
isSourceFileJS,
isStringLiteral,
isTryStatement,
isVariableDeclaration,
isVariableStatement,
MethodDeclaration,
Expand All @@ -52,6 +56,7 @@ import {
PropertyAccessExpression,
ReturnStatement,
skipAlias,
skipParentheses,
some,
SourceFile,
SyntaxKind,
Expand Down Expand Up @@ -132,6 +137,9 @@ export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Pr
if (canBeConvertedToAsync(node)) {
addConvertToAsyncFunctionDiagnostics(node, checker, diags);
}
if (isFunctionLikeDeclaration(node) && isAsyncFunction(node)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: recheck the desired behavior for async generators and add related tests

addMissingAwaitInReturnDiagnostics(node, checker, diags);
}
node.forEachChild(check);
}
}
Expand Down Expand Up @@ -190,6 +198,31 @@ function isConvertibleFunction(node: FunctionLikeDeclaration, checker: TypeCheck
returnsPromise(node, checker);
}

function addMissingAwaitInReturnDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: DiagnosticWithLocation[]) {
if (!node.body || !isBlock(node.body)) {
return;
}

forEachReturnStatement(node.body, statement => {
if (!statement.expression) {
return;
}
const expression = skipParentheses(statement.expression);
if (isAwaitExpression(expression)) {
return;
}
const type = checker.getTypeAtLocation(expression);
if (type.isUnion() ? type.types.every(t => !checker.getPromisedTypeOfPromise(t)) : !checker.getPromisedTypeOfPromise(type)) {
return;
}
const ancestor = findAncestor(statement, n => n === node || isTryStatement(n));
if (!ancestor || !isTryStatement(ancestor)) {
return;
}
diags.push(createDiagnosticForNode(statement, Diagnostics.This_may_need_await_keyword_Otherwise_the_enclosing_try_statement_won_t_handle_this));
});
}

/** @internal */
export function returnsPromise(node: FunctionLikeDeclaration, checker: TypeChecker): boolean {
const signature = checker.getSignatureFromDeclaration(node);
Expand Down
19 changes: 19 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingAwaitInReturn1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// <reference path="fourslash.ts" />

// @strict: true
// @target: esnext
// @lib: esnext

//// async function inner() {
//// if (Math.random() > 0.5) {
//// throw new Error("Ooops");
//// }
//// return 42;
//// }
////
//// async function outer() {
//// return inner();
//// }

verify.getSuggestionDiagnostics([]);
verify.not.codeFixAvailable(ts.Diagnostics.Add_missing_await.message);
14 changes: 14 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingAwaitInReturn10.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/// <reference path="fourslash.ts" />

// @strict: true
// @target: esnext
// @lib: esnext

//// async function inner() {
//// return 42;
//// }
////
//// const outer = async () => inner();

verify.getSuggestionDiagnostics([]);
verify.not.codeFixAvailable(ts.Diagnostics.Add_missing_await.message);
61 changes: 61 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingAwaitInReturn11.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/// <reference path="fourslash.ts" />

// @strict: true
// @target: esnext
// @lib: esnext

//// async function inner() {
//// return 42;
//// }
////
//// async function outer(v: number) {
//// try {
//// if (v > 30) {
//// [|return|] inner();
//// }
//// if (v > 20) {
//// return await inner();
//// }
//// if (v > 10) {
//// [|return|] inner();
//// }
//// [|return|] inner();
//// } catch (e) {}
//// }

verify.getSuggestionDiagnostics([{
message: ts.Diagnostics.This_may_need_await_keyword_Otherwise_the_enclosing_try_statement_won_t_handle_this.message,
code: 80011,
range: test.ranges()[0],
}, {
message: ts.Diagnostics.This_may_need_await_keyword_Otherwise_the_enclosing_try_statement_won_t_handle_this.message,
code: 80011,
range: test.ranges()[1],
}, {
message: ts.Diagnostics.This_may_need_await_keyword_Otherwise_the_enclosing_try_statement_won_t_handle_this.message,
code: 80011,
range: test.ranges()[2],
}]);
verify.codeFixAll({
fixId: "addMissingAwaitInReturn",
fixAllDescription: ts.Diagnostics.Add_all_missing_awaits.message,
newFileContent:
`async function inner() {
return 42;
}

async function outer(v: number) {
try {
if (v > 30) {
return await inner();
}
if (v > 20) {
return await inner();
}
if (v > 10) {
return await inner();
}
return await inner();
} catch (e) {}
}`,
});
41 changes: 41 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingAwaitInReturn12.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/// <reference path="fourslash.ts" />

// @strict: true
// @target: esnext
// @lib: esnext

//// async function inner() {
//// if (Math.random() > 0.5) {
//// throw new Error("Ooops");
//// }
//// return 42;
//// }
////
//// async function outer() {
//// try {
//// [|return|] Math.random() > 0.5 ? inner() : 5;
//// } catch (e) {}
//// }

verify.getSuggestionDiagnostics([{
message: ts.Diagnostics.This_may_need_await_keyword_Otherwise_the_enclosing_try_statement_won_t_handle_this.message,
code: 80011,
range: test.ranges()[0],
}]);
verify.codeFix({
description: ts.Diagnostics.Add_missing_await.message,
index: 0,
newFileContent:
`async function inner() {
if (Math.random() > 0.5) {
throw new Error("Ooops");
}
return 42;
}
async function outer() {
try {
return await (Math.random() > 0.5 ? inner() : 5);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should actually produce this:

Suggested change
return await (Math.random() > 0.5 ? inner() : 5);
return Math.random() > 0.5 ? await inner() : 5;

The current implementation is a draft and it's not yet fully implemented for more complex cases. The problem with it is that for the code like this:

return Math.random() > 0.5 ? await inner1() : inner2();

the compiler shouldn't produce smth like this

return await (Math.random() > 0.5 ? await inner1() : inner2());

but rather:

return Math.random() > 0.5 ? await inner1() : await inner2();

} catch (e) {}
}`,
});
41 changes: 41 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingAwaitInReturn2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/// <reference path="fourslash.ts" />

// @strict: true
// @target: esnext
// @lib: esnext

//// async function inner() {
//// if (Math.random() > 0.5) {
//// throw new Error("Ooops");
//// }
//// return 42;
//// }
////
//// async function outer() {
//// try {
//// [|return|] inner();
//// } catch (e) {}
//// }

verify.getSuggestionDiagnostics([{
message: ts.Diagnostics.This_may_need_await_keyword_Otherwise_the_enclosing_try_statement_won_t_handle_this.message,
code: 80011,
range: test.ranges()[0],
}]);
verify.codeFix({
description: ts.Diagnostics.Add_missing_await.message,
index: 0,
newFileContent:
`async function inner() {
if (Math.random() > 0.5) {
throw new Error("Ooops");
}
return 42;
}

async function outer() {
try {
return await inner();
} catch (e) {}
}`,
});
41 changes: 41 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingAwaitInReturn3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/// <reference path="fourslash.ts" />

// @strict: true
// @target: esnext
// @lib: esnext

//// async function inner() {
//// if (Math.random() > 0.5) {
//// throw new Error("Ooops");
//// }
//// return 42;
//// }
////
//// async function outer() {
//// try {
//// [|return|] inner();
//// } catch {}
//// }

verify.getSuggestionDiagnostics([{
message: ts.Diagnostics.This_may_need_await_keyword_Otherwise_the_enclosing_try_statement_won_t_handle_this.message,
code: 80011,
range: test.ranges()[0],
}]);
verify.codeFix({
description: ts.Diagnostics.Add_missing_await.message,
index: 0,
newFileContent:
`async function inner() {
if (Math.random() > 0.5) {
throw new Error("Ooops");
}
return 42;
}

async function outer() {
try {
return await inner();
} catch {}
}`,
});
Loading