From 11a0981d430871e0dd37c2ea03bf7744371b9f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 1 Jan 2020 20:46:56 -0500 Subject: [PATCH 1/6] test: add test fixtures --- .../input.js | 6 + .../options.json | 3 + .../output.json | 264 ++++++++++++++++++ .../input.js | 3 + .../options.json | 3 + .../output.json | 224 +++++++++++++++ 6 files changed, 503 insertions(+) create mode 100644 packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-class-property-in-generator/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-class-property-in-generator/options.json create mode 100644 packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-class-property-in-generator/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-generator-in-class-property/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-generator-in-class-property/options.json create mode 100644 packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-generator-in-class-property/output.json diff --git a/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-class-property-in-generator/input.js b/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-class-property-in-generator/input.js new file mode 100644 index 000000000000..25ecb3345bc9 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-class-property-in-generator/input.js @@ -0,0 +1,6 @@ +function* foo() { + class C { + // here yield is an identifier reference + p = yield + 42; + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-class-property-in-generator/options.json b/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-class-property-in-generator/options.json new file mode 100644 index 000000000000..9c27576d4ad0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-class-property-in-generator/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["classProperties"] +} diff --git a/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-class-property-in-generator/output.json b/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-class-property-in-generator/output.json new file mode 100644 index 000000000000..8f99924c16ba --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-class-property-in-generator/output.json @@ -0,0 +1,264 @@ +{ + "type": "File", + "start": 0, + "end": 100, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 6, + "column": 1 + } + }, + "errors": [ + "SyntaxError: Unexpected reserved word 'yield' (4:8)" + ], + "program": { + "type": "Program", + "start": 0, + "end": 100, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 6, + "column": 1 + } + }, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "FunctionDeclaration", + "start": 0, + "end": 100, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 6, + "column": 1 + } + }, + "id": { + "type": "Identifier", + "start": 10, + "end": 13, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 13 + }, + "identifierName": "foo" + }, + "name": "foo" + }, + "generator": true, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 16, + "end": 100, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 6, + "column": 1 + } + }, + "body": [ + { + "type": "ClassDeclaration", + "start": 20, + "end": 98, + "loc": { + "start": { + "line": 2, + "column": 2 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "id": { + "type": "Identifier", + "start": 26, + "end": 27, + "loc": { + "start": { + "line": 2, + "column": 8 + }, + "end": { + "line": 2, + "column": 9 + }, + "identifierName": "C" + }, + "name": "C" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 28, + "end": 98, + "loc": { + "start": { + "line": 2, + "column": 10 + }, + "end": { + "line": 5, + "column": 3 + } + }, + "body": [ + { + "type": "ClassProperty", + "start": 79, + "end": 94, + "loc": { + "start": { + "line": 4, + "column": 4 + }, + "end": { + "line": 4, + "column": 19 + } + }, + "static": false, + "key": { + "type": "Identifier", + "start": 79, + "end": 80, + "loc": { + "start": { + "line": 4, + "column": 4 + }, + "end": { + "line": 4, + "column": 5 + }, + "identifierName": "p" + }, + "name": "p" + }, + "computed": false, + "value": { + "type": "BinaryExpression", + "start": 83, + "end": 93, + "loc": { + "start": { + "line": 4, + "column": 8 + }, + "end": { + "line": 4, + "column": 18 + } + }, + "left": { + "type": "Identifier", + "start": 83, + "end": 88, + "loc": { + "start": { + "line": 4, + "column": 8 + }, + "end": { + "line": 4, + "column": 13 + }, + "identifierName": "yield" + }, + "name": "yield" + }, + "operator": "+", + "right": { + "type": "NumericLiteral", + "start": 91, + "end": 93, + "loc": { + "start": { + "line": 4, + "column": 16 + }, + "end": { + "line": 4, + "column": 18 + } + }, + "extra": { + "rawValue": 42, + "raw": "42" + }, + "value": 42 + } + }, + "leadingComments": [ + { + "type": "CommentLine", + "value": " here yield is an identifier reference", + "start": 34, + "end": 74, + "loc": { + "start": { + "line": 3, + "column": 4 + }, + "end": { + "line": 3, + "column": 44 + } + } + } + ] + } + ] + } + } + ], + "directives": [] + } + } + ], + "directives": [] + }, + "comments": [ + { + "type": "CommentLine", + "value": " here yield is an identifier reference", + "start": 34, + "end": 74, + "loc": { + "start": { + "line": 3, + "column": 4 + }, + "end": { + "line": 3, + "column": 44 + } + } + } + ] +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-generator-in-class-property/input.js b/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-generator-in-class-property/input.js new file mode 100644 index 000000000000..6eed441a87a6 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-generator-in-class-property/input.js @@ -0,0 +1,3 @@ +class C { + p = function* () { yield + 42 }; +} diff --git a/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-generator-in-class-property/options.json b/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-generator-in-class-property/options.json new file mode 100644 index 000000000000..9c27576d4ad0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-generator-in-class-property/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["classProperties"] +} diff --git a/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-generator-in-class-property/output.json b/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-generator-in-class-property/output.json new file mode 100644 index 000000000000..d0896adfc448 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-properties/yield-in-generator-in-class-property/output.json @@ -0,0 +1,224 @@ +{ + "type": "File", + "start": 0, + "end": 46, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 1 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 46, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 1 + } + }, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ClassDeclaration", + "start": 0, + "end": 46, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 1 + } + }, + "id": { + "type": "Identifier", + "start": 6, + "end": 7, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 7 + }, + "identifierName": "C" + }, + "name": "C" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start": 8, + "end": 46, + "loc": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 3, + "column": 1 + } + }, + "body": [ + { + "type": "ClassProperty", + "start": 12, + "end": 44, + "loc": { + "start": { + "line": 2, + "column": 2 + }, + "end": { + "line": 2, + "column": 34 + } + }, + "static": false, + "key": { + "type": "Identifier", + "start": 12, + "end": 13, + "loc": { + "start": { + "line": 2, + "column": 2 + }, + "end": { + "line": 2, + "column": 3 + }, + "identifierName": "p" + }, + "name": "p" + }, + "computed": false, + "value": { + "type": "FunctionExpression", + "start": 16, + "end": 43, + "loc": { + "start": { + "line": 2, + "column": 6 + }, + "end": { + "line": 2, + "column": 33 + } + }, + "id": null, + "generator": true, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start": 29, + "end": 43, + "loc": { + "start": { + "line": 2, + "column": 19 + }, + "end": { + "line": 2, + "column": 33 + } + }, + "body": [ + { + "type": "ExpressionStatement", + "start": 31, + "end": 41, + "loc": { + "start": { + "line": 2, + "column": 21 + }, + "end": { + "line": 2, + "column": 31 + } + }, + "expression": { + "type": "YieldExpression", + "start": 31, + "end": 41, + "loc": { + "start": { + "line": 2, + "column": 21 + }, + "end": { + "line": 2, + "column": 31 + } + }, + "delegate": false, + "argument": { + "type": "UnaryExpression", + "start": 37, + "end": 41, + "loc": { + "start": { + "line": 2, + "column": 27 + }, + "end": { + "line": 2, + "column": 31 + } + }, + "operator": "+", + "prefix": true, + "argument": { + "type": "NumericLiteral", + "start": 39, + "end": 41, + "loc": { + "start": { + "line": 2, + "column": 29 + }, + "end": { + "line": 2, + "column": 31 + } + }, + "extra": { + "rawValue": 42, + "raw": "42" + }, + "value": 42 + } + } + } + } + ], + "directives": [] + } + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file From ae0ca34cf915473e0da6ee259e1187499aba0d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 1 Jan 2020 18:38:02 -0500 Subject: [PATCH 2/6] refactor: track AWAIT and YIELD in separate handler --- .../babel-parser/src/parser/expression.js | 57 ++++++++++++------- packages/babel-parser/src/parser/index.js | 14 +++-- packages/babel-parser/src/parser/statement.js | 13 ++++- packages/babel-parser/src/plugins/flow.js | 4 +- .../src/plugins/typescript/index.js | 7 +++ .../babel-parser/src/tokenizer/context.js | 2 +- .../src/util/production-parameter.js | 52 +++++++++++++++++ packages/babel-parser/src/util/scope.js | 21 ------- packages/babel-parser/src/util/scopeflags.js | 12 ---- 9 files changed, 119 insertions(+), 63 deletions(-) create mode 100644 packages/babel-parser/src/util/production-parameter.js diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 2598eefc1d47..d251a6244a3d 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -33,15 +33,19 @@ import * as charCodes from "charcodes"; import { BIND_OUTSIDE, BIND_VAR, - functionFlags, SCOPE_ARROW, SCOPE_CLASS, SCOPE_DIRECT_SUPER, + SCOPE_FUNCTION, SCOPE_SUPER, SCOPE_PROGRAM, - SCOPE_ASYNC, } from "../util/scopeflags"; import { ExpressionErrors } from "./util"; +import { + PARAM_AWAIT, + PARAM_, + functionFlags, +} from "../util/production-parameter"; export default class ExpressionParser extends LValParser { // Forward-declaration: defined in statement.js @@ -106,11 +110,12 @@ export default class ExpressionParser extends LValParser { // Convenience method to parse an Expression only getExpression(): N.Expression { - let scopeFlags = SCOPE_PROGRAM; + let paramFlags = PARAM_; if (this.hasPlugin("topLevelAwait") && this.inModule) { - scopeFlags |= SCOPE_ASYNC; + paramFlags |= PARAM_AWAIT; } - this.scope.enter(scopeFlags); + this.scope.enter(SCOPE_PROGRAM); + this.param.enter(paramFlags); this.nextToken(); const expr = this.parseExpression(); if (!this.match(tt.eof)) { @@ -129,12 +134,18 @@ export default class ExpressionParser extends LValParser { // and, *if* the syntactic construct they handle is present, wrap // the AST node that the inner parser gave them in another node. - // Parse a full expression. The optional arguments are used to - // forbid the `in` operator (in for loops initialization expressions) - // and provide reference for storing '=' operator inside shorthand - // property assignment in contexts where both object expression - // and object pattern might appear (so it's possible to raise - // delayed syntax error at correct position). + // Parse a full expression. + // - `noIn` + // is used to forbid the `in` operator (in for loops initialization expressions) + // When `noIn` is true, the production parameter [In] is not present. + // Whenever [?In] appears in the right-hand sides of a production, we pass + // `noIn` to the subroutine calls. + + // - `refExpressionErrors ` + // provides reference for storing '=' operator inside shorthand + // property assignment in contexts where both object expression + // and object pattern might appear (so it's possible to raise + // delayed syntax error at correct position). parseExpression( noIn?: boolean, @@ -167,7 +178,7 @@ export default class ExpressionParser extends LValParser { const startPos = this.state.start; const startLoc = this.state.startLoc; if (this.isContextual("yield")) { - if (this.scope.inGenerator) { + if (this.param.hasYield) { let left = this.parseYield(noIn); if (afterLeftParse) { left = afterLeftParse.call(this, left, startPos, startLoc); @@ -359,7 +370,7 @@ export default class ExpressionParser extends LValParser { if ( this.match(tt.name) && this.state.value === "await" && - this.scope.inAsync + this.param.hasAwait ) { throw this.raise( this.state.start, @@ -1180,7 +1191,7 @@ export default class ExpressionParser extends LValParser { this.next(); meta = this.createIdentifier(meta, "function"); - if (this.scope.inGenerator && this.eat(tt.dot)) { + if (this.param.hasYield && this.eat(tt.dot)) { return this.parseMetaProperty(node, meta, "sent"); } return this.parseFunction(node); @@ -1852,13 +1863,15 @@ export default class ExpressionParser extends LValParser { node.generator = !!isGenerator; const allowModifiers = isConstructor; // For TypeScript parameter properties this.scope.enter( - functionFlags(isAsync, node.generator) | + SCOPE_FUNCTION | SCOPE_SUPER | (inClassScope ? SCOPE_CLASS : 0) | (allowDirectSuper ? SCOPE_DIRECT_SUPER : 0), ); + this.param.enter(functionFlags(isAsync, node.generator)); this.parseFunctionParams((node: any), allowModifiers); this.parseFunctionBodyAndFinish(node, type, true); + this.param.exit(); this.scope.exit(); this.state.yieldPos = oldYieldPos; @@ -1876,7 +1889,8 @@ export default class ExpressionParser extends LValParser { isAsync: boolean, trailingCommaPos: ?number, ): N.ArrowFunctionExpression { - this.scope.enter(functionFlags(isAsync, false) | SCOPE_ARROW); + this.scope.enter(SCOPE_FUNCTION | SCOPE_ARROW); + this.param.enter(functionFlags(isAsync, false)); this.initFunction(node, isAsync); const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; @@ -1889,6 +1903,7 @@ export default class ExpressionParser extends LValParser { if (params) this.setArrowFunctionParameters(node, params, trailingCommaPos); this.parseFunctionBody(node, true); + this.param.exit(); this.scope.exit(); this.state.maybeInArrowParameters = oldMaybeInArrowParameters; this.state.yieldPos = oldYieldPos; @@ -2162,7 +2177,7 @@ export default class ExpressionParser extends LValParser { checkKeywords: boolean, isBinding: boolean, ): void { - if (this.scope.inGenerator && word === "yield") { + if (this.param.hasYield && word === "yield") { this.raise( startLoc, "Can not use 'yield' as identifier inside a generator", @@ -2171,7 +2186,7 @@ export default class ExpressionParser extends LValParser { } if (word === "await") { - if (this.scope.inAsync) { + if (this.param.hasAwait) { this.raise( startLoc, "Can not use 'await' as identifier inside an async function", @@ -2209,7 +2224,7 @@ export default class ExpressionParser extends LValParser { : isStrictReservedWord; if (reservedTest(word, this.inModule)) { - if (!this.scope.inAsync && word === "await") { + if (!this.param.hasAwait && word === "await") { this.raise( startLoc, "Can not use keyword 'await' outside an async function", @@ -2221,10 +2236,10 @@ export default class ExpressionParser extends LValParser { } isAwaitAllowed(): boolean { - if (this.scope.inFunction) return this.scope.inAsync; + if (this.scope.inFunction) return this.param.hasAwait; if (this.options.allowAwaitOutsideFunction) return true; if (this.hasPlugin("topLevelAwait")) { - return this.inModule && this.scope.inAsync; + return this.inModule && this.param.hasAwait; } return false; } diff --git a/packages/babel-parser/src/parser/index.js b/packages/babel-parser/src/parser/index.js index d3f5db419c6b..6aebaca1d0cf 100644 --- a/packages/babel-parser/src/parser/index.js +++ b/packages/babel-parser/src/parser/index.js @@ -5,9 +5,13 @@ import type { File, JSXOpeningElement } from "../types"; import type { PluginList } from "../plugin-utils"; import { getOptions } from "../options"; import StatementParser from "./statement"; -import { SCOPE_ASYNC, SCOPE_PROGRAM } from "../util/scopeflags"; +import { SCOPE_PROGRAM } from "../util/scopeflags"; import ScopeHandler from "../util/scope"; import ClassScopeHandler from "../util/class-scope"; +import ProductionParameterHandler, { + PARAM_AWAIT, + PARAM_, +} from "../util/production-parameter"; export type PluginsMap = Map; @@ -26,6 +30,7 @@ export default class Parser extends StatementParser { this.options = options; this.inModule = this.options.sourceType === "module"; this.scope = new ScopeHandler(this.raise.bind(this), this.inModule); + this.param = new ProductionParameterHandler(); this.classScope = new ClassScopeHandler(this.raise.bind(this)); this.plugins = pluginsMap(this.options.plugins); this.filename = options.sourceFilename; @@ -37,11 +42,12 @@ export default class Parser extends StatementParser { } parse(): File { - let scopeFlags = SCOPE_PROGRAM; + let paramFlags = PARAM_; if (this.hasPlugin("topLevelAwait") && this.inModule) { - scopeFlags |= SCOPE_ASYNC; + paramFlags |= PARAM_AWAIT; } - this.scope.enter(scopeFlags); + this.scope.enter(SCOPE_PROGRAM); + this.param.enter(paramFlags); const file = this.startNode(); const program = this.startNode(); this.nextToken(); diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index a57a618c9748..abdb07c173a0 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -15,8 +15,8 @@ import { BIND_LEXICAL, BIND_VAR, BIND_FUNCTION, - functionFlags, SCOPE_CLASS, + SCOPE_FUNCTION, SCOPE_OTHER, SCOPE_SIMPLE_CATCH, SCOPE_SUPER, @@ -28,6 +28,7 @@ import { type BindingTypes, } from "../util/scopeflags"; import { ExpressionErrors } from "./util"; +import { PARAM_, functionFlags } from "../util/production-parameter"; const loopLabel = { kind: "loop" }, switchLabel = { kind: "switch" }; @@ -1059,7 +1060,8 @@ export default class StatementParser extends ExpressionParser { this.state.maybeInArrowParameters = false; this.state.yieldPos = -1; this.state.awaitPos = -1; - this.scope.enter(functionFlags(node.async, node.generator)); + this.scope.enter(SCOPE_FUNCTION); + this.param.enter(functionFlags(isAsync, node.generator)); if (!isStatement) { node.id = this.parseFunctionId(); @@ -1078,6 +1080,7 @@ export default class StatementParser extends ExpressionParser { ); }); + this.param.exit(); this.scope.exit(); if (isStatement && !isHangingStatement) { @@ -1599,9 +1602,12 @@ export default class StatementParser extends ExpressionParser { node: N.ClassPrivateProperty, ): N.ClassPrivateProperty { this.scope.enter(SCOPE_CLASS | SCOPE_SUPER); + // [In] production parameter is tracked in parseMaybeAssign + this.param.enter(PARAM_); node.value = this.eat(tt.eq) ? this.parseMaybeAssign() : null; this.semicolon(); + this.param.exit(); this.scope.exit(); @@ -1614,6 +1620,8 @@ export default class StatementParser extends ExpressionParser { } this.scope.enter(SCOPE_CLASS | SCOPE_SUPER); + // [In] production parameter is tracked in parseMaybeAssign + this.param.enter(PARAM_); if (this.match(tt.eq)) { this.expectPlugin("classProperties"); @@ -1624,6 +1632,7 @@ export default class StatementParser extends ExpressionParser { } this.semicolon(); + this.param.exit(); this.scope.exit(); return this.finishNode(node, "ClassProperty"); diff --git a/packages/babel-parser/src/plugins/flow.js b/packages/babel-parser/src/plugins/flow.js index 2ab7604514d1..960889bb3d9b 100644 --- a/packages/babel-parser/src/plugins/flow.js +++ b/packages/babel-parser/src/plugins/flow.js @@ -12,13 +12,13 @@ import { types as tc } from "../tokenizer/context"; import * as charCodes from "charcodes"; import { isIteratorStart } from "../util/identifier"; import { - functionFlags, type BindingTypes, BIND_NONE, BIND_LEXICAL, BIND_VAR, BIND_FUNCTION, SCOPE_ARROW, + SCOPE_FUNCTION, SCOPE_OTHER, } from "../util/scopeflags"; import type { ExpressionErrors } from "../parser/util"; @@ -1891,7 +1891,7 @@ export default (superClass: Class): Class => node.extra?.trailingComma, ); // Enter scope, as checkParams defines bindings - this.scope.enter(functionFlags(false, false) | SCOPE_ARROW); + this.scope.enter(SCOPE_FUNCTION | SCOPE_ARROW); // Use super's method to force the parameters to be checked super.checkParams(node, false, true); this.scope.exit(); diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index 08691c5b90ba..a7498e9ec006 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -26,6 +26,7 @@ import { import TypeScriptScopeHandler from "./scope"; import * as charCodes from "charcodes"; import type { ExpressionErrors } from "../../parser/util"; +import { PARAM_ } from "../../util/production-parameter"; type TsModifier = | "readonly" @@ -1265,7 +1266,9 @@ export default (superClass: Class): Class => node.body = inner; } else { this.scope.enter(SCOPE_TS_MODULE); + this.param.enter(PARAM_); node.body = this.tsParseModuleBlock(); + this.param.exit(); this.scope.exit(); } return this.finishNode(node, "TSModuleDeclaration"); @@ -1284,7 +1287,9 @@ export default (superClass: Class): Class => } if (this.match(tt.braceL)) { this.scope.enter(SCOPE_TS_MODULE); + this.param.enter(PARAM_); node.body = this.tsParseModuleBlock(); + this.param.exit(); this.scope.exit(); } else { this.semicolon(); @@ -1439,11 +1444,13 @@ export default (superClass: Class): Class => // Would like to use tsParseAmbientExternalModuleDeclaration here, but already ran past "global". if (this.match(tt.braceL)) { this.scope.enter(SCOPE_TS_MODULE); + this.param.enter(PARAM_); const mod: N.TsModuleDeclaration = node; mod.global = true; mod.id = expr; mod.body = this.tsParseModuleBlock(); this.scope.exit(); + this.param.exit(); return this.finishNode(mod, "TSModuleDeclaration"); } break; diff --git a/packages/babel-parser/src/tokenizer/context.js b/packages/babel-parser/src/tokenizer/context.js index 1d8108874f4a..5a5a762d7b69 100644 --- a/packages/babel-parser/src/tokenizer/context.js +++ b/packages/babel-parser/src/tokenizer/context.js @@ -60,7 +60,7 @@ tt.name.updateContext = function(prevType) { if (prevType !== tt.dot) { if ( (this.state.value === "of" && !this.state.exprAllowed) || - (this.state.value === "yield" && this.scope.inGenerator) + (this.state.value === "yield" && this.param.hasYield) ) { allowed = true; } diff --git a/packages/babel-parser/src/util/production-parameter.js b/packages/babel-parser/src/util/production-parameter.js new file mode 100644 index 000000000000..e08851e8988c --- /dev/null +++ b/packages/babel-parser/src/util/production-parameter.js @@ -0,0 +1,52 @@ +export const PARAM_ = 0b000, // Initial Parameter flags + PARAM_YIELD = 0b001, // track [Await] production parameter + PARAM_AWAIT = 0b010; // track [Yield] production parameter + +// ProductionParameterHandler is a stack fashioned production parameter tracker +// https://tc39.es/ecma262/#sec-grammar-notation +// It only tracks [Await] and [Yield] parameter. The [In] parameter is tracked +// in `noIn` argument of `parseExpression`. +// +// Whenever [+Await]/[+Yield] appears in the right-hand sides of a production, +// we must enter a new tracking stack. For example when parsing +// +// AsyncFunctionDeclaration [Yield, Await]: +// async [no LineTerminator here] function BindingIdentifier[?Yield, ?Await] +// ( FormalParameters[~Yield, +Await] ) { AsyncFunctionBody } +// +// we must follow such process: +// +// 1. parse async keyword +// 2. parse function keyword +// 3. parse bindingIdentifier <= inherit current parameters: [?Await] +// 4. enter new stack with (PARAM_AWAIT) +// 5. parse formal parameters <= must have [Await] parameter [+Await] +// 6. parse function body +// 7. exit current stack + +export default class ProductionParameterHandler { + stacks: Array = []; + enter(flags) { + this.stacks.push(flags); + } + + exit() { + this.stacks.pop(); + } + + currentFlags() { + return this.stacks[this.stacks.length - 1]; + } + + get hasAwait() { + return (this.currentFlags() & PARAM_AWAIT) > 0; + } + + get hasYield() { + return (this.currentFlags() & PARAM_YIELD) > 0; + } +} + +export function functionFlags(isAsync: boolean, isGenerator: boolean) { + return (isAsync ? PARAM_AWAIT : 0) | (isGenerator ? PARAM_YIELD : 0); +} diff --git a/packages/babel-parser/src/util/scope.js b/packages/babel-parser/src/util/scope.js index 1cbf06fb671f..0c64df17799f 100644 --- a/packages/babel-parser/src/util/scope.js +++ b/packages/babel-parser/src/util/scope.js @@ -1,10 +1,8 @@ // @flow import { SCOPE_ARROW, - SCOPE_ASYNC, SCOPE_DIRECT_SUPER, SCOPE_FUNCTION, - SCOPE_GENERATOR, SCOPE_SIMPLE_CATCH, SCOPE_SUPER, SCOPE_PROGRAM, @@ -53,25 +51,6 @@ export default class ScopeHandler { get inFunction() { return (this.currentVarScope().flags & SCOPE_FUNCTION) > 0; } - get inGenerator() { - return (this.currentVarScope().flags & SCOPE_GENERATOR) > 0; - } - // the following loop always exit because SCOPE_PROGRAM is SCOPE_VAR - // $FlowIgnore - get inAsync() { - for (let i = this.scopeStack.length - 1; ; i--) { - const scope = this.scopeStack[i]; - const isVarScope = scope.flags & SCOPE_VAR; - const isClassScope = scope.flags & SCOPE_CLASS; - if (isClassScope && !isVarScope) { - // If it meets a class scope before a var scope, it means it is a class property initializer - // which does not have an [Await] parameter in its grammar - return false; - } else if (isVarScope) { - return (scope.flags & SCOPE_ASYNC) > 0; - } - } - } get allowSuper() { return (this.currentThisScope().flags & SCOPE_SUPER) > 0; } diff --git a/packages/babel-parser/src/util/scopeflags.js b/packages/babel-parser/src/util/scopeflags.js index e49359cf615c..4a2ed0994735 100644 --- a/packages/babel-parser/src/util/scopeflags.js +++ b/packages/babel-parser/src/util/scopeflags.js @@ -5,8 +5,6 @@ export const SCOPE_OTHER = 0b0000000000, SCOPE_PROGRAM = 0b0000000001, SCOPE_FUNCTION = 0b0000000010, - SCOPE_ASYNC = 0b0000000100, - SCOPE_GENERATOR = 0b0000001000, SCOPE_ARROW = 0b0000010000, SCOPE_SIMPLE_CATCH = 0b0000100000, SCOPE_SUPER = 0b0001000000, @@ -20,22 +18,12 @@ export type ScopeFlags = | typeof SCOPE_PROGRAM | typeof SCOPE_FUNCTION | typeof SCOPE_VAR - | typeof SCOPE_ASYNC - | typeof SCOPE_GENERATOR | typeof SCOPE_ARROW | typeof SCOPE_SIMPLE_CATCH | typeof SCOPE_SUPER | typeof SCOPE_DIRECT_SUPER | typeof SCOPE_CLASS; -export function functionFlags(isAsync: boolean, isGenerator: boolean) { - return ( - SCOPE_FUNCTION | - (isAsync ? SCOPE_ASYNC : 0) | - (isGenerator ? SCOPE_GENERATOR : 0) - ); -} - // These flags are meant to be _only_ used inside the Scope class (or subclasses). // prettier-ignore export const BIND_KIND_VALUE = 0b00000_0000_01, From d76bfbb5fdb37263d21ea8736ecf8cadc0c19890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 3 Jan 2020 18:06:11 -0500 Subject: [PATCH 3/6] fix flow errors --- packages/babel-parser/src/parser/base.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/babel-parser/src/parser/base.js b/packages/babel-parser/src/parser/base.js index f02e6778270f..a67756d97db0 100644 --- a/packages/babel-parser/src/parser/base.js +++ b/packages/babel-parser/src/parser/base.js @@ -5,6 +5,7 @@ import type State from "../tokenizer/state"; import type { PluginsMap } from "./index"; import type ScopeHandler from "../util/scope"; import type ClassScopeHandler from "../util/class-scope"; +import type ProductionParameterHandler from "../util/production-parameter"; export default class BaseParser { // Properties set by constructor in index.js @@ -12,6 +13,7 @@ export default class BaseParser { inModule: boolean; scope: ScopeHandler<*>; classScope: ClassScopeHandler; + param: ProductionParameterHandler; plugins: PluginsMap; filename: ?string; sawUnambiguousESM: boolean = false; From 13cb86bb65ef0d6ecf5e1e84b2287c849f426a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 3 Jan 2020 18:10:38 -0500 Subject: [PATCH 4/6] add flow type annotation to production-parameter --- .../src/util/production-parameter.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/babel-parser/src/util/production-parameter.js b/packages/babel-parser/src/util/production-parameter.js index e08851e8988c..5833755dd357 100644 --- a/packages/babel-parser/src/util/production-parameter.js +++ b/packages/babel-parser/src/util/production-parameter.js @@ -1,3 +1,4 @@ +// @flow export const PARAM_ = 0b000, // Initial Parameter flags PARAM_YIELD = 0b001, // track [Await] production parameter PARAM_AWAIT = 0b010; // track [Yield] production parameter @@ -24,9 +25,11 @@ export const PARAM_ = 0b000, // Initial Parameter flags // 6. parse function body // 7. exit current stack +export type ParamKind = typeof PARAM_ | typeof PARAM_AWAIT | typeof PARAM_YIELD; + export default class ProductionParameterHandler { - stacks: Array = []; - enter(flags) { + stacks: Array = []; + enter(flags: ParamKind) { this.stacks.push(flags); } @@ -34,19 +37,22 @@ export default class ProductionParameterHandler { this.stacks.pop(); } - currentFlags() { + currentFlags(): ParamKind { return this.stacks[this.stacks.length - 1]; } - get hasAwait() { + get hasAwait(): boolean { return (this.currentFlags() & PARAM_AWAIT) > 0; } - get hasYield() { + get hasYield(): boolean { return (this.currentFlags() & PARAM_YIELD) > 0; } } -export function functionFlags(isAsync: boolean, isGenerator: boolean) { +export function functionFlags( + isAsync: boolean, + isGenerator: boolean, +): ParamKind { return (isAsync ? PARAM_AWAIT : 0) | (isGenerator ? PARAM_YIELD : 0); } From 2f7be26932c496a83618e0ef97c366293602f184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 3 Jan 2020 21:41:29 -0500 Subject: [PATCH 5/6] address review comments --- packages/babel-parser/src/parser/base.js | 2 +- .../babel-parser/src/parser/expression.js | 30 +++++++++---------- packages/babel-parser/src/parser/index.js | 8 ++--- packages/babel-parser/src/parser/statement.js | 14 ++++----- .../src/plugins/typescript/index.js | 14 ++++----- .../babel-parser/src/tokenizer/context.js | 2 +- .../src/util/production-parameter.js | 8 ++--- packages/babel-parser/src/util/scopeflags.js | 18 +++++------ 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/packages/babel-parser/src/parser/base.js b/packages/babel-parser/src/parser/base.js index a67756d97db0..6edb7ef063ae 100644 --- a/packages/babel-parser/src/parser/base.js +++ b/packages/babel-parser/src/parser/base.js @@ -13,7 +13,7 @@ export default class BaseParser { inModule: boolean; scope: ScopeHandler<*>; classScope: ClassScopeHandler; - param: ProductionParameterHandler; + prodParam: ProductionParameterHandler; plugins: PluginsMap; filename: ?string; sawUnambiguousESM: boolean = false; diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index d251a6244a3d..8d26c6b57d11 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -43,7 +43,7 @@ import { import { ExpressionErrors } from "./util"; import { PARAM_AWAIT, - PARAM_, + PARAM, functionFlags, } from "../util/production-parameter"; @@ -110,12 +110,12 @@ export default class ExpressionParser extends LValParser { // Convenience method to parse an Expression only getExpression(): N.Expression { - let paramFlags = PARAM_; + let paramFlags = PARAM; if (this.hasPlugin("topLevelAwait") && this.inModule) { paramFlags |= PARAM_AWAIT; } this.scope.enter(SCOPE_PROGRAM); - this.param.enter(paramFlags); + this.prodParam.enter(paramFlags); this.nextToken(); const expr = this.parseExpression(); if (!this.match(tt.eof)) { @@ -178,7 +178,7 @@ export default class ExpressionParser extends LValParser { const startPos = this.state.start; const startLoc = this.state.startLoc; if (this.isContextual("yield")) { - if (this.param.hasYield) { + if (this.prodParam.hasYield) { let left = this.parseYield(noIn); if (afterLeftParse) { left = afterLeftParse.call(this, left, startPos, startLoc); @@ -370,7 +370,7 @@ export default class ExpressionParser extends LValParser { if ( this.match(tt.name) && this.state.value === "await" && - this.param.hasAwait + this.prodParam.hasAwait ) { throw this.raise( this.state.start, @@ -1191,7 +1191,7 @@ export default class ExpressionParser extends LValParser { this.next(); meta = this.createIdentifier(meta, "function"); - if (this.param.hasYield && this.eat(tt.dot)) { + if (this.prodParam.hasYield && this.eat(tt.dot)) { return this.parseMetaProperty(node, meta, "sent"); } return this.parseFunction(node); @@ -1868,10 +1868,10 @@ export default class ExpressionParser extends LValParser { (inClassScope ? SCOPE_CLASS : 0) | (allowDirectSuper ? SCOPE_DIRECT_SUPER : 0), ); - this.param.enter(functionFlags(isAsync, node.generator)); + this.prodParam.enter(functionFlags(isAsync, node.generator)); this.parseFunctionParams((node: any), allowModifiers); this.parseFunctionBodyAndFinish(node, type, true); - this.param.exit(); + this.prodParam.exit(); this.scope.exit(); this.state.yieldPos = oldYieldPos; @@ -1890,7 +1890,7 @@ export default class ExpressionParser extends LValParser { trailingCommaPos: ?number, ): N.ArrowFunctionExpression { this.scope.enter(SCOPE_FUNCTION | SCOPE_ARROW); - this.param.enter(functionFlags(isAsync, false)); + this.prodParam.enter(functionFlags(isAsync, false)); this.initFunction(node, isAsync); const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; @@ -1903,7 +1903,7 @@ export default class ExpressionParser extends LValParser { if (params) this.setArrowFunctionParameters(node, params, trailingCommaPos); this.parseFunctionBody(node, true); - this.param.exit(); + this.prodParam.exit(); this.scope.exit(); this.state.maybeInArrowParameters = oldMaybeInArrowParameters; this.state.yieldPos = oldYieldPos; @@ -2177,7 +2177,7 @@ export default class ExpressionParser extends LValParser { checkKeywords: boolean, isBinding: boolean, ): void { - if (this.param.hasYield && word === "yield") { + if (this.prodParam.hasYield && word === "yield") { this.raise( startLoc, "Can not use 'yield' as identifier inside a generator", @@ -2186,7 +2186,7 @@ export default class ExpressionParser extends LValParser { } if (word === "await") { - if (this.param.hasAwait) { + if (this.prodParam.hasAwait) { this.raise( startLoc, "Can not use 'await' as identifier inside an async function", @@ -2224,7 +2224,7 @@ export default class ExpressionParser extends LValParser { : isStrictReservedWord; if (reservedTest(word, this.inModule)) { - if (!this.param.hasAwait && word === "await") { + if (!this.prodParam.hasAwait && word === "await") { this.raise( startLoc, "Can not use keyword 'await' outside an async function", @@ -2236,10 +2236,10 @@ export default class ExpressionParser extends LValParser { } isAwaitAllowed(): boolean { - if (this.scope.inFunction) return this.param.hasAwait; + if (this.scope.inFunction) return this.prodParam.hasAwait; if (this.options.allowAwaitOutsideFunction) return true; if (this.hasPlugin("topLevelAwait")) { - return this.inModule && this.param.hasAwait; + return this.inModule && this.prodParam.hasAwait; } return false; } diff --git a/packages/babel-parser/src/parser/index.js b/packages/babel-parser/src/parser/index.js index 6aebaca1d0cf..0070d320c645 100644 --- a/packages/babel-parser/src/parser/index.js +++ b/packages/babel-parser/src/parser/index.js @@ -10,7 +10,7 @@ import ScopeHandler from "../util/scope"; import ClassScopeHandler from "../util/class-scope"; import ProductionParameterHandler, { PARAM_AWAIT, - PARAM_, + PARAM, } from "../util/production-parameter"; export type PluginsMap = Map; @@ -30,7 +30,7 @@ export default class Parser extends StatementParser { this.options = options; this.inModule = this.options.sourceType === "module"; this.scope = new ScopeHandler(this.raise.bind(this), this.inModule); - this.param = new ProductionParameterHandler(); + this.prodParam = new ProductionParameterHandler(); this.classScope = new ClassScopeHandler(this.raise.bind(this)); this.plugins = pluginsMap(this.options.plugins); this.filename = options.sourceFilename; @@ -42,12 +42,12 @@ export default class Parser extends StatementParser { } parse(): File { - let paramFlags = PARAM_; + let paramFlags = PARAM; if (this.hasPlugin("topLevelAwait") && this.inModule) { paramFlags |= PARAM_AWAIT; } this.scope.enter(SCOPE_PROGRAM); - this.param.enter(paramFlags); + this.prodParam.enter(paramFlags); const file = this.startNode(); const program = this.startNode(); this.nextToken(); diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index abdb07c173a0..67f4279fe805 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -28,7 +28,7 @@ import { type BindingTypes, } from "../util/scopeflags"; import { ExpressionErrors } from "./util"; -import { PARAM_, functionFlags } from "../util/production-parameter"; +import { PARAM, functionFlags } from "../util/production-parameter"; const loopLabel = { kind: "loop" }, switchLabel = { kind: "switch" }; @@ -1061,7 +1061,7 @@ export default class StatementParser extends ExpressionParser { this.state.yieldPos = -1; this.state.awaitPos = -1; this.scope.enter(SCOPE_FUNCTION); - this.param.enter(functionFlags(isAsync, node.generator)); + this.prodParam.enter(functionFlags(isAsync, node.generator)); if (!isStatement) { node.id = this.parseFunctionId(); @@ -1080,7 +1080,7 @@ export default class StatementParser extends ExpressionParser { ); }); - this.param.exit(); + this.prodParam.exit(); this.scope.exit(); if (isStatement && !isHangingStatement) { @@ -1603,11 +1603,11 @@ export default class StatementParser extends ExpressionParser { ): N.ClassPrivateProperty { this.scope.enter(SCOPE_CLASS | SCOPE_SUPER); // [In] production parameter is tracked in parseMaybeAssign - this.param.enter(PARAM_); + this.prodParam.enter(PARAM); node.value = this.eat(tt.eq) ? this.parseMaybeAssign() : null; this.semicolon(); - this.param.exit(); + this.prodParam.exit(); this.scope.exit(); @@ -1621,7 +1621,7 @@ export default class StatementParser extends ExpressionParser { this.scope.enter(SCOPE_CLASS | SCOPE_SUPER); // [In] production parameter is tracked in parseMaybeAssign - this.param.enter(PARAM_); + this.prodParam.enter(PARAM); if (this.match(tt.eq)) { this.expectPlugin("classProperties"); @@ -1632,7 +1632,7 @@ export default class StatementParser extends ExpressionParser { } this.semicolon(); - this.param.exit(); + this.prodParam.exit(); this.scope.exit(); return this.finishNode(node, "ClassProperty"); diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index a7498e9ec006..83771ccd0ae0 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -26,7 +26,7 @@ import { import TypeScriptScopeHandler from "./scope"; import * as charCodes from "charcodes"; import type { ExpressionErrors } from "../../parser/util"; -import { PARAM_ } from "../../util/production-parameter"; +import { PARAM } from "../../util/production-parameter"; type TsModifier = | "readonly" @@ -1266,9 +1266,9 @@ export default (superClass: Class): Class => node.body = inner; } else { this.scope.enter(SCOPE_TS_MODULE); - this.param.enter(PARAM_); + this.prodParam.enter(PARAM); node.body = this.tsParseModuleBlock(); - this.param.exit(); + this.prodParam.exit(); this.scope.exit(); } return this.finishNode(node, "TSModuleDeclaration"); @@ -1287,9 +1287,9 @@ export default (superClass: Class): Class => } if (this.match(tt.braceL)) { this.scope.enter(SCOPE_TS_MODULE); - this.param.enter(PARAM_); + this.prodParam.enter(PARAM); node.body = this.tsParseModuleBlock(); - this.param.exit(); + this.prodParam.exit(); this.scope.exit(); } else { this.semicolon(); @@ -1444,13 +1444,13 @@ export default (superClass: Class): Class => // Would like to use tsParseAmbientExternalModuleDeclaration here, but already ran past "global". if (this.match(tt.braceL)) { this.scope.enter(SCOPE_TS_MODULE); - this.param.enter(PARAM_); + this.prodParam.enter(PARAM); const mod: N.TsModuleDeclaration = node; mod.global = true; mod.id = expr; mod.body = this.tsParseModuleBlock(); this.scope.exit(); - this.param.exit(); + this.prodParam.exit(); return this.finishNode(mod, "TSModuleDeclaration"); } break; diff --git a/packages/babel-parser/src/tokenizer/context.js b/packages/babel-parser/src/tokenizer/context.js index 5a5a762d7b69..3f3907872c06 100644 --- a/packages/babel-parser/src/tokenizer/context.js +++ b/packages/babel-parser/src/tokenizer/context.js @@ -60,7 +60,7 @@ tt.name.updateContext = function(prevType) { if (prevType !== tt.dot) { if ( (this.state.value === "of" && !this.state.exprAllowed) || - (this.state.value === "yield" && this.param.hasYield) + (this.state.value === "yield" && this.prodParam.hasYield) ) { allowed = true; } diff --git a/packages/babel-parser/src/util/production-parameter.js b/packages/babel-parser/src/util/production-parameter.js index 5833755dd357..9f468addc3d6 100644 --- a/packages/babel-parser/src/util/production-parameter.js +++ b/packages/babel-parser/src/util/production-parameter.js @@ -1,7 +1,7 @@ // @flow -export const PARAM_ = 0b000, // Initial Parameter flags - PARAM_YIELD = 0b001, // track [Await] production parameter - PARAM_AWAIT = 0b010; // track [Yield] production parameter +export const PARAM = 0b00, // Initial Parameter flags + PARAM_YIELD = 0b01, // track [Await] production parameter + PARAM_AWAIT = 0b10; // track [Yield] production parameter // ProductionParameterHandler is a stack fashioned production parameter tracker // https://tc39.es/ecma262/#sec-grammar-notation @@ -25,7 +25,7 @@ export const PARAM_ = 0b000, // Initial Parameter flags // 6. parse function body // 7. exit current stack -export type ParamKind = typeof PARAM_ | typeof PARAM_AWAIT | typeof PARAM_YIELD; +export type ParamKind = typeof PARAM | typeof PARAM_AWAIT | typeof PARAM_YIELD; export default class ProductionParameterHandler { stacks: Array = []; diff --git a/packages/babel-parser/src/util/scopeflags.js b/packages/babel-parser/src/util/scopeflags.js index 4a2ed0994735..65c8fd4a608f 100644 --- a/packages/babel-parser/src/util/scopeflags.js +++ b/packages/babel-parser/src/util/scopeflags.js @@ -2,15 +2,15 @@ // Each scope gets a bitset that may contain these flags // prettier-ignore -export const SCOPE_OTHER = 0b0000000000, - SCOPE_PROGRAM = 0b0000000001, - SCOPE_FUNCTION = 0b0000000010, - SCOPE_ARROW = 0b0000010000, - SCOPE_SIMPLE_CATCH = 0b0000100000, - SCOPE_SUPER = 0b0001000000, - SCOPE_DIRECT_SUPER = 0b0010000000, - SCOPE_CLASS = 0b0100000000, - SCOPE_TS_MODULE = 0b1000000000, +export const SCOPE_OTHER = 0b00000000, + SCOPE_PROGRAM = 0b00000001, + SCOPE_FUNCTION = 0b00000010, + SCOPE_ARROW = 0b00000100, + SCOPE_SIMPLE_CATCH = 0b00001000, + SCOPE_SUPER = 0b00010000, + SCOPE_DIRECT_SUPER = 0b00100000, + SCOPE_CLASS = 0b01000000, + SCOPE_TS_MODULE = 0b10000000, SCOPE_VAR = SCOPE_PROGRAM | SCOPE_FUNCTION | SCOPE_TS_MODULE; export type ScopeFlags = From 810264896dd85216a84a7fa29a691b6ceec0b1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 3 Jan 2020 22:10:27 -0500 Subject: [PATCH 6/6] refactor: track [Return] parameter --- packages/babel-parser/src/parser/expression.js | 5 +++++ packages/babel-parser/src/parser/statement.js | 2 +- .../babel-parser/src/util/production-parameter.js | 15 ++++++++++----- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 8d26c6b57d11..d469de858b04 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -43,6 +43,7 @@ import { import { ExpressionErrors } from "./util"; import { PARAM_AWAIT, + PARAM_RETURN, PARAM, functionFlags, } from "../util/production-parameter"; @@ -1986,7 +1987,11 @@ export default class ExpressionParser extends LValParser { allowExpression, !oldStrict && useStrict, ); + // FunctionBody[Yield, Await]: + // StatementList[?Yield, ?Await, +Return] opt + this.prodParam.enter(this.prodParam.currentFlags() | PARAM_RETURN); node.body = this.parseBlock(true, false); + this.prodParam.exit(); this.state.labels = oldLabels; } diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 67f4279fe805..0a0d595a5457 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -575,7 +575,7 @@ export default class StatementParser extends ExpressionParser { } parseReturnStatement(node: N.ReturnStatement): N.ReturnStatement { - if (!this.scope.inFunction && !this.options.allowReturnOutsideFunction) { + if (!this.prodParam.hasReturn && !this.options.allowReturnOutsideFunction) { this.raise(this.state.start, "'return' outside of function"); } diff --git a/packages/babel-parser/src/util/production-parameter.js b/packages/babel-parser/src/util/production-parameter.js index 9f468addc3d6..43644f6131ce 100644 --- a/packages/babel-parser/src/util/production-parameter.js +++ b/packages/babel-parser/src/util/production-parameter.js @@ -1,12 +1,13 @@ // @flow -export const PARAM = 0b00, // Initial Parameter flags - PARAM_YIELD = 0b01, // track [Await] production parameter - PARAM_AWAIT = 0b10; // track [Yield] production parameter +export const PARAM = 0b000, // Initial Parameter flags + PARAM_YIELD = 0b001, // track [Yield] production parameter + PARAM_AWAIT = 0b010, // track [Await] production parameter + PARAM_RETURN = 0b100; // track [Return] production parameter // ProductionParameterHandler is a stack fashioned production parameter tracker // https://tc39.es/ecma262/#sec-grammar-notation -// It only tracks [Await] and [Yield] parameter. The [In] parameter is tracked -// in `noIn` argument of `parseExpression`. +// The tracked parameters are defined above. Note that the [In] parameter is +// tracked in `noIn` argument of `parseExpression`. // // Whenever [+Await]/[+Yield] appears in the right-hand sides of a production, // we must enter a new tracking stack. For example when parsing @@ -48,6 +49,10 @@ export default class ProductionParameterHandler { get hasYield(): boolean { return (this.currentFlags() & PARAM_YIELD) > 0; } + + get hasReturn(): boolean { + return (this.currentFlags() & PARAM_RETURN) > 0; + } } export function functionFlags(