Skip to content

Commit b487ff1

Browse files
committed
Better top level await check
1 parent eb04520 commit b487ff1

4 files changed

Lines changed: 43 additions & 17 deletions

File tree

src/transformation/utils/typescript/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,19 @@ export function getFunctionTypeForCall(context: TransformationContext, node: ts.
9292
}
9393
return context.checker.getTypeFromTypeNode(typeDeclaration.type);
9494
}
95+
96+
// https://github.com/microsoft/TypeScript/blob/663b19fe4a7c4d4ddaa61aedadd28da06acd27b6/src/services/documentHighlights.ts#L435
97+
// Do not cross function/class/interface/module/type boundaries.
98+
export function traverseWithoutCrossingFunction(node: ts.Node, cb: (node: ts.Node) => void) {
99+
cb(node);
100+
if (
101+
!ts.isFunctionLike(node) &&
102+
!ts.isClassLike(node) &&
103+
!ts.isInterfaceDeclaration(node) &&
104+
!ts.isModuleDeclaration(node) &&
105+
!ts.isTypeAliasDeclaration(node) &&
106+
!ts.isTypeNode(node)
107+
) {
108+
ts.forEachChild(node, child => traverseWithoutCrossingFunction(child, cb));
109+
}
110+
}

src/transformation/visitors/function.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,11 @@ export function transformFunctionToExpression(
196196
spreadIdentifier,
197197
node
198198
);
199+
200+
const possiblyAsyncBody = isAsyncFunction(node) ? wrapInAsyncAwaiter(context, transformedBody) : transformedBody;
201+
199202
const functionExpression = lua.createFunctionExpression(
200-
lua.createBlock(isAsyncFunction(node) ? wrapInAsyncAwaiter(context, transformedBody) : transformedBody),
203+
lua.createBlock(possiblyAsyncBody),
201204
paramNames,
202205
dotsLiteral,
203206
flags,

src/transformation/visitors/sourceFile.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { notAllowedTopLevelAwait } from "../utils/diagnostics";
66
import { createExportsIdentifier } from "../utils/lua-ast";
77
import { getUsedLuaLibFeatures } from "../utils/lualib";
88
import { performHoisting, popScope, pushScope, ScopeType } from "../utils/scope";
9-
import { hasExportEquals } from "../utils/typescript";
9+
import { hasExportEquals, traverseWithoutCrossingFunction } from "../utils/typescript";
1010

1111
export const transformSourceFileNode: FunctionVisitor<ts.SourceFile> = (node, context) => {
1212
let statements: lua.Statement[] = [];
@@ -52,11 +52,12 @@ export const transformSourceFileNode: FunctionVisitor<ts.SourceFile> = (node, co
5252
};
5353

5454
function isTopLevelAwait(statement: ts.Statement) {
55-
return (
56-
(ts.isExpressionStatement(statement) && ts.isAwaitExpression(statement.expression)) ||
57-
(ts.isVariableStatement(statement) &&
58-
statement.declarationList.declarations.some(
59-
declaration => declaration.initializer && ts.isAwaitExpression(declaration.initializer)
60-
))
61-
);
55+
// Check if expression contains an await child, without going into function declarations
56+
let containsAwait = false;
57+
traverseWithoutCrossingFunction(statement, node => {
58+
if (ts.isAwaitExpression(node)) {
59+
containsAwait = true;
60+
}
61+
});
62+
return containsAwait;
6263
}

test/unit/builtins/async-await.spec.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,18 +225,24 @@ test("can call async function at top-level", () => {
225225
});
226226
});
227227

228-
test.each(["await a();", "const b = await a();", "export const b = await a();"])(
229-
"cannot await at top-level (%p)",
230-
awaitUsage => {
231-
util.testModule`
228+
test.each([
229+
"await a();",
230+
"const b = await a();",
231+
"export const b = await a();",
232+
"declare function foo(n: number): number; foo(await a());",
233+
"declare function foo(n: number): number; const b = foo(await a());",
234+
"const b = [await a()];",
235+
"const b = [4, await a()];",
236+
"const b = true ? 4 : await a();",
237+
])("cannot await at top-level (%p)", awaitUsage => {
238+
util.testModule`
232239
async function a() {
233240
return 42;
234241
}
235242
236243
${awaitUsage}
237244
export {} // Required to make TS happy, cannot await without import/exports
238245
`
239-
.setOptions({ module: ModuleKind.ESNext, target: ScriptTarget.ES2017 })
240-
.expectToHaveDiagnostics([notAllowedTopLevelAwait.code]);
241-
}
242-
);
246+
.setOptions({ module: ModuleKind.ESNext, target: ScriptTarget.ES2017 })
247+
.expectToHaveDiagnostics([notAllowedTopLevelAwait.code]);
248+
});

0 commit comments

Comments
 (0)