From 44ed57945a5e32ed1182c1340782c5620f49abc3 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Fri, 23 Apr 2021 09:35:43 +0900 Subject: [PATCH] fix `await` and `yield` in class field initializer --- acorn-loose/src/expression.js | 11 +++-- acorn-loose/src/state.js | 1 + acorn-loose/src/statement.js | 15 ++++++- acorn/src/state.js | 4 +- test/tests-class-features-2022.js | 73 +++++++++++++++++++++++++++++++ test/tests-harmony.js | 3 -- 6 files changed, 97 insertions(+), 10 deletions(-) diff --git a/acorn-loose/src/expression.js b/acorn-loose/src/expression.js index ddad8378a..b7903c474 100644 --- a/acorn-loose/src/expression.js +++ b/acorn-loose/src/expression.js @@ -42,7 +42,8 @@ lp.parseParenExpression = function() { } lp.parseMaybeAssign = function(noIn) { - if (this.toks.isContextual("yield")) { + // `yield` should be an identifier reference if it's not in generator functions. + if (this.inGenerator && this.toks.isContextual("yield")) { let node = this.startNode() this.next() if (this.semicolon() || this.canInsertSemicolon() || (this.tok.type !== tt.star && !this.tok.type.startsExpr)) { @@ -580,28 +581,31 @@ lp.parseFunctionParams = function(params) { } lp.parseMethod = function(isGenerator, isAsync) { - let node = this.startNode(), oldInAsync = this.inAsync, oldInFunction = this.inFunction + let node = this.startNode(), oldInAsync = this.inAsync, oldInGenerator = this.inGenerator, oldInFunction = this.inFunction this.initFunction(node) if (this.options.ecmaVersion >= 6) node.generator = !!isGenerator if (this.options.ecmaVersion >= 8) node.async = !!isAsync this.inAsync = node.async + this.inGenerator = node.generator this.inFunction = true node.params = this.parseFunctionParams() node.body = this.parseBlock() this.toks.adaptDirectivePrologue(node.body.body) this.inAsync = oldInAsync + this.inGenerator = oldInGenerator this.inFunction = oldInFunction return this.finishNode(node, "FunctionExpression") } lp.parseArrowExpression = function(node, params, isAsync) { - let oldInAsync = this.inAsync, oldInFunction = this.inFunction + let oldInAsync = this.inAsync, oldInGenerator = this.inGenerator, oldInFunction = this.inFunction this.initFunction(node) if (this.options.ecmaVersion >= 8) node.async = !!isAsync this.inAsync = node.async + this.inGenerator = false this.inFunction = true node.params = this.toAssignableList(params, true) node.expression = this.tok.type !== tt.braceL @@ -612,6 +616,7 @@ lp.parseArrowExpression = function(node, params, isAsync) { this.toks.adaptDirectivePrologue(node.body.body) } this.inAsync = oldInAsync + this.inGenerator = oldInGenerator this.inFunction = oldInFunction return this.finishNode(node, "ArrowFunctionExpression") } diff --git a/acorn-loose/src/state.js b/acorn-loose/src/state.js index ea6211c03..519224d8b 100644 --- a/acorn-loose/src/state.js +++ b/acorn-loose/src/state.js @@ -21,6 +21,7 @@ export class LooseParser { this.curLineStart = 0 this.nextLineStart = this.lineEnd(this.curLineStart) + 1 this.inAsync = false + this.inGenerator = false this.inFunction = false } diff --git a/acorn-loose/src/statement.js b/acorn-loose/src/statement.js index 9229cef20..d55ce0477 100644 --- a/acorn-loose/src/statement.js +++ b/acorn-loose/src/statement.js @@ -323,7 +323,16 @@ lp.parseClass = function(isStatement) { element.value = this.parseMethod(isGenerator, isAsync) this.finishNode(element, "MethodDefinition") } else { - element.value = this.eat(tt.eq) ? this.parseMaybeAssign() : null + if (this.eat(tt.eq)) { + const oldInAsync = this.inAsync, oldInGenerator = this.inGenerator + this.inAsync = false + this.inGenerator = false + element.value = this.parseMaybeAssign() + this.inAsync = oldInAsync + this.inGenerator = oldInGenerator + } else { + element.value = null + } this.semicolon() this.finishNode(element, "PropertyDefinition") } @@ -351,7 +360,7 @@ lp.parseClassElementName = function(element) { } lp.parseFunction = function(node, isStatement, isAsync) { - let oldInAsync = this.inAsync, oldInFunction = this.inFunction + let oldInAsync = this.inAsync, oldInGenerator = this.inGenerator, oldInFunction = this.inFunction this.initFunction(node) if (this.options.ecmaVersion >= 6) { node.generator = this.eat(tt.star) @@ -362,11 +371,13 @@ lp.parseFunction = function(node, isStatement, isAsync) { if (this.tok.type === tt.name) node.id = this.parseIdent() else if (isStatement === true) node.id = this.dummyIdent() this.inAsync = node.async + this.inGenerator = node.generator this.inFunction = true node.params = this.parseFunctionParams() node.body = this.parseBlock() this.toks.adaptDirectivePrologue(node.body.body) this.inAsync = oldInAsync + this.inGenerator = oldInGenerator this.inFunction = oldInFunction return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression") } diff --git a/acorn/src/state.js b/acorn/src/state.js index 756b5b17a..c5555d551 100644 --- a/acorn/src/state.js +++ b/acorn/src/state.js @@ -97,8 +97,8 @@ export class Parser { } get inFunction() { return (this.currentVarScope().flags & SCOPE_FUNCTION) > 0 } - get inGenerator() { return (this.currentVarScope().flags & SCOPE_GENERATOR) > 0 } - get inAsync() { return (this.currentVarScope().flags & SCOPE_ASYNC) > 0 } + get inGenerator() { return (this.currentVarScope().flags & SCOPE_GENERATOR) > 0 && !this.currentThisScope().inClassFieldInit } + get inAsync() { return (this.currentVarScope().flags & SCOPE_ASYNC) > 0 && !this.currentThisScope().inClassFieldInit } get allowSuper() { const {flags, inClassFieldInit} = this.currentThisScope() return (flags & SCOPE_SUPER) > 0 || inClassFieldInit diff --git a/test/tests-class-features-2022.js b/test/tests-class-features-2022.js index 41e41e8dc..5d25a743b 100644 --- a/test/tests-class-features-2022.js +++ b/test/tests-class-features-2022.js @@ -1250,6 +1250,79 @@ test("class C { async\n get(){} }", { "sourceType": "script" }, {ecmaVersion: 13, loose: true}) +// `await` is reference +test("async function f() { class C { aaa = await } }", { + "type": "Program", + "start": 0, + "end": 46, + "body": [ + { + "type": "FunctionDeclaration", + "start": 0, + "end": 46, + "id": { + "type": "Identifier", + "start": 15, + "end": 16, + "name": "f" + }, + "params": [], + "generator": false, + "expression": false, + "async": true, + "body": { + "type": "BlockStatement", + "start": 19, + "end": 46, + "body": [ + { + "type": "ClassDeclaration", + "start": 21, + "end": 44, + "id": { + "type": "Identifier", + "start": 27, + "end": 28, + "name": "C" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 29, + "end": 44, + "body": [ + { + "type": "PropertyDefinition", + "start": 31, + "end": 42, + "static": false, + "computed": false, + "key": { + "type": "Identifier", + "start": 31, + "end": 34, + "name": "aaa" + }, + "value": { + "type": "Identifier", + "start": 37, + "end": 42, + "name": "await" + } + } + ] + } + } + ] + } + } + ], + "sourceType": "script" +}, {ecmaVersion: 13}) + +// `yield` is keyword in strict mode +testFail("function* f() { class C { aaa = yield } }", "The keyword 'yield' is reserved (1:32)", {ecmaVersion: 13}) + // old ecma version testFail("class C { aaa }", "Unexpected token (1:14)", {ecmaVersion: 12}) diff --git a/test/tests-harmony.js b/test/tests-harmony.js index 6425ac5a5..0b17a795e 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -13217,7 +13217,6 @@ test("yield* 10", { } }, { ecmaVersion: 6, - loose: false, ranges: true, locations: true }); @@ -13280,7 +13279,6 @@ test("e => yield* 10", { } }, { ecmaVersion: 6, - loose: false, ranges: true, locations: true }); @@ -13352,7 +13350,6 @@ test("(function () { yield* 10 })", { } }, { ecmaVersion: 6, - loose: false, ranges: true, locations: true });