mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-12 21:37:41 -06:00
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:
parent
b50b9e0fd2
commit
571ca60b08
@ -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);
|
||||
|
||||
|
||||
@ -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), ";");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}`
|
||||
});
|
||||
50
tests/cases/fourslash/codeFixAddMissingAwait_signatures2.ts
Normal file
50
tests/cases/fourslash/codeFixAddMissingAwait_signatures2.ts
Normal 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)()
|
||||
}`
|
||||
});
|
||||
18
tests/cases/fourslash/completionOfAwaitPromise7.ts
Normal file
18
tests/cases/fourslash/completionOfAwaitPromise7.ts
Normal 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,
|
||||
},
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user