diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 5fd5c402efc9..7eae9fff270f 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -490,11 +490,7 @@ export default class ExpressionParser extends LValParser { // Parse unary operators, both prefix and postfix. parseMaybeUnary(refShorthandDefaultPos: ?Pos): N.Expression { - if ( - this.isContextual("await") && - (this.scope.inAsync || - (!this.scope.inFunction && this.options.allowAwaitOutsideFunction)) - ) { + if (this.isContextual("await") && this.isAwaitAllowed()) { return this.parseAwait(); } else if (this.state.type.prefix) { const node = this.startNode(); @@ -676,8 +672,8 @@ export default class ExpressionParser extends LValParser { const oldYieldPos = this.state.yieldPos; const oldAwaitPos = this.state.awaitPos; this.state.maybeInArrowParameters = true; - this.state.yieldPos = 0; - this.state.awaitPos = 0; + this.state.yieldPos = -1; + this.state.awaitPos = -1; this.next(); @@ -716,8 +712,34 @@ export default class ExpressionParser extends LValParser { // We keep the old value if it isn't null, for cases like // (x = async(yield)) => {} - this.state.yieldPos = oldYieldPos || this.state.yieldPos; - this.state.awaitPos = oldAwaitPos || this.state.awaitPos; + // + // Hi developer of the future :) If you are implementing generator + // arrow functions, please read the note below about "await" and + // verify if the same logic is needed for yield. + if (oldYieldPos !== -1) this.state.yieldPos = oldYieldPos; + + // Await is trickier than yield. When parsing a possible arrow function + // (e.g. something starting with `async(`) we don't know if its possible + // parameters will actually be inside an async arrow function or if it is + // a normal call expression. + // If it ended up being a call expression, if we are in a context where + // await expression are disallowed (and thus "await" is an identifier) + // we must be careful not to leak this.state.awaitPos to an even outer + // context, where "await" could not be an identifier. + // For example, this code is valid because "await" isn't directly inside + // an async function: + // + // async function a() { + // function b(param = async (await)) { + // } + // } + // + if ( + (!this.isAwaitAllowed() && !oldMaybeInArrowParameters) || + oldAwaitPos !== -1 + ) { + this.state.awaitPos = oldAwaitPos; + } } this.state.maybeInArrowParameters = oldMaybeInArrowParameters; @@ -1231,8 +1253,8 @@ export default class ExpressionParser extends LValParser { const oldAwaitPos = this.state.awaitPos; const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody; this.state.maybeInArrowParameters = true; - this.state.yieldPos = 0; - this.state.awaitPos = 0; + this.state.yieldPos = -1; + this.state.awaitPos = -1; this.state.inFSharpPipelineDirectBody = false; const innerStartPos = this.state.start; @@ -1310,8 +1332,8 @@ export default class ExpressionParser extends LValParser { // We keep the old value if it isn't null, for cases like // (x = (yield)) => {} - this.state.yieldPos = oldYieldPos || this.state.yieldPos; - this.state.awaitPos = oldAwaitPos || this.state.awaitPos; + if (oldYieldPos !== -1) this.state.yieldPos = oldYieldPos; + if (oldAwaitPos !== -1) this.state.awaitPos = oldAwaitPos; if (!exprList.length) { this.unexpected(this.state.lastTokStart); @@ -1804,8 +1826,8 @@ export default class ExpressionParser extends LValParser { ): T { const oldYieldPos = this.state.yieldPos; const oldAwaitPos = this.state.awaitPos; - this.state.yieldPos = 0; - this.state.awaitPos = 0; + this.state.yieldPos = -1; + this.state.awaitPos = -1; this.initFunction(node, isAsync); node.generator = !!isGenerator; @@ -1842,8 +1864,8 @@ export default class ExpressionParser extends LValParser { const oldYieldPos = this.state.yieldPos; const oldAwaitPos = this.state.awaitPos; this.state.maybeInArrowParameters = false; - this.state.yieldPos = 0; - this.state.awaitPos = 0; + this.state.yieldPos = -1; + this.state.awaitPos = -1; if (params) this.setArrowFunctionParameters(node, params); this.parseFunctionBody(node, true); @@ -2117,11 +2139,18 @@ export default class ExpressionParser extends LValParser { ); } - if (this.scope.inAsync && word === "await") { - this.raise( - startLoc, - "Can not use 'await' as identifier inside an async function", - ); + if (word === "await") { + if (this.scope.inAsync) { + this.raise( + startLoc, + "Can not use 'await' as identifier inside an async function", + ); + } else if ( + this.state.awaitPos === -1 && + (this.state.maybeInArrowParameters || this.isAwaitAllowed()) + ) { + this.state.awaitPos = this.state.start; + } } if (this.state.inClassProperty && word === "arguments") { @@ -2151,10 +2180,16 @@ export default class ExpressionParser extends LValParser { } } + isAwaitAllowed(): boolean { + if (this.scope.inFunction) return this.scope.inAsync; + if (this.options.allowAwaitOutsideFunction) return true; + return false; + } + // Parses await expression inside async function. parseAwait(): N.AwaitExpression { - if (!this.state.awaitPos) { + if (this.state.awaitPos === -1) { this.state.awaitPos = this.state.start; } const node = this.startNode(); @@ -2183,7 +2218,7 @@ export default class ExpressionParser extends LValParser { // Parses yield expression inside generator. parseYield(noIn?: ?boolean): N.YieldExpression { - if (!this.state.yieldPos) { + if (this.state.yieldPos === -1) { this.state.yieldPos = this.state.start; } const node = this.startNode(); diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index bb5702e7de34..bfadb601dbcf 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -1050,12 +1050,14 @@ export default class StatementParser extends ExpressionParser { node.id = this.parseFunctionId(requireId); } + const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; const oldInClassProperty = this.state.inClassProperty; const oldYieldPos = this.state.yieldPos; const oldAwaitPos = this.state.awaitPos; + this.state.maybeInArrowParameters = false; this.state.inClassProperty = false; - this.state.yieldPos = 0; - this.state.awaitPos = 0; + this.state.yieldPos = -1; + this.state.awaitPos = -1; this.scope.enter(functionFlags(node.async, node.generator)); if (!isStatement) { @@ -1084,6 +1086,7 @@ export default class StatementParser extends ExpressionParser { this.checkFunctionStatementId(node); } + this.state.maybeInArrowParameters = oldMaybeInArrowParameters; this.state.inClassProperty = oldInClassProperty; this.state.yieldPos = oldYieldPos; this.state.awaitPos = oldAwaitPos; diff --git a/packages/babel-parser/src/parser/util.js b/packages/babel-parser/src/parser/util.js index bd6139a2480a..41e19c1a3c2e 100644 --- a/packages/babel-parser/src/parser/util.js +++ b/packages/babel-parser/src/parser/util.js @@ -159,15 +159,15 @@ export default class UtilParser extends Tokenizer { checkYieldAwaitInDefaultParams() { if ( - this.state.yieldPos && - (!this.state.awaitPos || this.state.yieldPos < this.state.awaitPos) + this.state.yieldPos !== -1 && + (this.state.awaitPos === -1 || this.state.yieldPos < this.state.awaitPos) ) { this.raise( this.state.yieldPos, "Yield cannot be used as name inside a generator function", ); } - if (this.state.awaitPos) { + if (this.state.awaitPos !== -1) { this.raise( this.state.awaitPos, "Await cannot be used as name inside an async function", diff --git a/packages/babel-parser/src/tokenizer/state.js b/packages/babel-parser/src/tokenizer/state.js index 80842fbc5103..a9dccb2bf0e6 100644 --- a/packages/babel-parser/src/tokenizer/state.js +++ b/packages/babel-parser/src/tokenizer/state.js @@ -97,8 +97,8 @@ export default class State { decoratorStack: Array> = [[]]; // Positions to delayed-check that yield/await does not exist in default parameters. - yieldPos: number = 0; - awaitPos: number = 0; + yieldPos: number = -1; + awaitPos: number = -1; // Token store. tokens: Array = []; diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/await-binding-inside-arrow-params-inside-async-arrow-params/input.js b/packages/babel-parser/test/fixtures/es2017/async-functions/await-binding-inside-arrow-params-inside-async-arrow-params/input.js new file mode 100644 index 000000000000..5b6e6b2866ae --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/await-binding-inside-arrow-params-inside-async-arrow-params/input.js @@ -0,0 +1 @@ +async (a = ({ await }) => {}) => {}; diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/await-binding-inside-arrow-params-inside-async-arrow-params/options.json b/packages/babel-parser/test/fixtures/es2017/async-functions/await-binding-inside-arrow-params-inside-async-arrow-params/options.json new file mode 100644 index 000000000000..c066100759e2 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/await-binding-inside-arrow-params-inside-async-arrow-params/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Await cannot be used as name inside an async function (1:20)" +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/await-inside-async-call-inside-parameters-of-function-inside-async-function/input.js b/packages/babel-parser/test/fixtures/es2017/async-functions/await-inside-async-call-inside-parameters-of-function-inside-async-function/input.js new file mode 100644 index 000000000000..1c99cb921526 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/await-inside-async-call-inside-parameters-of-function-inside-async-function/input.js @@ -0,0 +1,3 @@ +async function f() { + function g(x = async(await)) {} +} diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/await-inside-async-call-inside-parameters-of-function-inside-async-function/output.json b/packages/babel-parser/test/fixtures/es2017/async-functions/await-inside-async-call-inside-parameters-of-function-inside-async-function/output.json new file mode 100644 index 000000000000..41946b0a2bcb --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/await-inside-async-call-inside-parameters-of-function-inside-async-function/output.json @@ -0,0 +1,224 @@ +{ + "type": "File", + "start": 0, + "end": 56, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 1 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 56, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 1 + } + }, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "FunctionDeclaration", + "start": 0, + "end": 56, + "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": 56, + "loc": { + "start": { + "line": 1, + "column": 19 + }, + "end": { + "line": 3, + "column": 1 + } + }, + "body": [ + { + "type": "FunctionDeclaration", + "start": 23, + "end": 54, + "loc": { + "start": { + "line": 2, + "column": 2 + }, + "end": { + "line": 2, + "column": 33 + } + }, + "id": { + "type": "Identifier", + "start": 32, + "end": 33, + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 12 + }, + "identifierName": "g" + }, + "name": "g" + }, + "generator": false, + "async": false, + "params": [ + { + "type": "AssignmentPattern", + "start": 34, + "end": 50, + "loc": { + "start": { + "line": 2, + "column": 13 + }, + "end": { + "line": 2, + "column": 29 + } + }, + "left": { + "type": "Identifier", + "start": 34, + "end": 35, + "loc": { + "start": { + "line": 2, + "column": 13 + }, + "end": { + "line": 2, + "column": 14 + }, + "identifierName": "x" + }, + "name": "x" + }, + "right": { + "type": "CallExpression", + "start": 38, + "end": 50, + "loc": { + "start": { + "line": 2, + "column": 17 + }, + "end": { + "line": 2, + "column": 29 + } + }, + "callee": { + "type": "Identifier", + "start": 38, + "end": 43, + "loc": { + "start": { + "line": 2, + "column": 17 + }, + "end": { + "line": 2, + "column": 22 + }, + "identifierName": "async" + }, + "name": "async" + }, + "arguments": [ + { + "type": "Identifier", + "start": 44, + "end": 49, + "loc": { + "start": { + "line": 2, + "column": 23 + }, + "end": { + "line": 2, + "column": 28 + }, + "identifierName": "await" + }, + "name": "await" + } + ] + } + } + ], + "body": { + "type": "BlockStatement", + "start": 52, + "end": 54, + "loc": { + "start": { + "line": 2, + "column": 31 + }, + "end": { + "line": 2, + "column": 33 + } + }, + "body": [], + "directives": [] + } + } + ], + "directives": [] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/await-object-key-inside-arrow-params-inside-async-arrow-params copy/input.js b/packages/babel-parser/test/fixtures/es2017/async-functions/await-object-key-inside-arrow-params-inside-async-arrow-params copy/input.js new file mode 100644 index 000000000000..72058aa6dd6b --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/await-object-key-inside-arrow-params-inside-async-arrow-params copy/input.js @@ -0,0 +1 @@ +async (a = ({ await: x }) => {}) => {}; diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/await-object-key-inside-arrow-params-inside-async-arrow-params copy/output.json b/packages/babel-parser/test/fixtures/es2017/async-functions/await-object-key-inside-arrow-params-inside-async-arrow-params copy/output.json new file mode 100644 index 000000000000..0d5a5b2fa589 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/await-object-key-inside-arrow-params-inside-async-arrow-params copy/output.json @@ -0,0 +1,225 @@ +{ + "type": "File", + "start": 0, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 39 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 39 + } + }, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 39, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 39 + } + }, + "expression": { + "type": "ArrowFunctionExpression", + "start": 0, + "end": 38, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 38 + } + }, + "id": null, + "generator": false, + "async": true, + "params": [ + { + "type": "AssignmentPattern", + "start": 7, + "end": 31, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 31 + } + }, + "left": { + "type": "Identifier", + "start": 7, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 8 + }, + "identifierName": "a" + }, + "name": "a" + }, + "right": { + "type": "ArrowFunctionExpression", + "start": 11, + "end": 31, + "loc": { + "start": { + "line": 1, + "column": 11 + }, + "end": { + "line": 1, + "column": 31 + } + }, + "id": null, + "generator": false, + "async": false, + "params": [ + { + "type": "ObjectPattern", + "start": 12, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 12 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "properties": [ + { + "type": "ObjectProperty", + "start": 14, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 14 + }, + "end": { + "line": 1, + "column": 22 + } + }, + "method": false, + "key": { + "type": "Identifier", + "start": 14, + "end": 19, + "loc": { + "start": { + "line": 1, + "column": 14 + }, + "end": { + "line": 1, + "column": 19 + }, + "identifierName": "await" + }, + "name": "await" + }, + "computed": false, + "shorthand": false, + "value": { + "type": "Identifier", + "start": 21, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 21 + }, + "end": { + "line": 1, + "column": 22 + }, + "identifierName": "x" + }, + "name": "x" + } + } + ] + } + ], + "body": { + "type": "BlockStatement", + "start": 29, + "end": 31, + "loc": { + "start": { + "line": 1, + "column": 29 + }, + "end": { + "line": 1, + "column": 31 + } + }, + "body": [], + "directives": [] + } + } + } + ], + "body": { + "type": "BlockStatement", + "start": 36, + "end": 38, + "loc": { + "start": { + "line": 1, + "column": 36 + }, + "end": { + "line": 1, + "column": 38 + } + }, + "body": [], + "directives": [] + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/scripts/tests/test262/test262_whitelist.txt b/scripts/tests/test262/test262_whitelist.txt index 01c45f11a59d..a339397deb19 100644 --- a/scripts/tests/test262/test262_whitelist.txt +++ b/scripts/tests/test262/test262_whitelist.txt @@ -1,15 +1,5 @@ language/expressions/assignment/destructuring/obj-prop-__proto__dup.js(default) language/expressions/assignment/destructuring/obj-prop-__proto__dup.js(strict mode) -language/expressions/async-arrow-function/await-as-param-ident-nested-arrow-parameter-position.js(default) -language/expressions/async-arrow-function/await-as-param-ident-nested-arrow-parameter-position.js(strict mode) -language/expressions/async-arrow-function/await-as-param-nested-arrow-parameter-position.js(default) -language/expressions/async-arrow-function/await-as-param-nested-arrow-parameter-position.js(strict mode) -language/expressions/async-arrow-function/await-as-param-rest-nested-arrow-parameter-position.js(default) -language/expressions/async-arrow-function/await-as-param-rest-nested-arrow-parameter-position.js(strict mode) -language/expressions/async-arrow-function/early-errors-arrow-await-in-formals-default.js(default) -language/expressions/async-arrow-function/early-errors-arrow-await-in-formals-default.js(strict mode) -language/expressions/async-arrow-function/early-errors-arrow-await-in-formals.js(default) -language/expressions/async-arrow-function/early-errors-arrow-await-in-formals.js(strict mode) language/expressions/class/elements/fields-duplicate-privatenames.js(default) language/expressions/class/elements/fields-duplicate-privatenames.js(strict mode) language/expressions/class/elements/private-methods/prod-private-method-initialize-order.js(default)