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
This commit is contained in:
Andrew Branch 2019-11-19 13:11:42 -08:00 committed by GitHub
parent b50b9e0fd2
commit 571ca60b08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 115 additions and 13 deletions

View File

@ -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);

View File

@ -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), ";");
}
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -0,0 +1,15 @@
/// <reference path="fourslash.ts" />
////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;
}`
});

View File

@ -0,0 +1,50 @@
/// <reference path="fourslash.ts" />
////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)()
}`
});

View File

@ -0,0 +1,18 @@
/// <reference path='fourslash.ts'/>
////async function foo(x: Promise<string>) {
//// console.log
//// [|x./**/|]
////}
const replacementSpan = test.ranges()[0];
verify.completions({
marker: "",
includes: [
"then",
{ name: "trim", insertText: ';(await x).trim', replacementSpan },
],
preferences: {
includeInsertTextCompletions: true,
},
});