Skip to content

Commit 87e017e

Browse files
committed
Require code to be inside a function body to offer await
1 parent c2517ee commit 87e017e

File tree

4 files changed

+52
-7
lines changed

4 files changed

+52
-7
lines changed

src/harness/fourslash.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2828,11 +2828,28 @@ Actual: ${stringify(fullActual)}`);
28282828
}
28292829
}
28302830

2831-
public verifyCodeFixAvailable(negative: boolean, expected: FourSlashInterface.VerifyCodeFixAvailableOptions[] | undefined): void {
2832-
assert(!(negative && expected), "Cannot provide an expected code fix with a negated assertion");
2831+
public verifyCodeFixAvailable(negative: boolean, expected: FourSlashInterface.VerifyCodeFixAvailableOptions[] | string | undefined): void {
28332832
const codeFixes = this.getCodeFixes(this.activeFile.fileName);
2834-
const actuals = codeFixes.map((fix): FourSlashInterface.VerifyCodeFixAvailableOptions => ({ description: fix.description, commands: fix.commands }));
2835-
this.assertObjectsEqual(actuals, negative ? ts.emptyArray : expected);
2833+
if (negative) {
2834+
if (typeof expected === "undefined") {
2835+
this.assertObjectsEqual(codeFixes, ts.emptyArray);
2836+
}
2837+
else if (typeof expected === "string") {
2838+
if (codeFixes.some(fix => fix.fixName === expected)) {
2839+
this.raiseError(`Expected not to find a fix with the name '${expected}', but one exists.`);
2840+
}
2841+
}
2842+
else {
2843+
assert(typeof expected === "undefined" || typeof expected === "string", "With a negated assertion, 'expected' must be undefined or a string value of a codefix name.");
2844+
}
2845+
}
2846+
else if (typeof expected === "string") {
2847+
this.assertObjectsEqual(codeFixes.map(({ fixName }) => fixName), [expected]);
2848+
}
2849+
else {
2850+
const actuals = codeFixes.map((fix): FourSlashInterface.VerifyCodeFixAvailableOptions => ({ description: fix.description, commands: fix.commands }));
2851+
this.assertObjectsEqual(actuals, negative ? ts.emptyArray : expected);
2852+
}
28362853
}
28372854

28382855
public verifyApplicableRefactorAvailableAtMarker(negative: boolean, markerName: string) {

src/services/codefixes/addMissingAwait.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,21 @@ namespace ts.codefix {
8888

8989
function getAwaitableExpression(sourceFile: SourceFile, errorCode: number, span: TextSpan, cancellationToken: CancellationToken, program: Program): Expression | undefined {
9090
const token = getTokenAtPosition(sourceFile, span.start);
91+
// Checker has already done work to determine that await might be possible, and has attached
92+
// related info to the node, so start by finding the expression that exactly matches up
93+
// with the diagnostic range.
9194
const expression = findAncestor(token, node => {
9295
if (node.getStart(sourceFile) < span.start || node.getEnd() > textSpanEnd(span)) {
9396
return "quit";
9497
}
9598
return isExpression(node) && textSpansEqual(span, createTextSpanFromNode(node, sourceFile));
9699
}) as Expression | undefined;
97100

98-
return isMissingAwaitError(sourceFile, errorCode, span, cancellationToken, program) ? expression : undefined;
101+
return expression
102+
&& isMissingAwaitError(sourceFile, errorCode, span, cancellationToken, program)
103+
&& isInsideAwaitableBody(expression)
104+
? expression
105+
: undefined;
99106
}
100107

101108
function findAwaitableInitializer(expression: Node, sourceFile: SourceFile, checker: TypeChecker): Expression | undefined {
@@ -116,7 +123,8 @@ namespace ts.codefix {
116123
!declaration.initializer ||
117124
variableStatement.getSourceFile() !== sourceFile ||
118125
hasModifier(variableStatement, ModifierFlags.Export) ||
119-
!variableName) {
126+
!variableName ||
127+
!isInsideAwaitableBody(declaration.initializer)) {
120128
return;
121129
}
122130

@@ -131,6 +139,16 @@ namespace ts.codefix {
131139
return declaration.initializer;
132140
}
133141

142+
function isInsideAwaitableBody(node: Node) {
143+
return !!findAncestor(node, ancestor =>
144+
ancestor.parent && isArrowFunction(ancestor.parent) && ancestor.parent.body === ancestor ||
145+
isBlock(ancestor) && (
146+
ancestor.parent.kind === SyntaxKind.FunctionDeclaration ||
147+
ancestor.parent.kind === SyntaxKind.FunctionExpression ||
148+
ancestor.parent.kind === SyntaxKind.ArrowFunction ||
149+
ancestor.parent.kind === SyntaxKind.MethodDeclaration));
150+
}
151+
134152
function makeChange(changeTracker: textChanges.ChangeTracker, errorCode: number, sourceFile: SourceFile, checker: TypeChecker, insertionSite: Expression) {
135153
if (isBinaryExpression(insertionSite)) {
136154
const { left, right } = insertionSite;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path="fourslash.ts" />
2+
////declare function getPromise(): Promise<string>;
3+
////const p = getPromise();
4+
////while (true) {
5+
//// p/*0*/.toLowerCase();
6+
//// getPromise()/*1*/.toLowerCase();
7+
////}
8+
9+
verify.not.codeFixAvailable("addMissingAwait");
10+
verify.not.codeFixAvailable("addMissingAwaitToInitializer");

tests/cases/fourslash/fourslash.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ declare namespace FourSlashInterface {
185185
applyChanges?: boolean,
186186
commands?: {}[],
187187
});
188-
codeFixAvailable(options?: ReadonlyArray<VerifyCodeFixAvailableOptions>): void;
188+
codeFixAvailable(options?: ReadonlyArray<VerifyCodeFixAvailableOptions> | string): void;
189189
applicableRefactorAvailableAtMarker(markerName: string): void;
190190
codeFixDiagnosticsAvailableAtMarkers(markerNames: string[], diagnosticCode?: number): void;
191191
applicableRefactorAvailableForRange(): void;

0 commit comments

Comments
 (0)