Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse for await (async of ...) #13244

Merged
merged 7 commits into from May 1, 2021
9 changes: 7 additions & 2 deletions packages/babel-generator/src/node/parentheses.ts
Expand Up @@ -283,8 +283,13 @@ export function LogicalExpression(node: any, parent: any): boolean {

export function Identifier(node: t.Identifier, parent: t.Node): boolean {
// ECMAScript specifically forbids a for-of loop from starting with the
// token sequence "for ( async of", because it would be ambiguous with
// "for (async of => {};;)", so we need to add extra parentheses.
// token sequence `for (async of`, because it would be ambiguous with
// `for (async of => {};;)`, so we need to add extra parentheses.
//
// If the parent is a for-await-of loop (i.e. parent.await === true), the
// parentheses aren't strictly needed, but we add them anyway because
// some tools (including earlier Babel versions) can't parse
// `for await (async of [])` without them.
return (
node.name === "async" && t.isForOfStatement(parent) && node === parent.left
);
Expand Down
Expand Up @@ -4,4 +4,6 @@ for ((async) of async) async;

for (\u0061sync of []);

for (async.x of []);

for (async in []);
Expand Up @@ -4,4 +4,6 @@ for ((async) of async) async;

for ((async) of []);

for (async.x of []);

for (async in []);
@@ -0,0 +1,11 @@
async function f() {
for await (async of []);

for await (async of async) async;

for await ((async) of []);

for await (async.x of []);

for await (\u0061sync of []);
}
@@ -0,0 +1,11 @@
async function f() {
for await ((async) of []);

for await ((async) of async) async;

for await ((async) of []);

for await (async.x of []);

for await ((async) of []);
}
1 change: 1 addition & 0 deletions packages/babel-parser/src/parser/error-message.js
Expand Up @@ -61,6 +61,7 @@ export const ErrorMessages = makeErrorTemplates(
"'from' is not allowed as an identifier after 'export default'.",
ForInOfLoopInitializer:
"'%0' loop variable declaration may not have an initializer.",
ForOfAsync: "The left-hand side of a for-of loop may not be 'async'.",
ForOfLet: "The left-hand side of a for-of loop may not start with 'let'.",
GeneratorInSingleStatementContext:
"Generators can only be declared at the top level or inside a block.",
Expand Down
11 changes: 10 additions & 1 deletion packages/babel-parser/src/parser/expression.js
Expand Up @@ -1032,7 +1032,16 @@ export default class ExpressionParser extends LValParser {
true,
);
} else if (this.match(tt.name)) {
return this.parseAsyncArrowUnaryFunction(id);
// If the next token begins with "=", commit to parsing an async
// arrow function. (Peeking ahead for "=" lets us avoid a more
// expensive full-token lookahead on this common path.)
if (this.lookaheadCharCode() === charCodes.equalsTo) {
return this.parseAsyncArrowUnaryFunction(id);
} else {
// Otherwise, treat "async" as an identifier and let calling code
// deal with the current tt.name token.
return id;
}
} else if (this.match(tt._do)) {
return this.parseDo(true);
}
Expand Down
24 changes: 22 additions & 2 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -539,13 +539,33 @@ export default class StatementParser extends ExpressionParser {
return this.parseFor(node, init);
}

// Check whether the first token is possibly a contextual keyword, so that
// we can forbid `for (async of` if this turns out to be a for-of loop.
const startsWithUnescapedName =
this.match(tt.name) && !this.state.containsEsc;

const refExpressionErrors = new ExpressionErrors();
const init = this.parseExpression(true, refExpressionErrors);
const isForOf = this.isContextual("of");
if (isForOf || this.match(tt._in)) {
if (isForOf && startsWithLet) {
if (isForOf) {
// Check for leading tokens that are forbidden in for-of loops:
if (startsWithLet) {
this.raise(init.start, Errors.ForOfLet);
} else if (
// `for await (async of []);` is allowed.
awaitAt === -1 &&
startsWithUnescapedName &&
init.type === "Identifier" &&
init.name === "async"
) {
// This catches the case where the `async` in `for (async of` was
// parsed as an identifier. If it was parsed as the start of an async
// arrow function (e.g. `for (async of => {} of []);`), the LVal check
// further down will raise a more appropriate error.
this.raise(init.start, Errors.ForOfAsync);
}
}
if (isForOf || this.match(tt._in)) {
this.toAssignable(init, /* isLHS */ true);
const description = isForOf ? "for-of statement" : "for-in statement";
this.checkLVal(init, description);
Expand Down
@@ -1,4 +1,3 @@
{
"sourceType": "module",
"throws": "Unexpected token, expected \"=>\" (1:31)"
}
"sourceType": "module"
}
@@ -0,0 +1,46 @@
{
"type": "File",
"start":0,"end":36,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":36}},
"errors": [
"SyntaxError: Missing semicolon. (1:20)",
"SyntaxError: Missing semicolon. (1:33)"
],
"program": {
"type": "Program",
"start":0,"end":36,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":36}},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ExportDefaultDeclaration",
"start":0,"end":20,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":20}},
"declaration": {
"type": "Identifier",
"start":15,"end":20,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":20},"identifierName":"async"},
"name": "async"
}
},
{
"type": "ExpressionStatement",
"start":21,"end":33,"loc":{"start":{"line":1,"column":21},"end":{"line":1,"column":33}},
"expression": {
"type": "CallExpression",
"start":21,"end":33,"loc":{"start":{"line":1,"column":21},"end":{"line":1,"column":33}},
"callee": {
"type": "Identifier",
"start":21,"end":30,"loc":{"start":{"line":1,"column":21},"end":{"line":1,"column":30},"identifierName":"functionX"},
"name": "functionX"
},
"arguments": []
}
},
{
"type": "BlockStatement",
"start":34,"end":36,"loc":{"start":{"line":1,"column":34},"end":{"line":1,"column":36}},
"body": [],
"directives": []
}
],
"directives": []
}
}
@@ -0,0 +1,3 @@
async function f() {
for await (async of => {};;);
}
@@ -0,0 +1,3 @@
{
"throws": "Unexpected token (2:6)"
}
@@ -0,0 +1,3 @@
async function f() {
for await (async of => {} of x);
}
@@ -0,0 +1,69 @@
{
"type": "File",
"start":0,"end":57,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"errors": [
"SyntaxError: Invalid left-hand side in for-of statement. (2:13)"
],
"program": {
"type": "Program",
"start":0,"end":57,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "FunctionDeclaration",
"start":0,"end":57,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"id": {
"type": "Identifier",
"start":15,"end":16,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":16},"identifierName":"f"},
"name": "f"
},
"generator": false,
"async": true,
"params": [],
"body": {
"type": "BlockStatement",
"start":19,"end":57,"loc":{"start":{"line":1,"column":19},"end":{"line":3,"column":1}},
"body": [
{
"type": "ForOfStatement",
"start":23,"end":55,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":34}},
"await": true,
"left": {
"type": "ArrowFunctionExpression",
"start":34,"end":48,"loc":{"start":{"line":2,"column":13},"end":{"line":2,"column":27}},
"id": null,
"generator": false,
"async": true,
"params": [
{
"type": "Identifier",
"start":40,"end":42,"loc":{"start":{"line":2,"column":19},"end":{"line":2,"column":21},"identifierName":"of"},
"name": "of"
}
],
"body": {
"type": "BlockStatement",
"start":46,"end":48,"loc":{"start":{"line":2,"column":25},"end":{"line":2,"column":27}},
"body": [],
"directives": []
}
},
"right": {
"type": "Identifier",
"start":52,"end":53,"loc":{"start":{"line":2,"column":31},"end":{"line":2,"column":32},"identifierName":"x"},
"name": "x"
},
"body": {
"type": "EmptyStatement",
"start":54,"end":55,"loc":{"start":{"line":2,"column":33},"end":{"line":2,"column":34}}
}
}
],
"directives": []
}
}
],
"directives": []
}
}
@@ -0,0 +1,3 @@
async function f() {
for await (async of x);
}
@@ -0,0 +1,51 @@
{
"type": "File",
"start":0,"end":48,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"program": {
"type": "Program",
"start":0,"end":48,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "FunctionDeclaration",
"start":0,"end":48,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"id": {
"type": "Identifier",
"start":15,"end":16,"loc":{"start":{"line":1,"column":15},"end":{"line":1,"column":16},"identifierName":"f"},
"name": "f"
},
"generator": false,
"async": true,
"params": [],
"body": {
"type": "BlockStatement",
"start":19,"end":48,"loc":{"start":{"line":1,"column":19},"end":{"line":3,"column":1}},
"body": [
{
"type": "ForOfStatement",
"start":23,"end":46,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":25}},
"await": true,
"left": {
"type": "Identifier",
"start":34,"end":39,"loc":{"start":{"line":2,"column":13},"end":{"line":2,"column":18},"identifierName":"async"},
"name": "async"
},
"right": {
"type": "Identifier",
"start":43,"end":44,"loc":{"start":{"line":2,"column":22},"end":{"line":2,"column":23},"identifierName":"x"},
"name": "x"
},
"body": {
"type": "EmptyStatement",
"start":45,"end":46,"loc":{"start":{"line":2,"column":24},"end":{"line":2,"column":25}}
}
}
],
"directives": []
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
for (async.x of y);
@@ -0,0 +1,42 @@
{
"type": "File",
"start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}},
"program": {
"type": "Program",
"start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ForOfStatement",
"start":0,"end":19,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}},
"await": false,
"left": {
"type": "MemberExpression",
"start":5,"end":12,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":12}},
"object": {
"type": "Identifier",
"start":5,"end":10,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":10},"identifierName":"async"},
"name": "async"
},
"computed": false,
"property": {
"type": "Identifier",
"start":11,"end":12,"loc":{"start":{"line":1,"column":11},"end":{"line":1,"column":12},"identifierName":"x"},
"name": "x"
}
},
"right": {
"type": "Identifier",
"start":16,"end":17,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":17},"identifierName":"y"},
"name": "y"
},
"body": {
"type": "EmptyStatement",
"start":18,"end":19,"loc":{"start":{"line":1,"column":18},"end":{"line":1,"column":19}}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
for (\u0061sync of x);
@@ -0,0 +1,32 @@
{
"type": "File",
"start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}},
"program": {
"type": "Program",
"start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ForOfStatement",
"start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}},
"await": false,
"left": {
"type": "Identifier",
"start":5,"end":15,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":15},"identifierName":"async"},
"name": "async"
},
"right": {
"type": "Identifier",
"start":19,"end":20,"loc":{"start":{"line":1,"column":19},"end":{"line":1,"column":20},"identifierName":"x"},
"name": "x"
},
"body": {
"type": "EmptyStatement",
"start":21,"end":22,"loc":{"start":{"line":1,"column":21},"end":{"line":1,"column":22}}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
for (async of => {};;);