diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index 826efa6994b00..d22193ab08d13 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -3843,6 +3843,10 @@
"category": "Message",
"code": 90028
},
+ "Add async modifier to containing function": {
+ "category": "Message",
+ "code": 90029
+ },
"Convert function to an ES2015 class": {
"category": "Message",
"code": 95001
diff --git a/src/services/codefixes/fixAwaitInSyncFunction.ts b/src/services/codefixes/fixAwaitInSyncFunction.ts
new file mode 100644
index 0000000000000..883993e7b5146
--- /dev/null
+++ b/src/services/codefixes/fixAwaitInSyncFunction.ts
@@ -0,0 +1,74 @@
+/* @internal */
+namespace ts.codefix {
+ const fixId = "fixAwaitInSyncFunction";
+ const errorCodes = [
+ Diagnostics.await_expression_is_only_allowed_within_an_async_function.code,
+ Diagnostics.A_for_await_of_statement_is_only_allowed_within_an_async_function_or_async_generator.code,
+ ];
+ registerCodeFix({
+ errorCodes,
+ getCodeActions(context) {
+ const { sourceFile, span } = context;
+ const nodes = getNodes(sourceFile, span.start);
+ if (!nodes) return undefined;
+ const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, nodes));
+ return [{ description: getLocaleSpecificMessage(Diagnostics.Add_async_modifier_to_containing_function), changes, fixId }];
+ },
+ fixIds: [fixId],
+ getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
+ const nodes = getNodes(diag.file, diag.start);
+ if (!nodes) return;
+ doChange(changes, context.sourceFile, nodes);
+ }),
+ });
+
+ function getReturnType(expr: FunctionDeclaration | MethodDeclaration | FunctionExpression | ArrowFunction) {
+ if (expr.type) {
+ return expr.type;
+ }
+ if (isVariableDeclaration(expr.parent) &&
+ expr.parent.type &&
+ isFunctionTypeNode(expr.parent.type)) {
+ return expr.parent.type.type;
+ }
+ }
+
+ function getNodes(sourceFile: SourceFile, start: number): { insertBefore: Node, returnType: TypeNode | undefined } | undefined {
+ const token = getTokenAtPosition(sourceFile, start, /*includeJsDocComment*/ false);
+ const containingFunction = getContainingFunction(token);
+ let insertBefore: Node | undefined;
+ switch (containingFunction.kind) {
+ case SyntaxKind.MethodDeclaration:
+ insertBefore = containingFunction.name;
+ break;
+ case SyntaxKind.FunctionDeclaration:
+ case SyntaxKind.FunctionExpression:
+ insertBefore = findChildOfKind(containingFunction, SyntaxKind.FunctionKeyword, sourceFile);
+ break;
+ case SyntaxKind.ArrowFunction:
+ insertBefore = findChildOfKind(containingFunction, SyntaxKind.OpenParenToken, sourceFile) || first(containingFunction.parameters);
+ break;
+ default:
+ return;
+ }
+
+ return {
+ insertBefore,
+ returnType: getReturnType(containingFunction)
+ };
+ }
+
+ function doChange(
+ changes: textChanges.ChangeTracker,
+ sourceFile: SourceFile,
+ { insertBefore, returnType }: { insertBefore: Node | undefined, returnType: TypeNode | undefined }): void {
+
+ if (returnType) {
+ const entityName = getEntityNameFromTypeNode(returnType);
+ if (!entityName || entityName.kind !== SyntaxKind.Identifier || entityName.text !== "Promise") {
+ changes.replaceNode(sourceFile, returnType, createTypeReferenceNode("Promise", createNodeArray([returnType])));
+ }
+ }
+ changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, insertBefore);
+ }
+}
diff --git a/src/services/codefixes/fixes.ts b/src/services/codefixes/fixes.ts
index 5b1ee8ec8572c..6049cc70295cd 100644
--- a/src/services/codefixes/fixes.ts
+++ b/src/services/codefixes/fixes.ts
@@ -11,6 +11,7 @@
///
///
///
+///
///
///
///
diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts
index e73c639ba79bd..b0efef4464200 100644
--- a/src/services/textChanges.ts
+++ b/src/services/textChanges.ts
@@ -345,6 +345,11 @@ namespace ts.textChanges {
return this.replaceWithSingle(sourceFile, startPosition, startPosition, newNode, this.getOptionsForInsertNodeBefore(before, blankLineBetween));
}
+ public insertModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void {
+ const pos = before.getStart(sourceFile);
+ this.replaceWithSingle(sourceFile, pos, pos, createToken(modifier), { suffix: " " });
+ }
+
public changeIdentifierToPropertyAccess(sourceFile: SourceFile, prefix: string, node: Identifier): void {
const startPosition = getAdjustedStartPosition(sourceFile, node, {}, Position.Start);
this.replaceWithSingle(sourceFile, startPosition, startPosition, createPropertyAccess(createIdentifier(prefix), ""), {});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction1.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction1.ts
new file mode 100644
index 0000000000000..e64d4072757a6
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction1.ts
@@ -0,0 +1,13 @@
+///
+
+////function f() {
+//// await Promise.resolve();
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`async function f() {
+ await Promise.resolve();
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction10.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction10.ts
new file mode 100644
index 0000000000000..42e0bc5be780f
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction10.ts
@@ -0,0 +1,13 @@
+///
+
+////const f: () => number | string = () => {
+//// await Promise.resolve('foo');
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`const f: () => Promise = async () => {
+ await Promise.resolve('foo');
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction11.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction11.ts
new file mode 100644
index 0000000000000..bc7b17f8db5b4
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction11.ts
@@ -0,0 +1,14 @@
+///
+
+////const f: string = () => {
+//// await Promise.resolve('foo');
+////}
+
+// should not change type if it's incorrectly set
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`const f: string = async () => {
+ await Promise.resolve('foo');
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction12.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction12.ts
new file mode 100644
index 0000000000000..ee694a80e9f26
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction12.ts
@@ -0,0 +1,13 @@
+///
+
+////const f: () => Array = function() {
+//// await Promise.resolve([]);
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`const f: () => Promise> = async function() {
+ await Promise.resolve([]);
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction13.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction13.ts
new file mode 100644
index 0000000000000..06f54d29eeb68
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction13.ts
@@ -0,0 +1,13 @@
+///
+
+////const f: () => Promise = () => {
+//// await Promise.resolve('foo');
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`const f: () => Promise = async () => {
+ await Promise.resolve('foo');
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction14.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction14.ts
new file mode 100644
index 0000000000000..c798af5f5abd6
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction14.ts
@@ -0,0 +1,13 @@
+///
+
+////const f = function(): number {
+//// await Promise.resolve(1);
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`const f = async function(): Promise {
+ await Promise.resolve(1);
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction15.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction15.ts
new file mode 100644
index 0000000000000..a2c6f7dcb1ad9
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction15.ts
@@ -0,0 +1,13 @@
+///
+
+////const f = (): number[] => {
+//// await Promise.resolve([1]);
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`const f = async (): Promise => {
+ await Promise.resolve([1]);
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction2.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction2.ts
new file mode 100644
index 0000000000000..e8da351af5d0a
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction2.ts
@@ -0,0 +1,13 @@
+///
+
+////const f = function() {
+//// await Promise.resolve();
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`const f = async function() {
+ await Promise.resolve();
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction3.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction3.ts
new file mode 100644
index 0000000000000..54d0aba103be8
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction3.ts
@@ -0,0 +1,12 @@
+///
+
+////const f = {
+//// get a() {
+//// return await Promise.resolve();
+//// },
+//// get a() {
+//// await Promise.resolve();
+//// },
+////}
+
+verify.not.codeFixAvailable();
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction4.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction4.ts
new file mode 100644
index 0000000000000..dd123e25d0bf3
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction4.ts
@@ -0,0 +1,9 @@
+///
+
+////class Foo {
+//// constructor {
+//// await Promise.resolve();
+//// }
+////}
+
+verify.not.codeFixAvailable();
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction5.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction5.ts
new file mode 100644
index 0000000000000..a1c58b53831d4
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction5.ts
@@ -0,0 +1,17 @@
+///
+
+////class Foo {
+//// bar() {
+//// await Promise.resolve();
+//// }
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`class Foo {
+ async bar() {
+ await Promise.resolve();
+ }
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction6.5.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction6.5.ts
new file mode 100644
index 0000000000000..c1b06811113ef
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction6.5.ts
@@ -0,0 +1,13 @@
+///
+
+////const f = promise => {
+//// await promise;
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`const f = async promise => {
+ await promise;
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction6.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction6.ts
new file mode 100644
index 0000000000000..0b0aa09816443
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction6.ts
@@ -0,0 +1,13 @@
+///
+
+////const f = (promise) => {
+//// await promise;
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`const f = async (promise) => {
+ await promise;
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction7.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction7.ts
new file mode 100644
index 0000000000000..a467e9ee0cef1
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction7.ts
@@ -0,0 +1,17 @@
+///
+
+////function f() {
+//// for await (const x of g()) {
+//// console.log(x);
+//// }
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`async function f() {
+ for await (const x of g()) {
+ console.log(x);
+ }
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction8.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction8.ts
new file mode 100644
index 0000000000000..7c43add3edd21
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction8.ts
@@ -0,0 +1,13 @@
+///
+
+////function f(): number | string {
+//// await Promise.resolve(8);
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`async function f(): Promise {
+ await Promise.resolve(8);
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction9.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction9.ts
new file mode 100644
index 0000000000000..f93603e69c457
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction9.ts
@@ -0,0 +1,17 @@
+///
+
+////class Foo {
+//// bar(): string {
+//// await Promise.resolve('baz');
+//// }
+////}
+
+verify.codeFix({
+ description: "Add async modifier to containing function",
+ newFileContent:
+`class Foo {
+ async bar(): Promise {
+ await Promise.resolve('baz');
+ }
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixAwaitInSyncFunction_all.ts b/tests/cases/fourslash/codeFixAwaitInSyncFunction_all.ts
new file mode 100644
index 0000000000000..e6314a3a5f0a0
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAwaitInSyncFunction_all.ts
@@ -0,0 +1,21 @@
+///
+
+////function f() {
+//// await Promise.resolve();
+////}
+////
+////const g = () => {
+//// await f();
+////}
+
+verify.codeFixAll({
+ fixId: "fixAwaitInSyncFunction",
+ newFileContent:
+`async function f() {
+ await Promise.resolve();
+}
+
+const g = async () => {
+ await f();
+}`,
+});