Skip to content

Commit 571ca60

Browse files
authored
Add preceding semicolon on await insertion when parentheses are included (#34627)
* Add preceding semicolon on await insertion when parentheses are included * Just start with precedingToken * Fix semicolon formatter regression * Delete test with debatable expected behavior * Lint after control flow changes
1 parent b50b9e0 commit 571ca60

File tree

8 files changed

+115
-13
lines changed

8 files changed

+115
-13
lines changed

src/harness/fourslash.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2358,9 +2358,9 @@ namespace FourSlash {
23582358
if (!details) {
23592359
return this.raiseError(`No completions were found for the given name, source, and preferences.`);
23602360
}
2361-
const codeActions = details.codeActions!;
2362-
if (codeActions.length !== 1) {
2363-
this.raiseError(`Expected one code action, got ${codeActions.length}`);
2361+
const codeActions = details.codeActions;
2362+
if (codeActions?.length !== 1) {
2363+
this.raiseError(`Expected one code action, got ${codeActions?.length ?? 0}`);
23642364
}
23652365
const codeAction = ts.first(codeActions);
23662366

src/services/codefixes/addMissingAwait.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ namespace ts.codefix {
257257
sourceFile,
258258
insertionSite.parent.expression,
259259
createParen(createAwait(insertionSite.parent.expression)));
260+
insertLeadingSemicolonIfNeeded(changeTracker, insertionSite.parent.expression, sourceFile);
260261
}
261262
else if (contains(callableConstructableErrorCodes, errorCode) && isCallOrNewExpression(insertionSite.parent)) {
262263
if (fixedDeclarations && isIdentifier(insertionSite)) {
@@ -266,6 +267,7 @@ namespace ts.codefix {
266267
}
267268
}
268269
changeTracker.replaceNode(sourceFile, insertionSite, createParen(createAwait(insertionSite)));
270+
insertLeadingSemicolonIfNeeded(changeTracker, insertionSite, sourceFile);
269271
}
270272
else {
271273
if (fixedDeclarations && isVariableDeclaration(insertionSite.parent) && isIdentifier(insertionSite.parent.name)) {
@@ -277,4 +279,11 @@ namespace ts.codefix {
277279
changeTracker.replaceNode(sourceFile, insertionSite, createAwait(insertionSite));
278280
}
279281
}
282+
283+
function insertLeadingSemicolonIfNeeded(changeTracker: textChanges.ChangeTracker, beforeNode: Node, sourceFile: SourceFile) {
284+
const precedingToken = findPrecedingToken(beforeNode.pos, sourceFile);
285+
if (precedingToken && positionIsASICandidate(precedingToken.end, precedingToken.parent, sourceFile)) {
286+
changeTracker.insertText(sourceFile, beforeNode.getStart(sourceFile), ";");
287+
}
288+
}
280289
}

src/services/completions.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,13 @@ namespace ts.Completions {
373373
}
374374
if (origin && originIsPromise(origin) && propertyAccessToConvert) {
375375
if (insertText === undefined) insertText = name;
376-
const awaitText = `(await ${propertyAccessToConvert.expression.getText()})`;
376+
const precedingToken = findPrecedingToken(propertyAccessToConvert.pos, sourceFile);
377+
let awaitText = "";
378+
if (precedingToken && positionIsASICandidate(precedingToken.end, precedingToken.parent, sourceFile)) {
379+
awaitText = ";";
380+
}
381+
382+
awaitText += `(await ${propertyAccessToConvert.expression.getText()})`;
377383
insertText = needsConvertPropertyAccess ? `${awaitText}${insertText}` : `${awaitText}${insertQuestionDot ? "?." : "."}${insertText}`;
378384
replacementSpan = createTextSpanFromBounds(propertyAccessToConvert.getStart(sourceFile), propertyAccessToConvert.end);
379385
}

src/services/formatting/rules.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -855,13 +855,6 @@ namespace ts.formatting {
855855
}
856856

857857
function isSemicolonInsertionContext(context: FormattingContext): boolean {
858-
const contextAncestor = findAncestor(context.currentTokenParent, ancestor => {
859-
if (ancestor.end !== context.currentTokenSpan.end) {
860-
return "quit";
861-
}
862-
return syntaxMayBeASICandidate(ancestor.kind);
863-
});
864-
865-
return !!contextAncestor && isASICandidate(contextAncestor, context.sourceFile);
858+
return positionIsASICandidate(context.currentTokenSpan.end, context.currentTokenParent, context.sourceFile);
866859
}
867860
}

src/services/utilities.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2066,7 +2066,7 @@ namespace ts {
20662066
syntaxRequiresTrailingModuleBlockOrSemicolonOrASI,
20672067
syntaxRequiresTrailingSemicolonOrASI);
20682068

2069-
export function isASICandidate(node: Node, sourceFile: SourceFileLike): boolean {
2069+
function nodeIsASICandidate(node: Node, sourceFile: SourceFileLike): boolean {
20702070
const lastToken = node.getLastToken(sourceFile);
20712071
if (lastToken && lastToken.kind === SyntaxKind.SemicolonToken) {
20722072
return false;
@@ -2109,6 +2109,17 @@ namespace ts {
21092109
return startLine !== endLine;
21102110
}
21112111

2112+
export function positionIsASICandidate(pos: number, context: Node, sourceFile: SourceFileLike): boolean {
2113+
const contextAncestor = findAncestor(context, ancestor => {
2114+
if (ancestor.end !== pos) {
2115+
return "quit";
2116+
}
2117+
return syntaxMayBeASICandidate(ancestor.kind);
2118+
});
2119+
2120+
return !!contextAncestor && nodeIsASICandidate(contextAncestor, sourceFile);
2121+
}
2122+
21122123
export function probablyUsesSemicolons(sourceFile: SourceFile): boolean {
21132124
let withSemicolon = 0;
21142125
let withoutSemicolon = 0;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path="fourslash.ts" />
2+
////async function fn(a: Promise<{ x: string }>) {
3+
//// console.log(3)
4+
//// a.x;
5+
////}
6+
7+
verify.codeFix({
8+
description: ts.Diagnostics.Add_await.message,
9+
index: 0,
10+
newFileContent:
11+
`async function fn(a: Promise<{ x: string }>) {
12+
console.log(3)
13+
;(await a).x;
14+
}`
15+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/// <reference path="fourslash.ts" />
2+
////async function fn(a: Promise<() => void>, b: Promise<() => void> | (() => void), C: Promise<{ new(): any }>) {
3+
//// a()
4+
//// b()
5+
//// new C()
6+
////}
7+
8+
verify.codeFix({
9+
description: ts.Diagnostics.Add_await.message,
10+
index: 0,
11+
newFileContent:
12+
`async function fn(a: Promise<() => void>, b: Promise<() => void> | (() => void), C: Promise<{ new(): any }>) {
13+
(await a)()
14+
b()
15+
new C()
16+
}`
17+
});
18+
19+
verify.codeFix({
20+
description: ts.Diagnostics.Add_await.message,
21+
index: 1,
22+
newFileContent:
23+
`async function fn(a: Promise<() => void>, b: Promise<() => void> | (() => void), C: Promise<{ new(): any }>) {
24+
a()
25+
;(await b)()
26+
new C()
27+
}`
28+
});
29+
30+
verify.codeFix({
31+
description: ts.Diagnostics.Add_await.message,
32+
index: 2,
33+
newFileContent:
34+
`async function fn(a: Promise<() => void>, b: Promise<() => void> | (() => void), C: Promise<{ new(): any }>) {
35+
a()
36+
b()
37+
new (await C)()
38+
}`
39+
});
40+
41+
verify.codeFixAll({
42+
fixAllDescription: ts.Diagnostics.Fix_all_expressions_possibly_missing_await.message,
43+
fixId: "addMissingAwait",
44+
newFileContent:
45+
`async function fn(a: Promise<() => void>, b: Promise<() => void> | (() => void), C: Promise<{ new(): any }>) {
46+
(await a)()
47+
;(await b)()
48+
new (await C)()
49+
}`
50+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
////async function foo(x: Promise<string>) {
4+
//// console.log
5+
//// [|x./**/|]
6+
////}
7+
8+
const replacementSpan = test.ranges()[0];
9+
verify.completions({
10+
marker: "",
11+
includes: [
12+
"then",
13+
{ name: "trim", insertText: ';(await x).trim', replacementSpan },
14+
],
15+
preferences: {
16+
includeInsertTextCompletions: true,
17+
},
18+
});

0 commit comments

Comments
 (0)