Skip to content

Commit

Permalink
Parse for await (async of ...) (#13244)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zalathar committed May 1, 2021
1 parent 2a3e35f commit fa01fbe
Show file tree
Hide file tree
Showing 31 changed files with 554 additions and 11 deletions.
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 => {};;);

0 comments on commit fa01fbe

Please sign in to comment.