Skip to content

Commit

Permalink
Raise recoverable error for await expressions in sync functions (#12520)
Browse files Browse the repository at this point in the history
  • Loading branch information
sosukesuzuki committed Jan 24, 2021
1 parent d98418e commit 8fcba6e
Show file tree
Hide file tree
Showing 24 changed files with 726 additions and 59 deletions.
72 changes: 49 additions & 23 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -492,9 +492,15 @@ export default class ExpressionParser extends LValParser {
// Parse unary operators, both prefix and postfix.
// https://tc39.es/ecma262/#prod-UnaryExpression
parseMaybeUnary(refExpressionErrors: ?ExpressionErrors): N.Expression {
if (this.isContextual("await") && this.isAwaitAllowed()) {
return this.parseAwait();
const startPos = this.state.start;
const startLoc = this.state.startLoc;
const isAwait = this.isContextual("await");

if (isAwait && this.isAwaitAllowed()) {
this.next();
return this.parseAwait(startPos, startLoc);
}

const update = this.match(tt.incDec);
const node = this.startNode();
if (this.state.type.prefix) {
Expand Down Expand Up @@ -526,7 +532,24 @@ export default class ExpressionParser extends LValParser {
}
}

return this.parseUpdate(node, update, refExpressionErrors);
const expr = this.parseUpdate(node, update, refExpressionErrors);

const startsExpr = this.hasPlugin("v8intrinsic")
? this.state.type.startsExpr
: this.state.type.startsExpr && !this.match(tt.modulo);
if (isAwait && startsExpr && !this.isAmbiguousAwait()) {
if (!this.state.invalidAwaitErrors.has(startPos)) {
this.raise(
startPos,
this.hasPlugin("topLevelAwait")
? Errors.AwaitNotInAsyncContext
: Errors.AwaitNotInAsyncFunction,
);
}
return this.parseAwait(startPos, startLoc);
}

return expr;
}

// https://tc39.es/ecma262/#prod-UpdateExpression
Expand Down Expand Up @@ -2332,6 +2355,7 @@ export default class ExpressionParser extends LValParser {
? Errors.AwaitNotInAsyncContext
: Errors.AwaitNotInAsyncFunction,
);
this.state.invalidAwaitErrors.add(startLoc);
} else {
this.raise(startLoc, Errors.UnexpectedReservedWord, word);
}
Expand All @@ -2348,10 +2372,8 @@ export default class ExpressionParser extends LValParser {

// Parses await expression inside async function.

parseAwait(): N.AwaitExpression {
const node = this.startNode();

this.next();
parseAwait(startPos: number, startLoc: Position): N.AwaitExpression {
const node = this.startNodeAt(startPos, startLoc);

this.expressionScope.recordParameterInitializerError(
node.start,
Expand All @@ -2363,22 +2385,7 @@ export default class ExpressionParser extends LValParser {
}

if (!this.scope.inFunction && !this.options.allowAwaitOutsideFunction) {
if (
this.hasPrecedingLineBreak() ||
// All the following expressions are ambiguous:
// await + 0, await - 0, await ( 0 ), await [ 0 ], await / 0 /u, await ``
this.match(tt.plusMin) ||
this.match(tt.parenL) ||
this.match(tt.bracketL) ||
this.match(tt.backQuote) ||
// Sometimes the tokenizer generates tt.slash for regexps, and this is
// handler by parseExprAtom
this.match(tt.regexp) ||
this.match(tt.slash) ||
// This code could be parsed both as a modulo operator or as an intrinsic:
// await %x(0)
(this.hasPlugin("v8intrinsic") && this.match(tt.modulo))
) {
if (this.isAmbiguousAwait()) {
this.ambiguousScriptDifferentAst = true;
} else {
this.sawUnambiguousESM = true;
Expand All @@ -2392,6 +2399,25 @@ export default class ExpressionParser extends LValParser {
return this.finishNode(node, "AwaitExpression");
}

isAmbiguousAwait(): boolean {
return (
this.hasPrecedingLineBreak() ||
// All the following expressions are ambiguous:
// await + 0, await - 0, await ( 0 ), await [ 0 ], await / 0 /u, await ``
this.match(tt.plusMin) ||
this.match(tt.parenL) ||
this.match(tt.bracketL) ||
this.match(tt.backQuote) ||
// Sometimes the tokenizer generates tt.slash for regexps, and this is
// handler by parseExprAtom
this.match(tt.regexp) ||
this.match(tt.slash) ||
// This code could be parsed both as a modulo operator or as an intrinsic:
// await %x(0)
(this.hasPlugin("v8intrinsic") && this.match(tt.modulo))
);
}

// Parses yield expression inside generator.

parseYield(): N.YieldExpression {
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-parser/src/tokenizer/state.js
Expand Up @@ -154,6 +154,9 @@ export default class State {
// Tokens length in token store
tokensLength: number = 0;

// Positions of invalid await errors
invalidAwaitErrors: Set<number> = new Set();

curPosition(): Position {
return new Position(this.curLine, this.pos - this.lineStart);
}
Expand Down

This file was deleted.

@@ -0,0 +1,54 @@
{
"type": "File",
"start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":40}},
"errors": [
"SyntaxError: 'await' is only allowed within async functions (1:24)"
],
"program": {
"type": "Program",
"start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":40}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "FunctionDeclaration",
"start":0,"end":40,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":40}},
"id": {
"type": "Identifier",
"start":9,"end":12,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":12},"identifierName":"foo"},
"name": "foo"
},
"generator": false,
"async": false,
"params": [
{
"type": "Identifier",
"start":13,"end":20,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":20},"identifierName":"promise"},
"name": "promise"
}
],
"body": {
"type": "BlockStatement",
"start":22,"end":40,"loc":{"start":{"line":1,"column":22},"end":{"line":1,"column":40}},
"body": [
{
"type": "ExpressionStatement",
"start":24,"end":38,"loc":{"start":{"line":1,"column":24},"end":{"line":1,"column":38}},
"expression": {
"type": "AwaitExpression",
"start":24,"end":37,"loc":{"start":{"line":1,"column":24},"end":{"line":1,"column":37}},
"argument": {
"type": "Identifier",
"start":30,"end":37,"loc":{"start":{"line":1,"column":30},"end":{"line":1,"column":37},"identifierName":"promise"},
"name": "promise"
}
}
}
],
"directives": []
}
}
],
"directives": []
}
}

This file was deleted.

@@ -0,0 +1,52 @@
{
"type": "File",
"start":0,"end":33,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"errors": [
"SyntaxError: 'await' is only allowed within async functions (2:9)"
],
"program": {
"type": "Program",
"start":0,"end":33,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "FunctionDeclaration",
"start":0,"end":33,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"id": {
"type": "Identifier",
"start":9,"end":10,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":10},"identifierName":"a"},
"name": "a"
},
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start":13,"end":33,"loc":{"start":{"line":1,"column":13},"end":{"line":3,"column":1}},
"body": [
{
"type": "ReturnStatement",
"start":17,"end":31,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":16}},
"argument": {
"type": "AwaitExpression",
"start":24,"end":31,"loc":{"start":{"line":2,"column":9},"end":{"line":2,"column":16}},
"argument": {
"type": "NumericLiteral",
"start":30,"end":31,"loc":{"start":{"line":2,"column":15},"end":{"line":2,"column":16}},
"extra": {
"rawValue": 1,
"raw": "1"
},
"value": 1
}
}
}
],
"directives": []
}
}
],
"directives": []
}
}
@@ -0,0 +1,4 @@
function foo() {
await
foo;
}
@@ -0,0 +1,50 @@
{
"type": "File",
"start":0,"end":33,"loc":{"start":{"line":1,"column":0},"end":{"line":4,"column":1}},
"program": {
"type": "Program",
"start":0,"end":33,"loc":{"start":{"line":1,"column":0},"end":{"line":4,"column":1}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "FunctionDeclaration",
"start":0,"end":33,"loc":{"start":{"line":1,"column":0},"end":{"line":4,"column":1}},
"id": {
"type": "Identifier",
"start":9,"end":12,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":12},"identifierName":"foo"},
"name": "foo"
},
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start":15,"end":33,"loc":{"start":{"line":1,"column":15},"end":{"line":4,"column":1}},
"body": [
{
"type": "ExpressionStatement",
"start":19,"end":24,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":7}},
"expression": {
"type": "Identifier",
"start":19,"end":24,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":7},"identifierName":"await"},
"name": "await"
}
},
{
"type": "ExpressionStatement",
"start":27,"end":31,"loc":{"start":{"line":3,"column":2},"end":{"line":3,"column":6}},
"expression": {
"type": "Identifier",
"start":27,"end":30,"loc":{"start":{"line":3,"column":2},"end":{"line":3,"column":5},"identifierName":"foo"},
"name": "foo"
}
}
],
"directives": []
}
}
],
"directives": []
}
}

This file was deleted.

@@ -0,0 +1,48 @@
{
"type": "File",
"start":0,"end":17,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":17}},
"errors": [
"SyntaxError: 'await' is only allowed within async functions (1:8)"
],
"program": {
"type": "Program",
"start":0,"end":17,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":17}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":17,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":17}},
"expression": {
"type": "ArrowFunctionExpression",
"start":0,"end":17,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":17}},
"id": null,
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start":6,"end":17,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":17}},
"body": [
{
"type": "ExpressionStatement",
"start":8,"end":15,"loc":{"start":{"line":1,"column":8},"end":{"line":1,"column":15}},
"expression": {
"type": "AwaitExpression",
"start":8,"end":15,"loc":{"start":{"line":1,"column":8},"end":{"line":1,"column":15}},
"argument": {
"type": "Identifier",
"start":14,"end":15,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":15},"identifierName":"x"},
"name": "x"
}
}
}
],
"directives": []
}
}
}
],
"directives": []
}
}

This file was deleted.

0 comments on commit 8fcba6e

Please sign in to comment.