diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts
index f668037861798..62545e54bc7a4 100644
--- a/src/harness/fourslash.ts
+++ b/src/harness/fourslash.ts
@@ -2358,9 +2358,9 @@ namespace FourSlash {
if (!details) {
return this.raiseError(`No completions were found for the given name, source, and preferences.`);
}
- const codeActions = details.codeActions!;
- if (codeActions.length !== 1) {
- this.raiseError(`Expected one code action, got ${codeActions.length}`);
+ const codeActions = details.codeActions;
+ if (codeActions?.length !== 1) {
+ this.raiseError(`Expected one code action, got ${codeActions?.length ?? 0}`);
}
const codeAction = ts.first(codeActions);
diff --git a/src/services/codefixes/addMissingAwait.ts b/src/services/codefixes/addMissingAwait.ts
index 233c91b153b74..96cf09ed08a75 100644
--- a/src/services/codefixes/addMissingAwait.ts
+++ b/src/services/codefixes/addMissingAwait.ts
@@ -257,6 +257,7 @@ namespace ts.codefix {
sourceFile,
insertionSite.parent.expression,
createParen(createAwait(insertionSite.parent.expression)));
+ insertLeadingSemicolonIfNeeded(changeTracker, insertionSite.parent.expression, sourceFile);
}
else if (contains(callableConstructableErrorCodes, errorCode) && isCallOrNewExpression(insertionSite.parent)) {
if (fixedDeclarations && isIdentifier(insertionSite)) {
@@ -266,6 +267,7 @@ namespace ts.codefix {
}
}
changeTracker.replaceNode(sourceFile, insertionSite, createParen(createAwait(insertionSite)));
+ insertLeadingSemicolonIfNeeded(changeTracker, insertionSite, sourceFile);
}
else {
if (fixedDeclarations && isVariableDeclaration(insertionSite.parent) && isIdentifier(insertionSite.parent.name)) {
@@ -277,4 +279,11 @@ namespace ts.codefix {
changeTracker.replaceNode(sourceFile, insertionSite, createAwait(insertionSite));
}
}
+
+ function insertLeadingSemicolonIfNeeded(changeTracker: textChanges.ChangeTracker, beforeNode: Node, sourceFile: SourceFile) {
+ const precedingToken = findPrecedingToken(beforeNode.pos, sourceFile);
+ if (precedingToken && positionIsASICandidate(precedingToken.end, precedingToken.parent, sourceFile)) {
+ changeTracker.insertText(sourceFile, beforeNode.getStart(sourceFile), ";");
+ }
+ }
}
diff --git a/src/services/completions.ts b/src/services/completions.ts
index c47b4dab95a07..8cca9b678d93e 100644
--- a/src/services/completions.ts
+++ b/src/services/completions.ts
@@ -373,7 +373,13 @@ namespace ts.Completions {
}
if (origin && originIsPromise(origin) && propertyAccessToConvert) {
if (insertText === undefined) insertText = name;
- const awaitText = `(await ${propertyAccessToConvert.expression.getText()})`;
+ const precedingToken = findPrecedingToken(propertyAccessToConvert.pos, sourceFile);
+ let awaitText = "";
+ if (precedingToken && positionIsASICandidate(precedingToken.end, precedingToken.parent, sourceFile)) {
+ awaitText = ";";
+ }
+
+ awaitText += `(await ${propertyAccessToConvert.expression.getText()})`;
insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}${insertQuestionDot ? "?." : "."}${insertText}`;
replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end);
}
diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts
index 8a86cd7a65de2..59dcabfd6bcb2 100644
--- a/src/services/formatting/rules.ts
+++ b/src/services/formatting/rules.ts
@@ -855,13 +855,6 @@ namespace ts.formatting {
}
function isSemicolonInsertionContext(context: FormattingContext): boolean {
- const contextAncestor = findAncestor(context.currentTokenParent, ancestor => {
- if (ancestor.end !== context.currentTokenSpan.end) {
- return "quit";
- }
- return syntaxMayBeASICandidate(ancestor.kind);
- });
-
- return !!contextAncestor && isASICandidate(contextAncestor, context.sourceFile);
+ return positionIsASICandidate(context.currentTokenSpan.end, context.currentTokenParent, context.sourceFile);
}
}
diff --git a/src/services/utilities.ts b/src/services/utilities.ts
index 55dc95d3fc2a7..315f61c967d92 100644
--- a/src/services/utilities.ts
+++ b/src/services/utilities.ts
@@ -2066,7 +2066,7 @@ namespace ts {
syntaxRequiresTrailingModuleBlockOrSemicolonOrASI,
syntaxRequiresTrailingSemicolonOrASI);
- export function isASICandidate(node: Node, sourceFile: SourceFileLike): boolean {
+ function nodeIsASICandidate(node: Node, sourceFile: SourceFileLike): boolean {
const lastToken = node.getLastToken(sourceFile);
if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) {
return false;
@@ -2109,6 +2109,17 @@ namespace ts {
return startLine !== endLine;
}
+ export function positionIsASICandidate(pos: number, context: Node, sourceFile: SourceFileLike): boolean {
+ const contextAncestor = findAncestor(context, ancestor => {
+ if (ancestor.end !== pos) {
+ return "quit";
+ }
+ return syntaxMayBeASICandidate(ancestor.kind);
+ });
+
+ return !!contextAncestor && nodeIsASICandidate(contextAncestor, sourceFile);
+ }
+
export function probablyUsesSemicolons(sourceFile: SourceFile): boolean {
let withSemicolon = 0;
let withoutSemicolon = 0;
diff --git a/tests/cases/fourslash/codeFixAddMissingAwait_propertyAccess2.ts b/tests/cases/fourslash/codeFixAddMissingAwait_propertyAccess2.ts
new file mode 100644
index 0000000000000..e024028b1406f
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAddMissingAwait_propertyAccess2.ts
@@ -0,0 +1,15 @@
+///
+////async function fn(a: Promise<{ x: string }>) {
+//// console.log(3)
+//// a.x;
+////}
+
+verify.codeFix({
+ description: ts.Diagnostics.Add_await.message,
+ index: 0,
+ newFileContent:
+`async function fn(a: Promise<{ x: string }>) {
+ console.log(3)
+ ;(await a).x;
+}`
+});
diff --git a/tests/cases/fourslash/codeFixAddMissingAwait_signatures2.ts b/tests/cases/fourslash/codeFixAddMissingAwait_signatures2.ts
new file mode 100644
index 0000000000000..17f6651b3791c
--- /dev/null
+++ b/tests/cases/fourslash/codeFixAddMissingAwait_signatures2.ts
@@ -0,0 +1,50 @@
+///
+////async function fn(a: Promise<() => void>, b: Promise<() => void> | (() => void), C: Promise<{ new(): any }>) {
+//// a()
+//// b()
+//// new C()
+////}
+
+verify.codeFix({
+ description: ts.Diagnostics.Add_await.message,
+ index: 0,
+ newFileContent:
+`async function fn(a: Promise<() => void>, b: Promise<() => void> | (() => void), C: Promise<{ new(): any }>) {
+ (await a)()
+ b()
+ new C()
+}`
+});
+
+verify.codeFix({
+ description: ts.Diagnostics.Add_await.message,
+ index: 1,
+ newFileContent:
+`async function fn(a: Promise<() => void>, b: Promise<() => void> | (() => void), C: Promise<{ new(): any }>) {
+ a()
+ ;(await b)()
+ new C()
+}`
+});
+
+verify.codeFix({
+ description: ts.Diagnostics.Add_await.message,
+ index: 2,
+ newFileContent:
+`async function fn(a: Promise<() => void>, b: Promise<() => void> | (() => void), C: Promise<{ new(): any }>) {
+ a()
+ b()
+ new (await C)()
+}`
+});
+
+verify.codeFixAll({
+ fixAllDescription: ts.Diagnostics.Fix_all_expressions_possibly_missing_await.message,
+ fixId: "addMissingAwait",
+ newFileContent:
+`async function fn(a: Promise<() => void>, b: Promise<() => void> | (() => void), C: Promise<{ new(): any }>) {
+ (await a)()
+ ;(await b)()
+ new (await C)()
+}`
+});
diff --git a/tests/cases/fourslash/completionOfAwaitPromise7.ts b/tests/cases/fourslash/completionOfAwaitPromise7.ts
new file mode 100644
index 0000000000000..b8860a7f2c16c
--- /dev/null
+++ b/tests/cases/fourslash/completionOfAwaitPromise7.ts
@@ -0,0 +1,18 @@
+///
+
+////async function foo(x: Promise) {
+//// console.log
+//// [|x./**/|]
+////}
+
+const replacementSpan = test.ranges()[0];
+verify.completions({
+ marker: "",
+ includes: [
+ "then",
+ { name: "trim", insertText: ';(await x).trim', replacementSpan },
+ ],
+ preferences: {
+ includeInsertTextCompletions: true,
+ },
+});