From fa9b0e3cdba15fdf72233fb6d2dd650dbb636c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Thu, 24 Nov 2022 22:45:05 -0500 Subject: [PATCH 01/13] refactor: pack parseStatement flags --- packages/babel-parser/src/parser/statement.ts | 156 ++++++++++++------ .../babel-parser/src/plugins/flow/index.ts | 4 +- .../babel-parser/src/plugins/placeholders.ts | 8 +- .../src/plugins/typescript/index.ts | 9 +- 4 files changed, 116 insertions(+), 61 deletions(-) diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index b78f7149fb2e..528a3478469b 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -51,6 +51,13 @@ const FUNC_NO_FLAGS = 0b000, FUNC_HANGING_STATEMENT = 0b010, FUNC_NULLABLE_ID = 0b100; +export const enum ParseStatementFlag { + StatementOnly = 0b000, + AllowImportExport = 0b001, + AllowDeclaration = 0b010, + AllowFunctionDeclaration = 0b100, +} + const loneSurrogate = /[\uD800-\uDFFF]/u; const keywordRelationalOperator = /in(?:stanceof)?/y; @@ -277,23 +284,23 @@ export default abstract class StatementParser extends ExpressionParser { return this.finishNode(node, "InterpreterDirective"); } - isLet(context?: string | null): boolean { + isLet(): boolean { if (!this.isContextual(tt._let)) { return false; } - return this.hasFollowingIdentifier(context); + return this.hasFollowingIdentifier(true); } /** - * Assuming we have seen a contextual `let`, check if it starts a variable declaration - so that `left` should be interpreted as a `let` keyword. + * Assuming we have seen a contextual `let` / `using`, check if it starts a variable declaration + so that it should be interpreted as a keyword. * - * @param {?string} context When `context` is non nullish, it will return early and _skip_ checking + * @param {number} allowDeclaration When `allowDeclaration` is false, it will return early and _skip_ checking if the next token after `let` is `{` or a keyword relational operator * @returns {boolean} * @memberof StatementParser */ - hasFollowingIdentifier(context?: string | null): boolean { + hasFollowingIdentifier(allowDeclaration: boolean): boolean { const next = this.nextTokenStart(); const nextCh = this.codePointAtPos(next); // For ambiguous cases, determine if a LexicalDeclaration (or only a @@ -307,7 +314,7 @@ export default abstract class StatementParser extends ExpressionParser { ) { return true; } - if (context) return false; + if (!allowDeclaration) return false; if (nextCh === charCodes.leftCurlyBrace) return true; @@ -337,6 +344,27 @@ export default abstract class StatementParser extends ExpressionParser { } } + // https://tc39.es/ecma262/#prod-ModuleItem + parseModuleItem(this: Parser) { + return this.parseStatementLike( + ParseStatementFlag.AllowImportExport | + ParseStatementFlag.AllowDeclaration | + ParseStatementFlag.AllowFunctionDeclaration, + ); + } + + // https://tc39.es/ecma262/#prod-StatementListItem + parseStatementListItem(this: Parser) { + return this.parseStatementLike( + ParseStatementFlag.AllowDeclaration | + ParseStatementFlag.AllowFunctionDeclaration, + ); + } + + parseStatementOrFunctionDeclaration(this: Parser) { + return this.parseStatementLike(ParseStatementFlag.AllowFunctionDeclaration); + } + // Parse a single statement. // // If expecting a statement and finding a slash operator, parse a @@ -344,29 +372,39 @@ export default abstract class StatementParser extends ExpressionParser { // `if (foo) /blah/.exec(foo)`, where looking at the previous token // does not help. // https://tc39.es/ecma262/#prod-Statement + parseStatement(this: Parser) { + return this.parseStatementLike(ParseStatementFlag.StatementOnly); + } + // ImportDeclaration and ExportDeclaration are also handled here so we can throw recoverable errors // when they are not at the top level - parseStatement( + parseStatementLike( this: Parser, - context?: string | null, - topLevel?: boolean, - ): N.Statement { + flags: ParseStatementFlag, + ): + | N.Statement + | N.Declaration + | N.ImportDeclaration + | N.ExportDefaultDeclaration + | N.ExportNamedDeclaration + | N.ExportAllDeclaration { let decorators: N.Decorator[] | null = null; if (this.match(tt.at)) { decorators = this.parseDecorators(true); } - return this.parseStatementContent(context, topLevel, decorators); + return this.parseStatementContent(flags, decorators); } parseStatementContent( this: Parser, - context?: string | null, - topLevel?: boolean | null, + flags: ParseStatementFlag, decorators?: N.Decorator[] | null, ): N.Statement { const starttype = this.state.type; const node = this.startNode(); + const allowDeclaration = !!(flags & ParseStatementFlag.AllowDeclaration); + const topLevel = flags & ParseStatementFlag.AllowImportExport; // Most types of statements are recognized by the keyword they // start with. Many are trivial to parse, some require a bit of @@ -380,26 +418,26 @@ export default abstract class StatementParser extends ExpressionParser { case tt._debugger: return this.parseDebuggerStatement(node as Undone); case tt._do: - return this.parseDoStatement(node as Undone); + return this.parseDoWhileStatement(node as Undone); case tt._for: return this.parseForStatement(node as Undone); case tt._function: if (this.lookaheadCharCode() === charCodes.dot) break; - if (context) { + if (!allowDeclaration) { if (this.state.strict) { this.raise(Errors.StrictFunction, { at: this.state.startLoc }); - } else if (context !== "if" && context !== "label") { + } else if (!(flags & ParseStatementFlag.AllowFunctionDeclaration)) { this.raise(Errors.SloppyFunction, { at: this.state.startLoc }); } } return this.parseFunctionStatement( node as Undone, false, - !context, + allowDeclaration, ); case tt._class: - if (context) this.unexpected(); + if (!allowDeclaration) this.unexpected(); return this.parseClass( this.maybeTakeDecorators( decorators, @@ -426,11 +464,21 @@ export default abstract class StatementParser extends ExpressionParser { } // fall through case tt._let: - if (this.state.containsEsc || !this.hasFollowingIdentifier(context)) { + if ( + this.state.containsEsc || + !this.hasFollowingIdentifier(allowDeclaration) + ) { break; } // fall through - case tt._const: + case tt._const: { + if (!allowDeclaration) { + this.raise(Errors.UnexpectedLexicalDeclaration, { + at: this.state.startLoc, + }); + } + } + // fall through case tt._var: { const kind = this.state.value; if (kind === "using") { @@ -441,11 +489,6 @@ export default abstract class StatementParser extends ExpressionParser { }); } } - if (context && kind !== "var") { - this.raise(Errors.UnexpectedLexicalDeclaration, { - at: this.state.startLoc, - }); - } return this.parseVarStatement( node as Undone, kind, @@ -516,7 +559,7 @@ export default abstract class StatementParser extends ExpressionParser { default: { if (this.isAsyncFunction()) { - if (context) { + if (!allowDeclaration) { this.raise(Errors.AsyncFunctionInSingleStatementContext, { at: this.state.startLoc, }); @@ -525,7 +568,7 @@ export default abstract class StatementParser extends ExpressionParser { return this.parseFunctionStatement( node as Undone, true, - !context, + allowDeclaration, ); } } @@ -549,7 +592,7 @@ export default abstract class StatementParser extends ExpressionParser { maybeName, // @ts-expect-error migrate to Babel types expr, - context, + flags, ); } else { return this.parseExpressionStatement( @@ -749,7 +792,8 @@ export default abstract class StatementParser extends ExpressionParser { return val; } - parseDoStatement( + // https://tc39.es/ecma262/#prod-DoWhileStatement + parseDoWhileStatement( this: Parser, node: Undone, ): N.DoWhileStatement { @@ -763,7 +807,7 @@ export default abstract class StatementParser extends ExpressionParser { // outside of the loop body. this.withSmartMixTopicForbiddingContext(() => // Parse the loop body's body. - this.parseStatement("do"), + this.parseStatement(), ); this.state.labels.pop(); @@ -808,9 +852,9 @@ export default abstract class StatementParser extends ExpressionParser { const startsWithUsing = this.isContextual(tt._using) && !this.hasFollowingLineBreak(); const isLetOrUsing = - (startsWithLet && this.hasFollowingIdentifier()) || + (startsWithLet && this.hasFollowingIdentifier(true)) || (startsWithUsing && - this.hasFollowingIdentifier() && + this.hasFollowingIdentifier(true) && this.startsUsingForOf()); if (this.match(tt._var) || this.match(tt._const) || isLetOrUsing) { const initNode = this.startNode(); @@ -881,6 +925,7 @@ export default abstract class StatementParser extends ExpressionParser { return this.parseFor(node as Undone, init); } + // https://tc39.es/ecma262/#prod-HoistableDeclaration parseFunctionStatement( this: Parser, node: Undone, @@ -895,11 +940,16 @@ export default abstract class StatementParser extends ExpressionParser { ); } + // https://tc39.es/ecma262/#prod-IfStatement parseIfStatement(this: Parser, node: Undone) { this.next(); node.test = this.parseHeaderExpression(); - node.consequent = this.parseStatement("if"); - node.alternate = this.eat(tt._else) ? this.parseStatement("if") : null; + // Annex B.3.3 + // https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses + node.consequent = this.parseStatementOrFunctionDeclaration(); + node.alternate = this.eat(tt._else) + ? this.parseStatementOrFunctionDeclaration() + : null; return this.finishNode(node, "IfStatement"); } @@ -924,6 +974,7 @@ export default abstract class StatementParser extends ExpressionParser { return this.finishNode(node, "ReturnStatement"); } + // https://tc39.es/ecma262/#prod-SwitchStatement parseSwitchStatement(this: Parser, node: Undone) { this.next(); node.discriminant = this.parseHeaderExpression(); @@ -959,7 +1010,7 @@ export default abstract class StatementParser extends ExpressionParser { this.expect(tt.colon); } else { if (cur) { - cur.consequent.push(this.parseStatement(null)); + cur.consequent.push(this.parseStatementListItem()); } else { this.unexpected(); } @@ -1039,6 +1090,8 @@ export default abstract class StatementParser extends ExpressionParser { return this.finishNode(node, "TryStatement"); } + // https://tc39.es/ecma262/#prod-VariableStatement + // https://tc39.es/ecma262/#prod-LexicalDeclaration parseVarStatement( this: Parser, node: Undone, @@ -1051,6 +1104,7 @@ export default abstract class StatementParser extends ExpressionParser { return this.finishNode(node, "VariableDeclaration"); } + // https://tc39.es/ecma262/#prod-WhileStatement parseWhileStatement( this: Parser, node: Undone, @@ -1066,7 +1120,7 @@ export default abstract class StatementParser extends ExpressionParser { // They are permitted in test expressions, outside of the loop body. this.withSmartMixTopicForbiddingContext(() => // Parse loop body. - this.parseStatement("while"), + this.parseStatement(), ); this.state.labels.pop(); @@ -1092,7 +1146,7 @@ export default abstract class StatementParser extends ExpressionParser { // part of the outer context, outside of the with statement's body. this.withSmartMixTopicForbiddingContext(() => // Parse the statement body. - this.parseStatement("with"), + this.parseStatement(), ); return this.finishNode(node, "WithStatement"); @@ -1103,12 +1157,13 @@ export default abstract class StatementParser extends ExpressionParser { return this.finishNode(node, "EmptyStatement"); } + // https://tc39.es/ecma262/#prod-LabelledStatement parseLabeledStatement( this: Parser, node: Undone, maybeName: string, expr: N.Identifier, - context?: string | null, + flags: ParseStatementFlag, ): N.LabeledStatement { for (const label of this.state.labels) { if (label.name === maybeName) { @@ -1139,13 +1194,11 @@ export default abstract class StatementParser extends ExpressionParser { kind: kind, statementStart: this.state.start, }); - node.body = this.parseStatement( - context - ? context.indexOf("label") === -1 - ? context + "label" - : context - : "label", - ); + // https://tc39.es/ecma262/#prod-LabelledItem + node.body = + flags & ParseStatementFlag.AllowFunctionDeclaration + ? this.parseStatementOrFunctionDeclaration() + : this.parseStatement(); this.state.labels.pop(); node.label = expr; @@ -1238,7 +1291,9 @@ export default abstract class StatementParser extends ExpressionParser { let parsedNonDirective = false; while (!this.match(end)) { - const stmt = this.parseStatement(null, topLevel); + const stmt = topLevel + ? this.parseModuleItem() + : this.parseStatementListItem(); if (directives && !parsedNonDirective) { if (this.isValidDirective(stmt)) { @@ -1296,7 +1351,7 @@ export default abstract class StatementParser extends ExpressionParser { // outside of the loop body. this.withSmartMixTopicForbiddingContext(() => // Parse the loop body. - this.parseStatement("for"), + this.parseStatement(), ); this.scope.exit(); @@ -1357,7 +1412,7 @@ export default abstract class StatementParser extends ExpressionParser { // They are permitted in test expressions, outside of the loop body. this.withSmartMixTopicForbiddingContext(() => // Parse loop body. - this.parseStatement("for"), + this.parseStatement(), ); this.scope.exit(); @@ -2386,6 +2441,7 @@ export default abstract class StatementParser extends ExpressionParser { return res; } + // https://tc39.es/ecma262/#prod-ExportDeclaration parseExportDeclaration( this: Parser, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -2399,7 +2455,7 @@ export default abstract class StatementParser extends ExpressionParser { ); return node; } - return this.parseStatement(null) as N.Declaration; + return this.parseStatementListItem() as N.Declaration; } isExportDefaultSpecifier(): boolean { diff --git a/packages/babel-parser/src/plugins/flow/index.ts b/packages/babel-parser/src/plugins/flow/index.ts index 17bc42bd07b0..f3e3582c548c 100644 --- a/packages/babel-parser/src/plugins/flow/index.ts +++ b/packages/babel-parser/src/plugins/flow/index.ts @@ -1932,7 +1932,7 @@ export default (superClass: typeof Parser) => } // interfaces and enums - parseStatement(context?: string | null, topLevel?: boolean): N.Statement { + parseStatementLike(flags: number): N.Statement { // strict mode handling of `interface` since it's a reserved word if (this.state.strict && this.isContextual(tt._interface)) { const lookahead = this.lookahead(); @@ -1946,7 +1946,7 @@ export default (superClass: typeof Parser) => this.next(); return this.flowParseEnumDeclaration(node); } - const stmt = super.parseStatement(context, topLevel); + const stmt = super.parseStatementLike(flags); // We will parse a flow pragma in any comment before the first statement. if (this.flowPragma === undefined && !this.isValidDirective(stmt)) { this.flowPragma = null; diff --git a/packages/babel-parser/src/plugins/placeholders.ts b/packages/babel-parser/src/plugins/placeholders.ts index 232c41bdd5d2..c715efc748b0 100644 --- a/packages/babel-parser/src/plugins/placeholders.ts +++ b/packages/babel-parser/src/plugins/placeholders.ts @@ -152,12 +152,12 @@ export default (superClass: typeof Parser) => * parser/statement.js * * ============================================================ */ - hasFollowingIdentifier(context?: string | null): boolean { - if (super.hasFollowingIdentifier(context)) { + hasFollowingIdentifier(allowDeclaration: boolean): boolean { + if (super.hasFollowingIdentifier(allowDeclaration)) { return true; } - if (context) return false; + if (!allowDeclaration) return false; // Accept "let %%" as the start of "let %%placeholder%%", as though the // placeholder were an identifier. @@ -196,7 +196,7 @@ export default (superClass: typeof Parser) => const stmt: N.LabeledStatement = node; stmt.label = this.finishPlaceholder(expr, "Identifier"); this.next(); - stmt.body = super.parseStatement("label"); + stmt.body = super.parseStatementOrFunctionDeclaration(); return this.finishNode(stmt, "LabeledStatement"); } diff --git a/packages/babel-parser/src/plugins/typescript/index.ts b/packages/babel-parser/src/plugins/typescript/index.ts index e83acac52cf7..fd3db4f40ba7 100644 --- a/packages/babel-parser/src/plugins/typescript/index.ts +++ b/packages/babel-parser/src/plugins/typescript/index.ts @@ -2757,7 +2757,7 @@ export default (superClass: ClassWithMixin) => parseVarStatement( node: N.VariableDeclaration, - kind: "var" | "let" | "const", + kind: "var" | "let" | "const" | "using", allowMissingInitializer: boolean = false, ) { const { isAmbientContext } = this.state; @@ -2804,8 +2804,7 @@ export default (superClass: ClassWithMixin) => } parseStatementContent( - context?: string | null, - topLevel?: boolean | null, + flags: number, decorators?: N.Decorator[] | null, ): N.Statement { if (this.match(tt._const) && this.isLookaheadContextual("enum")) { @@ -2825,7 +2824,7 @@ export default (superClass: ClassWithMixin) => if (result) return result; } - return super.parseStatementContent(context, topLevel, decorators); + return super.parseStatementContent(flags, decorators); } parseAccessModifier(): N.Accessibility | undefined | null { @@ -3280,7 +3279,7 @@ export default (superClass: ClassWithMixin) => // `let x: number;` parseVarId( decl: N.VariableDeclarator, - kind: "var" | "let" | "const", + kind: "var" | "let" | "const" | "using", ): void { super.parseVarId(decl, kind); if ( From 705894756a48fc2c5741c746fbf7d803d6b7f26e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 25 Nov 2022 12:11:03 -0500 Subject: [PATCH 02/13] refactor: use const enum for parseFunctionFlags --- .../babel-parser/src/parser/expression.ts | 9 +-- packages/babel-parser/src/parser/statement.ts | 67 +++++++++++-------- packages/babel-parser/src/plugins/estree.ts | 5 +- .../src/plugins/typescript/index.ts | 2 +- 4 files changed, 43 insertions(+), 40 deletions(-) diff --git a/packages/babel-parser/src/parser/expression.ts b/packages/babel-parser/src/parser/expression.ts index 9298b7f6ee5c..3c8f8e20eda3 100644 --- a/packages/babel-parser/src/parser/expression.ts +++ b/packages/babel-parser/src/parser/expression.ts @@ -2407,13 +2407,10 @@ export default abstract class ExpressionParser extends LValParser { // Initialize empty function node. - initFunction( - node: N.BodilessFunctionOrMethodBase, - isAsync?: boolean | null, - ): void { + initFunction(node: N.BodilessFunctionOrMethodBase, isAsync: boolean): void { node.id = null; node.generator = false; - node.async = !!isAsync; + node.async = isAsync; } // Parse object or class method. @@ -2429,7 +2426,7 @@ export default abstract class ExpressionParser extends LValParser { inClassScope: boolean = false, ): T { this.initFunction(node, isAsync); - node.generator = !!isGenerator; + node.generator = isGenerator; const allowModifiers = isConstructor; // For TypeScript parameter properties this.scope.enter( SCOPE_FUNCTION | diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index 528a3478469b..feb0b97fb700 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -46,10 +46,12 @@ import type Parser from "./index"; const loopLabel = { kind: "loop" } as const, switchLabel = { kind: "switch" } as const; -const FUNC_NO_FLAGS = 0b000, - FUNC_STATEMENT = 0b001, - FUNC_HANGING_STATEMENT = 0b010, - FUNC_NULLABLE_ID = 0b100; +export const enum ParseFunctionFlag { + Expression = 0b000, + Declaration = 0b001, + HangingDeclaration = 0b010, + NullableId = 0b100, +} export const enum ParseStatementFlag { StatementOnly = 0b000, @@ -404,6 +406,9 @@ export default abstract class StatementParser extends ExpressionParser { const starttype = this.state.type; const node = this.startNode(); const allowDeclaration = !!(flags & ParseStatementFlag.AllowDeclaration); + const allowFunctionDeclaration = !!( + flags & ParseStatementFlag.AllowFunctionDeclaration + ); const topLevel = flags & ParseStatementFlag.AllowImportExport; // Most types of statements are recognized by the keyword they @@ -426,16 +431,15 @@ export default abstract class StatementParser extends ExpressionParser { if (!allowDeclaration) { if (this.state.strict) { this.raise(Errors.StrictFunction, { at: this.state.startLoc }); - } else if (!(flags & ParseStatementFlag.AllowFunctionDeclaration)) { + } else if (!allowFunctionDeclaration) { this.raise(Errors.SloppyFunction, { at: this.state.startLoc }); } } return this.parseFunctionStatement( node as Undone, false, - allowDeclaration, + !allowDeclaration, ); - case tt._class: if (!allowDeclaration) this.unexpected(); return this.parseClass( @@ -564,11 +568,11 @@ export default abstract class StatementParser extends ExpressionParser { at: this.state.startLoc, }); } - this.next(); + this.next(); // eat 'async' return this.parseFunctionStatement( node as Undone, true, - allowDeclaration, + !allowDeclaration, ); } } @@ -929,13 +933,14 @@ export default abstract class StatementParser extends ExpressionParser { parseFunctionStatement( this: Parser, node: Undone, - isAsync?: boolean, - declarationPosition?: boolean, + isAsync: boolean, + isHangingDeclaration: boolean, ): N.FunctionDeclaration { - this.next(); + this.next(); // eat 'function' return this.parseFunction( node, - FUNC_STATEMENT | (declarationPosition ? 0 : FUNC_HANGING_STATEMENT), + ParseFunctionFlag.Declaration | + (isHangingDeclaration ? ParseFunctionFlag.HangingDeclaration : 0), isAsync, ); } @@ -1482,29 +1487,33 @@ export default abstract class StatementParser extends ExpressionParser { decl.id = id; } - // Parse a function declaration or literal (depending on the - // `isStatement` parameter). + // Parse a function declaration or expression (depending on the + // ParseFunctionFlag.Declaration flag). parseFunction( this: Parser, node: Undone, - statement: number = FUNC_NO_FLAGS, + flags: ParseFunctionFlag = ParseFunctionFlag.Expression, isAsync: boolean = false, ): T { - const isStatement = statement & FUNC_STATEMENT; - const isHangingStatement = statement & FUNC_HANGING_STATEMENT; - const requireId = !!isStatement && !(statement & FUNC_NULLABLE_ID); + const hangingDeclaration = flags & ParseFunctionFlag.HangingDeclaration; + const isDeclaration = flags & ParseFunctionFlag.Declaration; + const requireId = + !!isDeclaration && !(flags & ParseFunctionFlag.NullableId); this.initFunction(node, isAsync); - if (this.match(tt.star) && isHangingStatement) { - this.raise(Errors.GeneratorInSingleStatementContext, { - at: this.state.startLoc, - }); + if (this.match(tt.star)) { + if (hangingDeclaration) { + this.raise(Errors.GeneratorInSingleStatementContext, { + at: this.state.startLoc, + }); + } + this.next(); // eat * + node.generator = true; } - node.generator = this.eat(tt.star); - if (isStatement) { + if (isDeclaration) { node.id = this.parseFunctionId(requireId); } @@ -1513,7 +1522,7 @@ export default abstract class StatementParser extends ExpressionParser { this.scope.enter(SCOPE_FUNCTION); this.prodParam.enter(functionFlags(isAsync, node.generator)); - if (!isStatement) { + if (!isDeclaration) { node.id = this.parseFunctionId(); } @@ -1526,14 +1535,14 @@ export default abstract class StatementParser extends ExpressionParser { // Parse the function body. this.parseFunctionBodyAndFinish( node, - isStatement ? "FunctionDeclaration" : "FunctionExpression", + isDeclaration ? "FunctionDeclaration" : "FunctionExpression", ); }); this.prodParam.exit(); this.scope.exit(); - if (isStatement && !isHangingStatement) { + if (isDeclaration && !hangingDeclaration) { // We need to register this _after_ parsing the function body // because of TypeScript body-less function declarations, // which shouldn't be added to the scope. @@ -2404,7 +2413,7 @@ export default abstract class StatementParser extends ExpressionParser { return this.parseFunction( expr as Undone, - FUNC_STATEMENT | FUNC_NULLABLE_ID, + ParseFunctionFlag.Declaration | ParseFunctionFlag.NullableId, isAsync, ); } diff --git a/packages/babel-parser/src/plugins/estree.ts b/packages/babel-parser/src/plugins/estree.ts index 64ecc24fdc68..021c423af185 100644 --- a/packages/babel-parser/src/plugins/estree.ts +++ b/packages/babel-parser/src/plugins/estree.ts @@ -120,10 +120,7 @@ export default (superClass: typeof Parser) => // Overrides // ================================== - initFunction( - node: N.BodilessFunctionOrMethodBase, - isAsync?: boolean | null, - ): void { + initFunction(node: N.BodilessFunctionOrMethodBase, isAsync: boolean): void { super.initFunction(node, isAsync); node.expression = false; } diff --git a/packages/babel-parser/src/plugins/typescript/index.ts b/packages/babel-parser/src/plugins/typescript/index.ts index fd3db4f40ba7..6df7d6238085 100644 --- a/packages/babel-parser/src/plugins/typescript/index.ts +++ b/packages/babel-parser/src/plugins/typescript/index.ts @@ -2009,7 +2009,7 @@ export default (superClass: ClassWithMixin) => return super.parseFunctionStatement( nany, /* async */ false, - /* declarationPosition */ true, + /* isHangingDeclaration */ false, ); } From 23ac1c11ae35f0471aaf8aeb96fc169e070d2492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 25 Nov 2022 12:13:27 -0500 Subject: [PATCH 03/13] polish: avoid double error report on sloppy generator function --- packages/babel-parser/src/parser/statement.ts | 4 +- .../invalid-sloppy-function/input.js | 1 + .../invalid-sloppy-function/output.json | 47 +++++++++++++++++++ .../invalid-generator-inside-loop/input.js | 1 + .../invalid-generator-inside-loop/output.json | 47 +++++++++++++++++++ 5 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/es2015/generators/invalid-sloppy-function/input.js create mode 100644 packages/babel-parser/test/fixtures/es2015/generators/invalid-sloppy-function/output.json create mode 100644 packages/babel-parser/test/fixtures/es2017/async-functions/invalid-generator-inside-loop/input.js create mode 100644 packages/babel-parser/test/fixtures/es2017/async-functions/invalid-generator-inside-loop/output.json diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index feb0b97fb700..763975006bee 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -438,7 +438,7 @@ export default abstract class StatementParser extends ExpressionParser { return this.parseFunctionStatement( node as Undone, false, - !allowDeclaration, + !allowDeclaration && allowFunctionDeclaration, ); case tt._class: if (!allowDeclaration) this.unexpected(); @@ -572,7 +572,7 @@ export default abstract class StatementParser extends ExpressionParser { return this.parseFunctionStatement( node as Undone, true, - !allowDeclaration, + !allowDeclaration && allowFunctionDeclaration, ); } } diff --git a/packages/babel-parser/test/fixtures/es2015/generators/invalid-sloppy-function/input.js b/packages/babel-parser/test/fixtures/es2015/generators/invalid-sloppy-function/input.js new file mode 100644 index 000000000000..bdbe1580a1ad --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/generators/invalid-sloppy-function/input.js @@ -0,0 +1 @@ +while (1) function *foo() {} diff --git a/packages/babel-parser/test/fixtures/es2015/generators/invalid-sloppy-function/output.json b/packages/babel-parser/test/fixtures/es2015/generators/invalid-sloppy-function/output.json new file mode 100644 index 000000000000..2bd85706954c --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/generators/invalid-sloppy-function/output.json @@ -0,0 +1,47 @@ +{ + "type": "File", + "start":0,"end":28,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":28,"index":28}}, + "errors": [ + "SyntaxError: In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement. (1:10)" + ], + "program": { + "type": "Program", + "start":0,"end":28,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":28,"index":28}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "WhileStatement", + "start":0,"end":28,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":28,"index":28}}, + "test": { + "type": "NumericLiteral", + "start":7,"end":8,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":8,"index":8}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + }, + "body": { + "type": "FunctionDeclaration", + "start":10,"end":28,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":28,"index":28}}, + "id": { + "type": "Identifier", + "start":20,"end":23,"loc":{"start":{"line":1,"column":20,"index":20},"end":{"line":1,"column":23,"index":23},"identifierName":"foo"}, + "name": "foo" + }, + "generator": true, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start":26,"end":28,"loc":{"start":{"line":1,"column":26,"index":26},"end":{"line":1,"column":28,"index":28}}, + "body": [], + "directives": [] + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/invalid-generator-inside-loop/input.js b/packages/babel-parser/test/fixtures/es2017/async-functions/invalid-generator-inside-loop/input.js new file mode 100644 index 000000000000..4a0ca15103db --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/invalid-generator-inside-loop/input.js @@ -0,0 +1 @@ +while (1) async function *foo(){} diff --git a/packages/babel-parser/test/fixtures/es2017/async-functions/invalid-generator-inside-loop/output.json b/packages/babel-parser/test/fixtures/es2017/async-functions/invalid-generator-inside-loop/output.json new file mode 100644 index 000000000000..9bbdcf0431a7 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2017/async-functions/invalid-generator-inside-loop/output.json @@ -0,0 +1,47 @@ +{ + "type": "File", + "start":0,"end":33,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":33,"index":33}}, + "errors": [ + "SyntaxError: Async functions can only be declared at the top level or inside a block. (1:10)" + ], + "program": { + "type": "Program", + "start":0,"end":33,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":33,"index":33}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "WhileStatement", + "start":0,"end":33,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":33,"index":33}}, + "test": { + "type": "NumericLiteral", + "start":7,"end":8,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":8,"index":8}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + }, + "body": { + "type": "FunctionDeclaration", + "start":10,"end":33,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":33,"index":33}}, + "id": { + "type": "Identifier", + "start":26,"end":29,"loc":{"start":{"line":1,"column":26,"index":26},"end":{"line":1,"column":29,"index":29},"identifierName":"foo"}, + "name": "foo" + }, + "generator": true, + "async": true, + "params": [], + "body": { + "type": "BlockStatement", + "start":31,"end":33,"loc":{"start":{"line":1,"column":31,"index":31},"end":{"line":1,"column":33,"index":33}}, + "body": [], + "directives": [] + } + } + } + ], + "directives": [] + } +} From f5115d6764860a8b070476525327c4edda2ad9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 25 Nov 2022 12:26:10 -0500 Subject: [PATCH 04/13] refactor: move isAsync into ParseFunctionFlag --- .../babel-parser/src/parser/expression.ts | 4 +- packages/babel-parser/src/parser/statement.ts | 48 +++++++++++-------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/packages/babel-parser/src/parser/expression.ts b/packages/babel-parser/src/parser/expression.ts index 3c8f8e20eda3..1e4e338abcff 100644 --- a/packages/babel-parser/src/parser/expression.ts +++ b/packages/babel-parser/src/parser/expression.ts @@ -1304,10 +1304,8 @@ export default abstract class ExpressionParser extends LValParser { if (type === tt._function) { this.resetPreviousNodeTrailingComments(id); this.next(); - return this.parseFunction( + return this.parseAsyncFunctionExpression( this.startNodeAtNode(id), - undefined, - true, ); } else if (tokenIsIdentifier(type)) { // If the next token begins with "=", commit to parsing an async diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index 763975006bee..6061010057ec 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -47,10 +47,11 @@ const loopLabel = { kind: "loop" } as const, switchLabel = { kind: "switch" } as const; export const enum ParseFunctionFlag { - Expression = 0b000, - Declaration = 0b001, - HangingDeclaration = 0b010, - NullableId = 0b100, + Expression = 0b0000, + Declaration = 0b0001, + HangingDeclaration = 0b0010, + NullableId = 0b0100, + Async = 0b1000, } export const enum ParseStatementFlag { @@ -940,8 +941,8 @@ export default abstract class StatementParser extends ExpressionParser { return this.parseFunction( node, ParseFunctionFlag.Declaration | - (isHangingDeclaration ? ParseFunctionFlag.HangingDeclaration : 0), - isAsync, + (isHangingDeclaration ? ParseFunctionFlag.HangingDeclaration : 0) | + (isAsync ? ParseFunctionFlag.Async : 0), ); } @@ -1487,6 +1488,14 @@ export default abstract class StatementParser extends ExpressionParser { decl.id = id; } + // https://tc39.es/ecma262/#prod-AsyncFunctionExpression + parseAsyncFunctionExpression( + this: Parser, + node: Undone, + ): N.FunctionExpression { + return this.parseFunction(node, ParseFunctionFlag.Async); + } + // Parse a function declaration or expression (depending on the // ParseFunctionFlag.Declaration flag). @@ -1494,12 +1503,11 @@ export default abstract class StatementParser extends ExpressionParser { this: Parser, node: Undone, flags: ParseFunctionFlag = ParseFunctionFlag.Expression, - isAsync: boolean = false, ): T { const hangingDeclaration = flags & ParseFunctionFlag.HangingDeclaration; - const isDeclaration = flags & ParseFunctionFlag.Declaration; - const requireId = - !!isDeclaration && !(flags & ParseFunctionFlag.NullableId); + const isDeclaration = !!(flags & ParseFunctionFlag.Declaration); + const requireId = isDeclaration && !(flags & ParseFunctionFlag.NullableId); + const isAsync = !!(flags & ParseFunctionFlag.Async); this.initFunction(node, isAsync); @@ -2403,18 +2411,20 @@ export default abstract class StatementParser extends ExpressionParser { parseExportDefaultExpression(this: Parser): N.Expression | N.Declaration { const expr = this.startNode(); - const isAsync = this.isAsyncFunction(); - - if (this.match(tt._function) || isAsync) { + if (this.match(tt._function)) { this.next(); - if (isAsync) { - this.next(); - } - return this.parseFunction( - expr as Undone, + expr as Undone, ParseFunctionFlag.Declaration | ParseFunctionFlag.NullableId, - isAsync, + ); + } else if (this.isAsyncFunction()) { + this.next(); // eat 'async' + this.next(); // eat 'function' + return this.parseFunction( + expr as Undone, + ParseFunctionFlag.Declaration | + ParseFunctionFlag.NullableId | + ParseFunctionFlag.Async, ); } From 67194e4f9bf2b613b6faee03aa60eee01736cc48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 25 Nov 2022 16:09:31 -0500 Subject: [PATCH 05/13] refactor: simplify hasFollowingIdentifier Also rename the method to hasFollowingBindingAtom --- packages/babel-parser/src/parser/statement.ts | 98 +++++---- .../babel-parser/src/plugins/placeholders.ts | 6 +- .../input.js | 8 + .../output.json | 199 ++++++++++++++++++ 4 files changed, 263 insertions(+), 48 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-in-single-statement-context/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-in-single-statement-context/output.json diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index 6061010057ec..19b160d2abb7 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -291,38 +291,12 @@ export default abstract class StatementParser extends ExpressionParser { if (!this.isContextual(tt._let)) { return false; } - return this.hasFollowingIdentifier(true); + return this.hasFollowingBindingAtom(); } - /** - * Assuming we have seen a contextual `let` / `using`, check if it starts a variable declaration - so that it should be interpreted as a keyword. - * - * @param {number} allowDeclaration When `allowDeclaration` is false, it will return early and _skip_ checking - if the next token after `let` is `{` or a keyword relational operator - * @returns {boolean} - * @memberof StatementParser - */ - hasFollowingIdentifier(allowDeclaration: boolean): boolean { - const next = this.nextTokenStart(); - const nextCh = this.codePointAtPos(next); - // For ambiguous cases, determine if a LexicalDeclaration (or only a - // Statement) is allowed here. If context is not empty then only a Statement - // is allowed. However, `let [` is an explicit negative lookahead for - // ExpressionStatement, so special-case it first. - // Also, `let \` is never valid as an expression so this must be a keyword. - if ( - nextCh === charCodes.backslash || - nextCh === charCodes.leftSquareBracket - ) { - return true; - } - if (!allowDeclaration) return false; - - if (nextCh === charCodes.leftCurlyBrace) return true; - - if (isIdentifierStart(nextCh)) { - keywordRelationalOperator.lastIndex = next; + chStartsBindingIdentifier(ch: number, pos: number) { + if (isIdentifierStart(ch)) { + keywordRelationalOperator.lastIndex = pos; if (keywordRelationalOperator.test(this.input)) { // We have seen `in` or `instanceof` so far, now check if the identfier // ends here @@ -332,8 +306,33 @@ export default abstract class StatementParser extends ExpressionParser { } } return true; + } else if (ch === charCodes.backslash) { + return true; + } else { + return false; } - return false; + } + + chStartsBindingPattern(ch: number) { + return ( + ch === charCodes.leftSquareBracket || ch === charCodes.leftCurlyBrace + ); + } + + /** + * Assuming we have seen a contextual `let` and declaration is allowed, check if it + * starts a variable declaration so that it should be interpreted as a keyword. + * + * @returns {boolean} + * @memberof StatementParser + */ + hasFollowingBindingAtom(): boolean { + const next = this.nextTokenStart(); + const nextCh = this.codePointAtPos(next); + return ( + this.chStartsBindingPattern(nextCh) || + this.chStartsBindingIdentifier(nextCh, next) + ); } startsUsingForOf(): boolean { @@ -464,14 +463,33 @@ export default abstract class StatementParser extends ExpressionParser { case tt._using: // using [no LineTerminator here] BindingList[+Using] - if (this.hasFollowingLineBreak()) { + if ( + this.hasFollowingLineBreak() || + this.state.containsEsc || + !this.hasFollowingBindingAtom() + ) { break; } - // fall through + this.expectPlugin("explicitResourceManagement"); + if (!allowDeclaration) { + this.raise(Errors.UnexpectedLexicalDeclaration, { + at: this.state.startLoc, + }); + } + if (!this.scope.inModule && this.scope.inTopLevel) { + this.raise(Errors.UnexpectedUsingDeclaration, { + at: this.state.startLoc, + }); + } + return this.parseVarStatement( + node as Undone, + "using", + ); case tt._let: if ( this.state.containsEsc || - !this.hasFollowingIdentifier(allowDeclaration) + !allowDeclaration || + !this.hasFollowingBindingAtom() ) { break; } @@ -486,14 +504,6 @@ export default abstract class StatementParser extends ExpressionParser { // fall through case tt._var: { const kind = this.state.value; - if (kind === "using") { - this.expectPlugin("explicitResourceManagement"); - if (!this.scope.inModule && this.scope.inTopLevel) { - this.raise(Errors.UnexpectedUsingDeclaration, { - at: this.state.startLoc, - }); - } - } return this.parseVarStatement( node as Undone, kind, @@ -857,9 +867,9 @@ export default abstract class StatementParser extends ExpressionParser { const startsWithUsing = this.isContextual(tt._using) && !this.hasFollowingLineBreak(); const isLetOrUsing = - (startsWithLet && this.hasFollowingIdentifier(true)) || + (startsWithLet && this.hasFollowingBindingAtom()) || (startsWithUsing && - this.hasFollowingIdentifier(true) && + this.hasFollowingBindingAtom() && this.startsUsingForOf()); if (this.match(tt._var) || this.match(tt._const) || isLetOrUsing) { const initNode = this.startNode(); diff --git a/packages/babel-parser/src/plugins/placeholders.ts b/packages/babel-parser/src/plugins/placeholders.ts index c715efc748b0..f1bf6af2db70 100644 --- a/packages/babel-parser/src/plugins/placeholders.ts +++ b/packages/babel-parser/src/plugins/placeholders.ts @@ -152,13 +152,11 @@ export default (superClass: typeof Parser) => * parser/statement.js * * ============================================================ */ - hasFollowingIdentifier(allowDeclaration: boolean): boolean { - if (super.hasFollowingIdentifier(allowDeclaration)) { + chStartsBindingIdentifier(ch: number, pos: number): boolean { + if (super.chStartsBindingIdentifier(ch, pos)) { return true; } - if (!allowDeclaration) return false; - // Accept "let %%" as the start of "let %%placeholder%%", as though the // placeholder were an identifier. const nextToken = this.lookahead(); diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-in-single-statement-context/input.js b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-in-single-statement-context/input.js new file mode 100644 index 000000000000..c934c14ff1ff --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-in-single-statement-context/input.js @@ -0,0 +1,8 @@ +{ + while (1) using a; + for (;;) using b; + do using c; while (1); + if (1) using d; + with (1) using e; + label: using f; +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-in-single-statement-context/output.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-in-single-statement-context/output.json new file mode 100644 index 000000000000..bd86db5079a7 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-in-single-statement-context/output.json @@ -0,0 +1,199 @@ +{ + "type": "File", + "start":0,"end":125,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":8,"column":1,"index":125}}, + "errors": [ + "SyntaxError: Lexical declaration cannot appear in a single-statement context. (2:12)", + "SyntaxError: Lexical declaration cannot appear in a single-statement context. (3:11)", + "SyntaxError: Lexical declaration cannot appear in a single-statement context. (4:5)", + "SyntaxError: Lexical declaration cannot appear in a single-statement context. (5:9)", + "SyntaxError: Lexical declaration cannot appear in a single-statement context. (6:11)", + "SyntaxError: Lexical declaration cannot appear in a single-statement context. (7:9)" + ], + "program": { + "type": "Program", + "start":0,"end":125,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":8,"column":1,"index":125}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "BlockStatement", + "start":0,"end":125,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":8,"column":1,"index":125}}, + "body": [ + { + "type": "WhileStatement", + "start":4,"end":22,"loc":{"start":{"line":2,"column":2,"index":4},"end":{"line":2,"column":20,"index":22}}, + "test": { + "type": "NumericLiteral", + "start":11,"end":12,"loc":{"start":{"line":2,"column":9,"index":11},"end":{"line":2,"column":10,"index":12}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + }, + "body": { + "type": "VariableDeclaration", + "start":14,"end":22,"loc":{"start":{"line":2,"column":12,"index":14},"end":{"line":2,"column":20,"index":22}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":20,"end":21,"loc":{"start":{"line":2,"column":18,"index":20},"end":{"line":2,"column":19,"index":21}}, + "id": { + "type": "Identifier", + "start":20,"end":21,"loc":{"start":{"line":2,"column":18,"index":20},"end":{"line":2,"column":19,"index":21},"identifierName":"a"}, + "name": "a" + }, + "init": null + } + ], + "kind": "using" + } + }, + { + "type": "ForStatement", + "start":25,"end":42,"loc":{"start":{"line":3,"column":2,"index":25},"end":{"line":3,"column":19,"index":42}}, + "init": null, + "test": null, + "update": null, + "body": { + "type": "VariableDeclaration", + "start":34,"end":42,"loc":{"start":{"line":3,"column":11,"index":34},"end":{"line":3,"column":19,"index":42}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":40,"end":41,"loc":{"start":{"line":3,"column":17,"index":40},"end":{"line":3,"column":18,"index":41}}, + "id": { + "type": "Identifier", + "start":40,"end":41,"loc":{"start":{"line":3,"column":17,"index":40},"end":{"line":3,"column":18,"index":41},"identifierName":"b"}, + "name": "b" + }, + "init": null + } + ], + "kind": "using" + } + }, + { + "type": "DoWhileStatement", + "start":45,"end":67,"loc":{"start":{"line":4,"column":2,"index":45},"end":{"line":4,"column":24,"index":67}}, + "body": { + "type": "VariableDeclaration", + "start":48,"end":56,"loc":{"start":{"line":4,"column":5,"index":48},"end":{"line":4,"column":13,"index":56}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":54,"end":55,"loc":{"start":{"line":4,"column":11,"index":54},"end":{"line":4,"column":12,"index":55}}, + "id": { + "type": "Identifier", + "start":54,"end":55,"loc":{"start":{"line":4,"column":11,"index":54},"end":{"line":4,"column":12,"index":55},"identifierName":"c"}, + "name": "c" + }, + "init": null + } + ], + "kind": "using" + }, + "test": { + "type": "NumericLiteral", + "start":64,"end":65,"loc":{"start":{"line":4,"column":21,"index":64},"end":{"line":4,"column":22,"index":65}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + } + }, + { + "type": "IfStatement", + "start":70,"end":85,"loc":{"start":{"line":5,"column":2,"index":70},"end":{"line":5,"column":17,"index":85}}, + "test": { + "type": "NumericLiteral", + "start":74,"end":75,"loc":{"start":{"line":5,"column":6,"index":74},"end":{"line":5,"column":7,"index":75}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + }, + "consequent": { + "type": "VariableDeclaration", + "start":77,"end":85,"loc":{"start":{"line":5,"column":9,"index":77},"end":{"line":5,"column":17,"index":85}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":83,"end":84,"loc":{"start":{"line":5,"column":15,"index":83},"end":{"line":5,"column":16,"index":84}}, + "id": { + "type": "Identifier", + "start":83,"end":84,"loc":{"start":{"line":5,"column":15,"index":83},"end":{"line":5,"column":16,"index":84},"identifierName":"d"}, + "name": "d" + }, + "init": null + } + ], + "kind": "using" + }, + "alternate": null + }, + { + "type": "WithStatement", + "start":88,"end":105,"loc":{"start":{"line":6,"column":2,"index":88},"end":{"line":6,"column":19,"index":105}}, + "object": { + "type": "NumericLiteral", + "start":94,"end":95,"loc":{"start":{"line":6,"column":8,"index":94},"end":{"line":6,"column":9,"index":95}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + }, + "body": { + "type": "VariableDeclaration", + "start":97,"end":105,"loc":{"start":{"line":6,"column":11,"index":97},"end":{"line":6,"column":19,"index":105}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":103,"end":104,"loc":{"start":{"line":6,"column":17,"index":103},"end":{"line":6,"column":18,"index":104}}, + "id": { + "type": "Identifier", + "start":103,"end":104,"loc":{"start":{"line":6,"column":17,"index":103},"end":{"line":6,"column":18,"index":104},"identifierName":"e"}, + "name": "e" + }, + "init": null + } + ], + "kind": "using" + } + }, + { + "type": "LabeledStatement", + "start":108,"end":123,"loc":{"start":{"line":7,"column":2,"index":108},"end":{"line":7,"column":17,"index":123}}, + "body": { + "type": "VariableDeclaration", + "start":115,"end":123,"loc":{"start":{"line":7,"column":9,"index":115},"end":{"line":7,"column":17,"index":123}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":121,"end":122,"loc":{"start":{"line":7,"column":15,"index":121},"end":{"line":7,"column":16,"index":122}}, + "id": { + "type": "Identifier", + "start":121,"end":122,"loc":{"start":{"line":7,"column":15,"index":121},"end":{"line":7,"column":16,"index":122},"identifierName":"f"}, + "name": "f" + }, + "init": null + } + ], + "kind": "using" + }, + "label": { + "type": "Identifier", + "start":108,"end":113,"loc":{"start":{"line":7,"column":2,"index":108},"end":{"line":7,"column":7,"index":113},"identifierName":"label"}, + "name": "label" + } + } + ], + "directives": [] + } + ], + "directives": [] + } +} From 3d673828b06add08de6bd11dec155bd1a8bbae7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 25 Nov 2022 16:22:27 -0500 Subject: [PATCH 06/13] fix: parse using[foo] as member expression --- packages/babel-parser/src/parser/statement.ts | 17 +- .../input.js | 3 + .../options.json | 3 + .../input.js | 3 + .../options.json | 3 + .../invalid-using-binding-pattern/input.js | 6 - .../invalid-using-binding-pattern/output.json | 204 ------------------ .../valid-using-as-identifier-callee/input.js | 1 + .../output.json | 33 +++ .../input.js | 2 + .../options.json | 3 + .../output.json | 75 +++++++ .../input.js | 2 + .../output.json | 75 +++++++ .../valid-using-as-identifier-for-in/input.js | 3 + .../output.json | 113 +++++++++- .../valid-using-as-identifier-for-of/input.js | 3 + .../output.json | 116 +++++++++- 18 files changed, 449 insertions(+), 216 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-declaration/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-declaration/options.json create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-for-lhs/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-for-lhs/options.json delete mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern/input.js delete mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-callee/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-callee/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member-no-plugin/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member-no-plugin/options.json create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member-no-plugin/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member/output.json diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index 19b160d2abb7..c80f5255c4db 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -335,6 +335,19 @@ export default abstract class StatementParser extends ExpressionParser { ); } + /** + * Assuming we have seen a contextual `using` and declaration is allowed, check if it + * starts a variable declaration so that it should be interpreted as a keyword. + * + * @returns {boolean} + * @memberof StatementParser + */ + hasFollowingBindingIdentifier(): boolean { + const next = this.nextTokenStart(); + const nextCh = this.codePointAtPos(next); + return this.chStartsBindingIdentifier(nextCh, next); + } + startsUsingForOf(): boolean { const lookahead = this.lookahead(); if (lookahead.type === tt._of && !lookahead.containsEsc) { @@ -466,7 +479,7 @@ export default abstract class StatementParser extends ExpressionParser { if ( this.hasFollowingLineBreak() || this.state.containsEsc || - !this.hasFollowingBindingAtom() + !this.hasFollowingBindingIdentifier() ) { break; } @@ -869,7 +882,7 @@ export default abstract class StatementParser extends ExpressionParser { const isLetOrUsing = (startsWithLet && this.hasFollowingBindingAtom()) || (startsWithUsing && - this.hasFollowingBindingAtom() && + this.hasFollowingBindingIdentifier() && this.startsUsingForOf()); if (this.match(tt._var) || this.match(tt._const) || isLetOrUsing) { const initNode = this.startNode(); diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-declaration/input.js b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-declaration/input.js new file mode 100644 index 000000000000..0d19349bc421 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-declaration/input.js @@ -0,0 +1,3 @@ +{ + using { foo } = f(); +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-declaration/options.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-declaration/options.json new file mode 100644 index 000000000000..79cf0896ee66 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-declaration/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Unexpected token (2:16)" +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-for-lhs/input.js b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-for-lhs/input.js new file mode 100644 index 000000000000..4475ddcb6afa --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-for-lhs/input.js @@ -0,0 +1,3 @@ +{ + for (using { qux } of h()); +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-for-lhs/options.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-for-lhs/options.json new file mode 100644 index 000000000000..88c4cb8d5f59 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern-for-lhs/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Unexpected token, expected \")\" (2:24)" +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern/input.js b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern/input.js deleted file mode 100644 index 2a0678a9ce82..000000000000 --- a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern/input.js +++ /dev/null @@ -1,6 +0,0 @@ -{ - using { foo } = f(); - using [ bar ] = g(); - for (using { qux } of h()); - for (using [ quux ] of i()); -} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern/output.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern/output.json deleted file mode 100644 index 7ddc83fa56b5..000000000000 --- a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-using-binding-pattern/output.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "type": "File", - "start":0,"end":110,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":6,"column":1,"index":110}}, - "errors": [ - "SyntaxError: Using declaration cannot have destructuring patterns. (2:8)", - "SyntaxError: Using declaration cannot have destructuring patterns. (3:8)", - "SyntaxError: Using declaration cannot have destructuring patterns. (4:13)", - "SyntaxError: Using declaration cannot have destructuring patterns. (5:13)" - ], - "program": { - "type": "Program", - "start":0,"end":110,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":6,"column":1,"index":110}}, - "sourceType": "script", - "interpreter": null, - "body": [ - { - "type": "BlockStatement", - "start":0,"end":110,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":6,"column":1,"index":110}}, - "body": [ - { - "type": "VariableDeclaration", - "start":4,"end":24,"loc":{"start":{"line":2,"column":2,"index":4},"end":{"line":2,"column":22,"index":24}}, - "declarations": [ - { - "type": "VariableDeclarator", - "start":10,"end":23,"loc":{"start":{"line":2,"column":8,"index":10},"end":{"line":2,"column":21,"index":23}}, - "id": { - "type": "ObjectPattern", - "start":10,"end":17,"loc":{"start":{"line":2,"column":8,"index":10},"end":{"line":2,"column":15,"index":17}}, - "properties": [ - { - "type": "ObjectProperty", - "start":12,"end":15,"loc":{"start":{"line":2,"column":10,"index":12},"end":{"line":2,"column":13,"index":15}}, - "key": { - "type": "Identifier", - "start":12,"end":15,"loc":{"start":{"line":2,"column":10,"index":12},"end":{"line":2,"column":13,"index":15},"identifierName":"foo"}, - "name": "foo" - }, - "computed": false, - "method": false, - "shorthand": true, - "value": { - "type": "Identifier", - "start":12,"end":15,"loc":{"start":{"line":2,"column":10,"index":12},"end":{"line":2,"column":13,"index":15},"identifierName":"foo"}, - "name": "foo" - }, - "extra": { - "shorthand": true - } - } - ] - }, - "init": { - "type": "CallExpression", - "start":20,"end":23,"loc":{"start":{"line":2,"column":18,"index":20},"end":{"line":2,"column":21,"index":23}}, - "callee": { - "type": "Identifier", - "start":20,"end":21,"loc":{"start":{"line":2,"column":18,"index":20},"end":{"line":2,"column":19,"index":21},"identifierName":"f"}, - "name": "f" - }, - "arguments": [] - } - } - ], - "kind": "using" - }, - { - "type": "VariableDeclaration", - "start":27,"end":47,"loc":{"start":{"line":3,"column":2,"index":27},"end":{"line":3,"column":22,"index":47}}, - "declarations": [ - { - "type": "VariableDeclarator", - "start":33,"end":46,"loc":{"start":{"line":3,"column":8,"index":33},"end":{"line":3,"column":21,"index":46}}, - "id": { - "type": "ArrayPattern", - "start":33,"end":40,"loc":{"start":{"line":3,"column":8,"index":33},"end":{"line":3,"column":15,"index":40}}, - "elements": [ - { - "type": "Identifier", - "start":35,"end":38,"loc":{"start":{"line":3,"column":10,"index":35},"end":{"line":3,"column":13,"index":38},"identifierName":"bar"}, - "name": "bar" - } - ] - }, - "init": { - "type": "CallExpression", - "start":43,"end":46,"loc":{"start":{"line":3,"column":18,"index":43},"end":{"line":3,"column":21,"index":46}}, - "callee": { - "type": "Identifier", - "start":43,"end":44,"loc":{"start":{"line":3,"column":18,"index":43},"end":{"line":3,"column":19,"index":44},"identifierName":"g"}, - "name": "g" - }, - "arguments": [] - } - } - ], - "kind": "using" - }, - { - "type": "ForOfStatement", - "start":50,"end":77,"loc":{"start":{"line":4,"column":2,"index":50},"end":{"line":4,"column":29,"index":77}}, - "await": false, - "left": { - "type": "VariableDeclaration", - "start":55,"end":68,"loc":{"start":{"line":4,"column":7,"index":55},"end":{"line":4,"column":20,"index":68}}, - "declarations": [ - { - "type": "VariableDeclarator", - "start":61,"end":68,"loc":{"start":{"line":4,"column":13,"index":61},"end":{"line":4,"column":20,"index":68}}, - "id": { - "type": "ObjectPattern", - "start":61,"end":68,"loc":{"start":{"line":4,"column":13,"index":61},"end":{"line":4,"column":20,"index":68}}, - "properties": [ - { - "type": "ObjectProperty", - "start":63,"end":66,"loc":{"start":{"line":4,"column":15,"index":63},"end":{"line":4,"column":18,"index":66}}, - "key": { - "type": "Identifier", - "start":63,"end":66,"loc":{"start":{"line":4,"column":15,"index":63},"end":{"line":4,"column":18,"index":66},"identifierName":"qux"}, - "name": "qux" - }, - "computed": false, - "method": false, - "shorthand": true, - "value": { - "type": "Identifier", - "start":63,"end":66,"loc":{"start":{"line":4,"column":15,"index":63},"end":{"line":4,"column":18,"index":66},"identifierName":"qux"}, - "name": "qux" - }, - "extra": { - "shorthand": true - } - } - ] - }, - "init": null - } - ], - "kind": "using" - }, - "right": { - "type": "CallExpression", - "start":72,"end":75,"loc":{"start":{"line":4,"column":24,"index":72},"end":{"line":4,"column":27,"index":75}}, - "callee": { - "type": "Identifier", - "start":72,"end":73,"loc":{"start":{"line":4,"column":24,"index":72},"end":{"line":4,"column":25,"index":73},"identifierName":"h"}, - "name": "h" - }, - "arguments": [] - }, - "body": { - "type": "EmptyStatement", - "start":76,"end":77,"loc":{"start":{"line":4,"column":28,"index":76},"end":{"line":4,"column":29,"index":77}} - } - }, - { - "type": "ForOfStatement", - "start":80,"end":108,"loc":{"start":{"line":5,"column":2,"index":80},"end":{"line":5,"column":30,"index":108}}, - "await": false, - "left": { - "type": "VariableDeclaration", - "start":85,"end":99,"loc":{"start":{"line":5,"column":7,"index":85},"end":{"line":5,"column":21,"index":99}}, - "declarations": [ - { - "type": "VariableDeclarator", - "start":91,"end":99,"loc":{"start":{"line":5,"column":13,"index":91},"end":{"line":5,"column":21,"index":99}}, - "id": { - "type": "ArrayPattern", - "start":91,"end":99,"loc":{"start":{"line":5,"column":13,"index":91},"end":{"line":5,"column":21,"index":99}}, - "elements": [ - { - "type": "Identifier", - "start":93,"end":97,"loc":{"start":{"line":5,"column":15,"index":93},"end":{"line":5,"column":19,"index":97},"identifierName":"quux"}, - "name": "quux" - } - ] - }, - "init": null - } - ], - "kind": "using" - }, - "right": { - "type": "CallExpression", - "start":103,"end":106,"loc":{"start":{"line":5,"column":25,"index":103},"end":{"line":5,"column":28,"index":106}}, - "callee": { - "type": "Identifier", - "start":103,"end":104,"loc":{"start":{"line":5,"column":25,"index":103},"end":{"line":5,"column":26,"index":104},"identifierName":"i"}, - "name": "i" - }, - "arguments": [] - }, - "body": { - "type": "EmptyStatement", - "start":107,"end":108,"loc":{"start":{"line":5,"column":29,"index":107},"end":{"line":5,"column":30,"index":108}} - } - } - ], - "directives": [] - } - ], - "directives": [] - } -} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-callee/input.js b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-callee/input.js new file mode 100644 index 000000000000..d6b824f7a7e1 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-callee/input.js @@ -0,0 +1 @@ +using (x); diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-callee/output.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-callee/output.json new file mode 100644 index 000000000000..35a8aaba7f29 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-callee/output.json @@ -0,0 +1,33 @@ +{ + "type": "File", + "start":0,"end":10,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":10,"index":10}}, + "program": { + "type": "Program", + "start":0,"end":10,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":10,"index":10}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":10,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":10,"index":10}}, + "expression": { + "type": "CallExpression", + "start":0,"end":9,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":9,"index":9}}, + "callee": { + "type": "Identifier", + "start":0,"end":5,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":5,"index":5},"identifierName":"using"}, + "name": "using" + }, + "arguments": [ + { + "type": "Identifier", + "start":7,"end":8,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":8,"index":8},"identifierName":"x"}, + "name": "x" + } + ] + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member-no-plugin/input.js b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member-no-plugin/input.js new file mode 100644 index 000000000000..76021eb02570 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member-no-plugin/input.js @@ -0,0 +1,2 @@ +using [x] = 0; +for (using [x] of []); diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member-no-plugin/options.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member-no-plugin/options.json new file mode 100644 index 000000000000..a5b818f53025 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member-no-plugin/options.json @@ -0,0 +1,3 @@ +{ + "plugins": [] +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member-no-plugin/output.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member-no-plugin/output.json new file mode 100644 index 000000000000..ca971e1f9386 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member-no-plugin/output.json @@ -0,0 +1,75 @@ +{ + "type": "File", + "start":0,"end":37,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":22,"index":37}}, + "program": { + "type": "Program", + "start":0,"end":37,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":22,"index":37}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":14,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":14,"index":14}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}}, + "operator": "=", + "left": { + "type": "MemberExpression", + "start":0,"end":9,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":9,"index":9}}, + "object": { + "type": "Identifier", + "start":0,"end":5,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":5,"index":5},"identifierName":"using"}, + "name": "using" + }, + "computed": true, + "property": { + "type": "Identifier", + "start":7,"end":8,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":8,"index":8},"identifierName":"x"}, + "name": "x" + } + }, + "right": { + "type": "NumericLiteral", + "start":12,"end":13,"loc":{"start":{"line":1,"column":12,"index":12},"end":{"line":1,"column":13,"index":13}}, + "extra": { + "rawValue": 0, + "raw": "0" + }, + "value": 0 + } + } + }, + { + "type": "ForOfStatement", + "start":15,"end":37,"loc":{"start":{"line":2,"column":0,"index":15},"end":{"line":2,"column":22,"index":37}}, + "await": false, + "left": { + "type": "MemberExpression", + "start":20,"end":29,"loc":{"start":{"line":2,"column":5,"index":20},"end":{"line":2,"column":14,"index":29}}, + "object": { + "type": "Identifier", + "start":20,"end":25,"loc":{"start":{"line":2,"column":5,"index":20},"end":{"line":2,"column":10,"index":25},"identifierName":"using"}, + "name": "using" + }, + "computed": true, + "property": { + "type": "Identifier", + "start":27,"end":28,"loc":{"start":{"line":2,"column":12,"index":27},"end":{"line":2,"column":13,"index":28},"identifierName":"x"}, + "name": "x" + } + }, + "right": { + "type": "ArrayExpression", + "start":33,"end":35,"loc":{"start":{"line":2,"column":18,"index":33},"end":{"line":2,"column":20,"index":35}}, + "elements": [] + }, + "body": { + "type": "EmptyStatement", + "start":36,"end":37,"loc":{"start":{"line":2,"column":21,"index":36},"end":{"line":2,"column":22,"index":37}} + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member/input.js b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member/input.js new file mode 100644 index 000000000000..76021eb02570 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member/input.js @@ -0,0 +1,2 @@ +using [x] = 0; +for (using [x] of []); diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member/output.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member/output.json new file mode 100644 index 000000000000..ca971e1f9386 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-computed-member/output.json @@ -0,0 +1,75 @@ +{ + "type": "File", + "start":0,"end":37,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":22,"index":37}}, + "program": { + "type": "Program", + "start":0,"end":37,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":22,"index":37}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":14,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":14,"index":14}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}}, + "operator": "=", + "left": { + "type": "MemberExpression", + "start":0,"end":9,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":9,"index":9}}, + "object": { + "type": "Identifier", + "start":0,"end":5,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":5,"index":5},"identifierName":"using"}, + "name": "using" + }, + "computed": true, + "property": { + "type": "Identifier", + "start":7,"end":8,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":8,"index":8},"identifierName":"x"}, + "name": "x" + } + }, + "right": { + "type": "NumericLiteral", + "start":12,"end":13,"loc":{"start":{"line":1,"column":12,"index":12},"end":{"line":1,"column":13,"index":13}}, + "extra": { + "rawValue": 0, + "raw": "0" + }, + "value": 0 + } + } + }, + { + "type": "ForOfStatement", + "start":15,"end":37,"loc":{"start":{"line":2,"column":0,"index":15},"end":{"line":2,"column":22,"index":37}}, + "await": false, + "left": { + "type": "MemberExpression", + "start":20,"end":29,"loc":{"start":{"line":2,"column":5,"index":20},"end":{"line":2,"column":14,"index":29}}, + "object": { + "type": "Identifier", + "start":20,"end":25,"loc":{"start":{"line":2,"column":5,"index":20},"end":{"line":2,"column":10,"index":25},"identifierName":"using"}, + "name": "using" + }, + "computed": true, + "property": { + "type": "Identifier", + "start":27,"end":28,"loc":{"start":{"line":2,"column":12,"index":27},"end":{"line":2,"column":13,"index":28},"identifierName":"x"}, + "name": "x" + } + }, + "right": { + "type": "ArrayExpression", + "start":33,"end":35,"loc":{"start":{"line":2,"column":18,"index":33},"end":{"line":2,"column":20,"index":35}}, + "elements": [] + }, + "body": { + "type": "EmptyStatement", + "start":36,"end":37,"loc":{"start":{"line":2,"column":21,"index":36},"end":{"line":2,"column":22,"index":37}} + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-in/input.js b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-in/input.js index fb5a69740d6b..43f6861ee621 100644 --- a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-in/input.js +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-in/input.js @@ -1 +1,4 @@ for (using in []); +for (using.foo in []); +for (using().foo in []); +for (using``.foo in []); diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-in/output.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-in/output.json index 41f7971dacc5..f96a31002fe3 100644 --- a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-in/output.json +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-in/output.json @@ -1,9 +1,9 @@ { "type": "File", - "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, + "start":0,"end":91,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":4,"column":24,"index":91}}, "program": { "type": "Program", - "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, + "start":0,"end":91,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":4,"column":24,"index":91}}, "sourceType": "script", "interpreter": null, "body": [ @@ -24,6 +24,115 @@ "type": "EmptyStatement", "start":17,"end":18,"loc":{"start":{"line":1,"column":17,"index":17},"end":{"line":1,"column":18,"index":18}} } + }, + { + "type": "ForInStatement", + "start":19,"end":41,"loc":{"start":{"line":2,"column":0,"index":19},"end":{"line":2,"column":22,"index":41}}, + "left": { + "type": "MemberExpression", + "start":24,"end":33,"loc":{"start":{"line":2,"column":5,"index":24},"end":{"line":2,"column":14,"index":33}}, + "object": { + "type": "Identifier", + "start":24,"end":29,"loc":{"start":{"line":2,"column":5,"index":24},"end":{"line":2,"column":10,"index":29},"identifierName":"using"}, + "name": "using" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":30,"end":33,"loc":{"start":{"line":2,"column":11,"index":30},"end":{"line":2,"column":14,"index":33},"identifierName":"foo"}, + "name": "foo" + } + }, + "right": { + "type": "ArrayExpression", + "start":37,"end":39,"loc":{"start":{"line":2,"column":18,"index":37},"end":{"line":2,"column":20,"index":39}}, + "elements": [] + }, + "body": { + "type": "EmptyStatement", + "start":40,"end":41,"loc":{"start":{"line":2,"column":21,"index":40},"end":{"line":2,"column":22,"index":41}} + } + }, + { + "type": "ForInStatement", + "start":42,"end":66,"loc":{"start":{"line":3,"column":0,"index":42},"end":{"line":3,"column":24,"index":66}}, + "left": { + "type": "MemberExpression", + "start":47,"end":58,"loc":{"start":{"line":3,"column":5,"index":47},"end":{"line":3,"column":16,"index":58}}, + "object": { + "type": "CallExpression", + "start":47,"end":54,"loc":{"start":{"line":3,"column":5,"index":47},"end":{"line":3,"column":12,"index":54}}, + "callee": { + "type": "Identifier", + "start":47,"end":52,"loc":{"start":{"line":3,"column":5,"index":47},"end":{"line":3,"column":10,"index":52},"identifierName":"using"}, + "name": "using" + }, + "arguments": [] + }, + "computed": false, + "property": { + "type": "Identifier", + "start":55,"end":58,"loc":{"start":{"line":3,"column":13,"index":55},"end":{"line":3,"column":16,"index":58},"identifierName":"foo"}, + "name": "foo" + } + }, + "right": { + "type": "ArrayExpression", + "start":62,"end":64,"loc":{"start":{"line":3,"column":20,"index":62},"end":{"line":3,"column":22,"index":64}}, + "elements": [] + }, + "body": { + "type": "EmptyStatement", + "start":65,"end":66,"loc":{"start":{"line":3,"column":23,"index":65},"end":{"line":3,"column":24,"index":66}} + } + }, + { + "type": "ForInStatement", + "start":67,"end":91,"loc":{"start":{"line":4,"column":0,"index":67},"end":{"line":4,"column":24,"index":91}}, + "left": { + "type": "MemberExpression", + "start":72,"end":83,"loc":{"start":{"line":4,"column":5,"index":72},"end":{"line":4,"column":16,"index":83}}, + "object": { + "type": "TaggedTemplateExpression", + "start":72,"end":79,"loc":{"start":{"line":4,"column":5,"index":72},"end":{"line":4,"column":12,"index":79}}, + "tag": { + "type": "Identifier", + "start":72,"end":77,"loc":{"start":{"line":4,"column":5,"index":72},"end":{"line":4,"column":10,"index":77},"identifierName":"using"}, + "name": "using" + }, + "quasi": { + "type": "TemplateLiteral", + "start":77,"end":79,"loc":{"start":{"line":4,"column":10,"index":77},"end":{"line":4,"column":12,"index":79}}, + "expressions": [], + "quasis": [ + { + "type": "TemplateElement", + "start":78,"end":78,"loc":{"start":{"line":4,"column":11,"index":78},"end":{"line":4,"column":11,"index":78}}, + "value": { + "raw": "", + "cooked": "" + }, + "tail": true + } + ] + } + }, + "computed": false, + "property": { + "type": "Identifier", + "start":80,"end":83,"loc":{"start":{"line":4,"column":13,"index":80},"end":{"line":4,"column":16,"index":83},"identifierName":"foo"}, + "name": "foo" + } + }, + "right": { + "type": "ArrayExpression", + "start":87,"end":89,"loc":{"start":{"line":4,"column":20,"index":87},"end":{"line":4,"column":22,"index":89}}, + "elements": [] + }, + "body": { + "type": "EmptyStatement", + "start":90,"end":91,"loc":{"start":{"line":4,"column":23,"index":90},"end":{"line":4,"column":24,"index":91}} + } } ], "directives": [] diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-of/input.js b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-of/input.js index 71c7cc667fa1..098c11b05616 100644 --- a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-of/input.js +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-of/input.js @@ -1 +1,4 @@ for (using of of); +for (using.foo of of); +for (using().foo of of); +for (using``.foo of of); diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-of/output.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-of/output.json index ad0e3954d5fd..829fc216a072 100644 --- a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-of/output.json +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/valid-using-as-identifier-for-of/output.json @@ -1,9 +1,9 @@ { "type": "File", - "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, + "start":0,"end":91,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":4,"column":24,"index":91}}, "program": { "type": "Program", - "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, + "start":0,"end":91,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":4,"column":24,"index":91}}, "sourceType": "script", "interpreter": null, "body": [ @@ -25,6 +25,118 @@ "type": "EmptyStatement", "start":17,"end":18,"loc":{"start":{"line":1,"column":17,"index":17},"end":{"line":1,"column":18,"index":18}} } + }, + { + "type": "ForOfStatement", + "start":19,"end":41,"loc":{"start":{"line":2,"column":0,"index":19},"end":{"line":2,"column":22,"index":41}}, + "await": false, + "left": { + "type": "MemberExpression", + "start":24,"end":33,"loc":{"start":{"line":2,"column":5,"index":24},"end":{"line":2,"column":14,"index":33}}, + "object": { + "type": "Identifier", + "start":24,"end":29,"loc":{"start":{"line":2,"column":5,"index":24},"end":{"line":2,"column":10,"index":29},"identifierName":"using"}, + "name": "using" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":30,"end":33,"loc":{"start":{"line":2,"column":11,"index":30},"end":{"line":2,"column":14,"index":33},"identifierName":"foo"}, + "name": "foo" + } + }, + "right": { + "type": "Identifier", + "start":37,"end":39,"loc":{"start":{"line":2,"column":18,"index":37},"end":{"line":2,"column":20,"index":39},"identifierName":"of"}, + "name": "of" + }, + "body": { + "type": "EmptyStatement", + "start":40,"end":41,"loc":{"start":{"line":2,"column":21,"index":40},"end":{"line":2,"column":22,"index":41}} + } + }, + { + "type": "ForOfStatement", + "start":42,"end":66,"loc":{"start":{"line":3,"column":0,"index":42},"end":{"line":3,"column":24,"index":66}}, + "await": false, + "left": { + "type": "MemberExpression", + "start":47,"end":58,"loc":{"start":{"line":3,"column":5,"index":47},"end":{"line":3,"column":16,"index":58}}, + "object": { + "type": "CallExpression", + "start":47,"end":54,"loc":{"start":{"line":3,"column":5,"index":47},"end":{"line":3,"column":12,"index":54}}, + "callee": { + "type": "Identifier", + "start":47,"end":52,"loc":{"start":{"line":3,"column":5,"index":47},"end":{"line":3,"column":10,"index":52},"identifierName":"using"}, + "name": "using" + }, + "arguments": [] + }, + "computed": false, + "property": { + "type": "Identifier", + "start":55,"end":58,"loc":{"start":{"line":3,"column":13,"index":55},"end":{"line":3,"column":16,"index":58},"identifierName":"foo"}, + "name": "foo" + } + }, + "right": { + "type": "Identifier", + "start":62,"end":64,"loc":{"start":{"line":3,"column":20,"index":62},"end":{"line":3,"column":22,"index":64},"identifierName":"of"}, + "name": "of" + }, + "body": { + "type": "EmptyStatement", + "start":65,"end":66,"loc":{"start":{"line":3,"column":23,"index":65},"end":{"line":3,"column":24,"index":66}} + } + }, + { + "type": "ForOfStatement", + "start":67,"end":91,"loc":{"start":{"line":4,"column":0,"index":67},"end":{"line":4,"column":24,"index":91}}, + "await": false, + "left": { + "type": "MemberExpression", + "start":72,"end":83,"loc":{"start":{"line":4,"column":5,"index":72},"end":{"line":4,"column":16,"index":83}}, + "object": { + "type": "TaggedTemplateExpression", + "start":72,"end":79,"loc":{"start":{"line":4,"column":5,"index":72},"end":{"line":4,"column":12,"index":79}}, + "tag": { + "type": "Identifier", + "start":72,"end":77,"loc":{"start":{"line":4,"column":5,"index":72},"end":{"line":4,"column":10,"index":77},"identifierName":"using"}, + "name": "using" + }, + "quasi": { + "type": "TemplateLiteral", + "start":77,"end":79,"loc":{"start":{"line":4,"column":10,"index":77},"end":{"line":4,"column":12,"index":79}}, + "expressions": [], + "quasis": [ + { + "type": "TemplateElement", + "start":78,"end":78,"loc":{"start":{"line":4,"column":11,"index":78},"end":{"line":4,"column":11,"index":78}}, + "value": { + "raw": "", + "cooked": "" + }, + "tail": true + } + ] + } + }, + "computed": false, + "property": { + "type": "Identifier", + "start":80,"end":83,"loc":{"start":{"line":4,"column":13,"index":80},"end":{"line":4,"column":16,"index":83},"identifierName":"foo"}, + "name": "foo" + } + }, + "right": { + "type": "Identifier", + "start":87,"end":89,"loc":{"start":{"line":4,"column":20,"index":87},"end":{"line":4,"column":22,"index":89},"identifierName":"of"}, + "name": "of" + }, + "body": { + "type": "EmptyStatement", + "start":90,"end":91,"loc":{"start":{"line":4,"column":23,"index":90},"end":{"line":4,"column":24,"index":91}} + } } ], "directives": [] From 379c30eb9ea63237a8517a6c3fb0b54c92f6864a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 25 Nov 2022 16:36:28 -0500 Subject: [PATCH 07/13] remove Errors.UsingDeclarationHasBindingPattern --- packages/babel-parser/src/parser/statement.ts | 3 -- .../placeholders/variable/using-init/input.js | 3 ++ .../variable/using-init/options.json | 3 ++ .../variable/using-init/output.json | 51 +++++++++++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/using-init/input.js create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/using-init/options.json create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/using-init/output.json diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index c80f5255c4db..2e36c3da8ad0 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -1501,9 +1501,6 @@ export default abstract class StatementParser extends ExpressionParser { kind: "var" | "let" | "const" | "using", ): void { const id = this.parseBindingAtom(); - if (kind === "using" && id.type !== "Identifier") { - this.raise(Errors.UsingDeclarationHasBindingPattern, { at: id }); - } this.checkLVal(id, { in: { type: "VariableDeclarator" }, binding: kind === "var" ? BIND_VAR : BIND_LEXICAL, diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/using-init/input.js b/packages/babel-parser/test/fixtures/placeholders/variable/using-init/input.js new file mode 100644 index 000000000000..c6acbed198d1 --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/using-init/input.js @@ -0,0 +1,3 @@ +{ + using %%LHS%% = %%RHS%%; +} diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/using-init/options.json b/packages/babel-parser/test/fixtures/placeholders/variable/using-init/options.json new file mode 100644 index 000000000000..bbe8d68f634a --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/using-init/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["placeholders", "explicitResourceManagement"] +} diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/using-init/output.json b/packages/babel-parser/test/fixtures/placeholders/variable/using-init/output.json new file mode 100644 index 000000000000..1aed02862846 --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/using-init/output.json @@ -0,0 +1,51 @@ +{ + "type": "File", + "start":0,"end":30,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":1,"index":30}}, + "program": { + "type": "Program", + "start":0,"end":30,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":1,"index":30}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "BlockStatement", + "start":0,"end":30,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":1,"index":30}}, + "body": [ + { + "type": "VariableDeclaration", + "start":4,"end":28,"loc":{"start":{"line":2,"column":2,"index":4},"end":{"line":2,"column":26,"index":28}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":10,"end":27,"loc":{"start":{"line":2,"column":8,"index":10},"end":{"line":2,"column":25,"index":27}}, + "id": { + "type": "Placeholder", + "start":10,"end":17,"loc":{"start":{"line":2,"column":8,"index":10},"end":{"line":2,"column":15,"index":17}}, + "name": { + "type": "Identifier", + "start":12,"end":15,"loc":{"start":{"line":2,"column":10,"index":12},"end":{"line":2,"column":13,"index":15},"identifierName":"LHS"}, + "name": "LHS" + }, + "expectedNode": "Pattern" + }, + "init": { + "type": "Placeholder", + "start":20,"end":27,"loc":{"start":{"line":2,"column":18,"index":20},"end":{"line":2,"column":25,"index":27}}, + "name": { + "type": "Identifier", + "start":22,"end":25,"loc":{"start":{"line":2,"column":20,"index":22},"end":{"line":2,"column":23,"index":25},"identifierName":"RHS"}, + "name": "RHS" + }, + "expectedNode": "Expression" + } + } + ], + "kind": "using" + } + ], + "directives": [] + } + ], + "directives": [] + } +} From bdb5e31bf0a9899e2e72f8f58b2dcf70c456e63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 25 Nov 2022 17:01:52 -0500 Subject: [PATCH 08/13] improve typings --- packages/babel-parser/src/plugins/flow/index.ts | 3 ++- packages/babel-parser/src/plugins/typescript/index.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/babel-parser/src/plugins/flow/index.ts b/packages/babel-parser/src/plugins/flow/index.ts index f3e3582c548c..175629c84139 100644 --- a/packages/babel-parser/src/plugins/flow/index.ts +++ b/packages/babel-parser/src/plugins/flow/index.ts @@ -28,6 +28,7 @@ import { type BindingTypes, } from "../../util/scopeflags"; import type { ExpressionErrors } from "../../parser/util"; +import type { ParseStatementFlag } from "../../parser/statement"; import { Errors, ParseErrorEnum } from "../../parse-error"; import { cloneIdentifier, type Undone } from "../../parser/node"; @@ -1932,7 +1933,7 @@ export default (superClass: typeof Parser) => } // interfaces and enums - parseStatementLike(flags: number): N.Statement { + parseStatementLike(flags: ParseStatementFlag): N.Statement { // strict mode handling of `interface` since it's a reserved word if (this.state.strict && this.isContextual(tt._interface)) { const lookahead = this.lookahead(); diff --git a/packages/babel-parser/src/plugins/typescript/index.ts b/packages/babel-parser/src/plugins/typescript/index.ts index 6df7d6238085..efcb155e9528 100644 --- a/packages/babel-parser/src/plugins/typescript/index.ts +++ b/packages/babel-parser/src/plugins/typescript/index.ts @@ -35,6 +35,7 @@ import { import TypeScriptScopeHandler from "./scope"; import * as charCodes from "charcodes"; import type { ExpressionErrors } from "../../parser/util"; +import type { ParseStatementFlag } from "../../parser/statement"; import { PARAM } from "../../util/production-parameter"; import { Errors, ParseErrorEnum } from "../../parse-error"; import { cloneIdentifier, type Undone } from "../../parser/node"; @@ -2804,7 +2805,7 @@ export default (superClass: ClassWithMixin) => } parseStatementContent( - flags: number, + flags: ParseStatementFlag, decorators?: N.Decorator[] | null, ): N.Statement { if (this.match(tt._const) && this.isLookaheadContextual("enum")) { From 43ac671fd519130a3f0d54fb9a5dbbdfbc0c0161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 25 Nov 2022 18:19:49 -0500 Subject: [PATCH 09/13] fix: add negative let [ lookahead for expression statement --- packages/babel-parser/src/parser/statement.ts | 22 +++++-- .../let/let-array-with-newline/input.js | 3 + .../let/let-array-with-newline/output.json | 56 ++++++++++++++++++ .../let-with-linebreak-and-escaped/input.js | 2 + .../output.json | 58 +++++++++++++++++++ 5 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/es2015/let/let-array-with-newline/input.js create mode 100644 packages/babel-parser/test/fixtures/es2015/let/let-array-with-newline/output.json create mode 100644 packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-escaped/input.js create mode 100644 packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-escaped/output.json diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index 2e36c3da8ad0..751bf151a9b9 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -498,14 +498,24 @@ export default abstract class StatementParser extends ExpressionParser { node as Undone, "using", ); - case tt._let: - if ( - this.state.containsEsc || - !allowDeclaration || - !this.hasFollowingBindingAtom() - ) { + case tt._let: { + if (this.state.containsEsc) { break; } + // `let [` is an explicit negative lookahead for + // ExpressionStatement, so special-case it first. + const next = this.nextTokenStart(); + const nextCh = this.codePointAtPos(next); + if (nextCh !== charCodes.leftSquareBracket) { + if (!allowDeclaration) break; + if ( + !this.chStartsBindingIdentifier(nextCh, next) && + nextCh !== charCodes.leftCurlyBrace + ) { + break; + } + } + } // fall through case tt._const: { if (!allowDeclaration) { diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-array-with-newline/input.js b/packages/babel-parser/test/fixtures/es2015/let/let-array-with-newline/input.js new file mode 100644 index 000000000000..6706e3aa8204 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-array-with-newline/input.js @@ -0,0 +1,3 @@ +do let +[x] = 0 +while (false); diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-array-with-newline/output.json b/packages/babel-parser/test/fixtures/es2015/let/let-array-with-newline/output.json new file mode 100644 index 000000000000..cfae141adf67 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-array-with-newline/output.json @@ -0,0 +1,56 @@ +{ + "type": "File", + "start":0,"end":29,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":14,"index":29}}, + "errors": [ + "SyntaxError: Lexical declaration cannot appear in a single-statement context. (1:3)" + ], + "program": { + "type": "Program", + "start":0,"end":29,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":14,"index":29}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "DoWhileStatement", + "start":0,"end":29,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":14,"index":29}}, + "body": { + "type": "VariableDeclaration", + "start":3,"end":14,"loc":{"start":{"line":1,"column":3,"index":3},"end":{"line":2,"column":7,"index":14}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":7,"end":14,"loc":{"start":{"line":2,"column":0,"index":7},"end":{"line":2,"column":7,"index":14}}, + "id": { + "type": "ArrayPattern", + "start":7,"end":10,"loc":{"start":{"line":2,"column":0,"index":7},"end":{"line":2,"column":3,"index":10}}, + "elements": [ + { + "type": "Identifier", + "start":8,"end":9,"loc":{"start":{"line":2,"column":1,"index":8},"end":{"line":2,"column":2,"index":9},"identifierName":"x"}, + "name": "x" + } + ] + }, + "init": { + "type": "NumericLiteral", + "start":13,"end":14,"loc":{"start":{"line":2,"column":6,"index":13},"end":{"line":2,"column":7,"index":14}}, + "extra": { + "rawValue": 0, + "raw": "0" + }, + "value": 0 + } + } + ], + "kind": "let" + }, + "test": { + "type": "BooleanLiteral", + "start":22,"end":27,"loc":{"start":{"line":3,"column":7,"index":22},"end":{"line":3,"column":12,"index":27}}, + "value": false + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-escaped/input.js b/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-escaped/input.js new file mode 100644 index 000000000000..280836f24438 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-escaped/input.js @@ -0,0 +1,2 @@ +while (0) let +\u0061 = 1; diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-escaped/output.json b/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-escaped/output.json new file mode 100644 index 000000000000..723b9c36a5c7 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-with-linebreak-and-escaped/output.json @@ -0,0 +1,58 @@ +{ + "type": "File", + "start":0,"end":25,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":11,"index":25}}, + "program": { + "type": "Program", + "start":0,"end":25,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":11,"index":25}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "WhileStatement", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}}, + "test": { + "type": "NumericLiteral", + "start":7,"end":8,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":8,"index":8}}, + "extra": { + "rawValue": 0, + "raw": "0" + }, + "value": 0 + }, + "body": { + "type": "ExpressionStatement", + "start":10,"end":13,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":13,"index":13}}, + "expression": { + "type": "Identifier", + "start":10,"end":13,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":13,"index":13},"identifierName":"let"}, + "name": "let" + } + } + }, + { + "type": "ExpressionStatement", + "start":14,"end":25,"loc":{"start":{"line":2,"column":0,"index":14},"end":{"line":2,"column":11,"index":25}}, + "expression": { + "type": "AssignmentExpression", + "start":14,"end":24,"loc":{"start":{"line":2,"column":0,"index":14},"end":{"line":2,"column":10,"index":24}}, + "operator": "=", + "left": { + "type": "Identifier", + "start":14,"end":20,"loc":{"start":{"line":2,"column":0,"index":14},"end":{"line":2,"column":6,"index":20},"identifierName":"a"}, + "name": "a" + }, + "right": { + "type": "NumericLiteral", + "start":23,"end":24,"loc":{"start":{"line":2,"column":9,"index":23},"end":{"line":2,"column":10,"index":24}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + } + } + } + ], + "directives": [] + } +} From 20dd34cee412505f1c87d9a555a81ba4b8a09eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 25 Nov 2022 22:00:10 -0500 Subject: [PATCH 10/13] fix: disallow labeled function within if --- packages/babel-parser/src/parser/statement.ts | 32 ++++++--- .../input.js | 1 + .../output.json | 66 +++++++++++++++++++ 3 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/core/categorized/invalid-fn-decl-labeled-inside-if/input.js create mode 100644 packages/babel-parser/test/fixtures/core/categorized/invalid-fn-decl-labeled-inside-if/output.json diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index 751bf151a9b9..4bbf561c4b0f 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -56,9 +56,10 @@ export const enum ParseFunctionFlag { export const enum ParseStatementFlag { StatementOnly = 0b000, - AllowImportExport = 0b001, - AllowDeclaration = 0b010, - AllowFunctionDeclaration = 0b100, + AllowImportExport = 0b0001, + AllowDeclaration = 0b0010, + AllowFunctionDeclaration = 0b0100, + AllowLabeledFunction = 0b1000, } const loneSurrogate = /[\uD800-\uDFFF]/u; @@ -364,7 +365,8 @@ export default abstract class StatementParser extends ExpressionParser { return this.parseStatementLike( ParseStatementFlag.AllowImportExport | ParseStatementFlag.AllowDeclaration | - ParseStatementFlag.AllowFunctionDeclaration, + ParseStatementFlag.AllowFunctionDeclaration | + ParseStatementFlag.AllowLabeledFunction, ); } @@ -372,12 +374,19 @@ export default abstract class StatementParser extends ExpressionParser { parseStatementListItem(this: Parser) { return this.parseStatementLike( ParseStatementFlag.AllowDeclaration | - ParseStatementFlag.AllowFunctionDeclaration, + ParseStatementFlag.AllowFunctionDeclaration | + ParseStatementFlag.AllowLabeledFunction, ); } - parseStatementOrFunctionDeclaration(this: Parser) { - return this.parseStatementLike(ParseStatementFlag.AllowFunctionDeclaration); + parseStatementOrFunctionDeclaration( + this: Parser, + disallowLabeledFunction: boolean = false, + ) { + return this.parseStatementLike( + ParseStatementFlag.AllowFunctionDeclaration | + (disallowLabeledFunction ? 0 : ParseStatementFlag.AllowLabeledFunction), + ); } // Parse a single statement. @@ -985,9 +994,12 @@ export default abstract class StatementParser extends ExpressionParser { node.test = this.parseHeaderExpression(); // Annex B.3.3 // https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses - node.consequent = this.parseStatementOrFunctionDeclaration(); + node.consequent = this.parseStatementOrFunctionDeclaration( + // https://tc39.es/ecma262/#sec-if-statement-static-semantics-early-errors + true, + ); node.alternate = this.eat(tt._else) - ? this.parseStatementOrFunctionDeclaration() + ? this.parseStatementOrFunctionDeclaration(true) : null; return this.finishNode(node, "IfStatement"); } @@ -1235,7 +1247,7 @@ export default abstract class StatementParser extends ExpressionParser { }); // https://tc39.es/ecma262/#prod-LabelledItem node.body = - flags & ParseStatementFlag.AllowFunctionDeclaration + flags & ParseStatementFlag.AllowLabeledFunction ? this.parseStatementOrFunctionDeclaration() : this.parseStatement(); diff --git a/packages/babel-parser/test/fixtures/core/categorized/invalid-fn-decl-labeled-inside-if/input.js b/packages/babel-parser/test/fixtures/core/categorized/invalid-fn-decl-labeled-inside-if/input.js new file mode 100644 index 000000000000..bce170562118 --- /dev/null +++ b/packages/babel-parser/test/fixtures/core/categorized/invalid-fn-decl-labeled-inside-if/input.js @@ -0,0 +1 @@ +if (1) foo: bar: function foo(){} diff --git a/packages/babel-parser/test/fixtures/core/categorized/invalid-fn-decl-labeled-inside-if/output.json b/packages/babel-parser/test/fixtures/core/categorized/invalid-fn-decl-labeled-inside-if/output.json new file mode 100644 index 000000000000..f69031a1a76e --- /dev/null +++ b/packages/babel-parser/test/fixtures/core/categorized/invalid-fn-decl-labeled-inside-if/output.json @@ -0,0 +1,66 @@ +{ + "type": "File", + "start":0,"end":33,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":33,"index":33}}, + "errors": [ + "SyntaxError: In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement. (1:17)" + ], + "program": { + "type": "Program", + "start":0,"end":33,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":33,"index":33}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "IfStatement", + "start":0,"end":33,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":33,"index":33}}, + "test": { + "type": "NumericLiteral", + "start":4,"end":5,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":5,"index":5}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + }, + "consequent": { + "type": "LabeledStatement", + "start":7,"end":33,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":33,"index":33}}, + "body": { + "type": "LabeledStatement", + "start":12,"end":33,"loc":{"start":{"line":1,"column":12,"index":12},"end":{"line":1,"column":33,"index":33}}, + "body": { + "type": "FunctionDeclaration", + "start":17,"end":33,"loc":{"start":{"line":1,"column":17,"index":17},"end":{"line":1,"column":33,"index":33}}, + "id": { + "type": "Identifier", + "start":26,"end":29,"loc":{"start":{"line":1,"column":26,"index":26},"end":{"line":1,"column":29,"index":29},"identifierName":"foo"}, + "name": "foo" + }, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start":31,"end":33,"loc":{"start":{"line":1,"column":31,"index":31},"end":{"line":1,"column":33,"index":33}}, + "body": [], + "directives": [] + } + }, + "label": { + "type": "Identifier", + "start":12,"end":15,"loc":{"start":{"line":1,"column":12,"index":12},"end":{"line":1,"column":15,"index":15},"identifierName":"bar"}, + "name": "bar" + } + }, + "label": { + "type": "Identifier", + "start":7,"end":10,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":10,"index":10},"identifierName":"foo"}, + "name": "foo" + } + }, + "alternate": null + } + ], + "directives": [] + } +} From 18eebaadd0f5dbbd8b5db428c809e4e1c746f5dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Fri, 25 Nov 2022 23:20:26 -0500 Subject: [PATCH 11/13] polish: improve error message for `if (0) let x` --- packages/babel-parser/src/parser/statement.ts | 2 +- .../statements/label-invalid-let/output.json | 35 +++++----- .../es2015/uncategorised/328/output.json | 57 +++++++-------- .../invalid-syntax/migrated_0141/output.json | 57 +++++++-------- .../variable/let-context-3/output.json | 69 +++++++++---------- 5 files changed, 99 insertions(+), 121 deletions(-) diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index 4bbf561c4b0f..4974fd345a74 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -516,7 +516,7 @@ export default abstract class StatementParser extends ExpressionParser { const next = this.nextTokenStart(); const nextCh = this.codePointAtPos(next); if (nextCh !== charCodes.leftSquareBracket) { - if (!allowDeclaration) break; + if (!allowDeclaration && this.hasFollowingLineBreak()) break; if ( !this.chStartsBindingIdentifier(nextCh, next) && nextCh !== charCodes.leftCurlyBrace diff --git a/packages/babel-parser/test/fixtures/es2015/statements/label-invalid-let/output.json b/packages/babel-parser/test/fixtures/es2015/statements/label-invalid-let/output.json index e258e527b2a4..1c1c03e60b09 100644 --- a/packages/babel-parser/test/fixtures/es2015/statements/label-invalid-let/output.json +++ b/packages/babel-parser/test/fixtures/es2015/statements/label-invalid-let/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}}, "errors": [ - "SyntaxError: Missing semicolon. (1:8)" + "SyntaxError: Lexical declaration cannot appear in a single-statement context. (1:5)" ], "program": { "type": "Program", @@ -12,30 +12,29 @@ "body": [ { "type": "LabeledStatement", - "start":0,"end":8,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":8,"index":8}}, + "start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}}, "body": { - "type": "ExpressionStatement", - "start":5,"end":8,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":8,"index":8}}, - "expression": { - "type": "Identifier", - "start":5,"end":8,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":8,"index":8},"identifierName":"let"}, - "name": "let" - } + "type": "VariableDeclaration", + "start":5,"end":13,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":13,"index":13}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":9,"end":12,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":12,"index":12}}, + "id": { + "type": "Identifier", + "start":9,"end":12,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":12,"index":12},"identifierName":"bar"}, + "name": "bar" + }, + "init": null + } + ], + "kind": "let" }, "label": { "type": "Identifier", "start":0,"end":3,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":3,"index":3},"identifierName":"foo"}, "name": "foo" } - }, - { - "type": "ExpressionStatement", - "start":9,"end":13,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":13,"index":13}}, - "expression": { - "type": "Identifier", - "start":9,"end":12,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":12,"index":12},"identifierName":"bar"}, - "name": "bar" - } } ], "directives": [] diff --git a/packages/babel-parser/test/fixtures/es2015/uncategorised/328/output.json b/packages/babel-parser/test/fixtures/es2015/uncategorised/328/output.json index fafb3c9a194b..6e5ce4ce5957 100644 --- a/packages/babel-parser/test/fixtures/es2015/uncategorised/328/output.json +++ b/packages/babel-parser/test/fixtures/es2015/uncategorised/328/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, "errors": [ - "SyntaxError: Missing semicolon. (1:10)" + "SyntaxError: Lexical declaration cannot appear in a single-statement context. (1:7)" ], "program": { "type": "Program", @@ -12,7 +12,7 @@ "body": [ { "type": "IfStatement", - "start":0,"end":10,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":10,"index":10}}, + "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, "test": { "type": "NumericLiteral", "start":4,"end":5,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":5,"index":5}}, @@ -23,38 +23,31 @@ "value": 1 }, "consequent": { - "type": "ExpressionStatement", - "start":7,"end":10,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":10,"index":10}}, - "expression": { - "type": "Identifier", - "start":7,"end":10,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":10,"index":10},"identifierName":"let"}, - "name": "let" - } + "type": "VariableDeclaration", + "start":7,"end":18,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":18,"index":18}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":11,"end":17,"loc":{"start":{"line":1,"column":11,"index":11},"end":{"line":1,"column":17,"index":17}}, + "id": { + "type": "Identifier", + "start":11,"end":12,"loc":{"start":{"line":1,"column":11,"index":11},"end":{"line":1,"column":12,"index":12},"identifierName":"x"}, + "name": "x" + }, + "init": { + "type": "NumericLiteral", + "start":15,"end":17,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":17,"index":17}}, + "extra": { + "rawValue": 10, + "raw": "10" + }, + "value": 10 + } + } + ], + "kind": "let" }, "alternate": null - }, - { - "type": "ExpressionStatement", - "start":11,"end":18,"loc":{"start":{"line":1,"column":11,"index":11},"end":{"line":1,"column":18,"index":18}}, - "expression": { - "type": "AssignmentExpression", - "start":11,"end":17,"loc":{"start":{"line":1,"column":11,"index":11},"end":{"line":1,"column":17,"index":17}}, - "operator": "=", - "left": { - "type": "Identifier", - "start":11,"end":12,"loc":{"start":{"line":1,"column":11,"index":11},"end":{"line":1,"column":12,"index":12},"identifierName":"x"}, - "name": "x" - }, - "right": { - "type": "NumericLiteral", - "start":15,"end":17,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":17,"index":17}}, - "extra": { - "rawValue": 10, - "raw": "10" - }, - "value": 10 - } - } } ], "directives": [] diff --git a/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0141/output.json b/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0141/output.json index 2c879716b0c2..6e746ea32646 100644 --- a/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0141/output.json +++ b/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0141/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":19,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":19,"index":19}}, "errors": [ - "SyntaxError: Missing semicolon. (1:12)" + "SyntaxError: Lexical declaration cannot appear in a single-statement context. (1:9)" ], "program": { "type": "Program", @@ -12,45 +12,38 @@ "body": [ { "type": "IfStatement", - "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "start":0,"end":19,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":19,"index":19}}, "test": { "type": "BooleanLiteral", "start":3,"end":7,"loc":{"start":{"line":1,"column":3,"index":3},"end":{"line":1,"column":7,"index":7}}, "value": true }, "consequent": { - "type": "ExpressionStatement", - "start":9,"end":12,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":12,"index":12}}, - "expression": { - "type": "Identifier", - "start":9,"end":12,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":12,"index":12},"identifierName":"let"}, - "name": "let" - } + "type": "VariableDeclaration", + "start":9,"end":19,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":19,"index":19}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":13,"end":18,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":18,"index":18}}, + "id": { + "type": "Identifier", + "start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14},"identifierName":"a"}, + "name": "a" + }, + "init": { + "type": "NumericLiteral", + "start":17,"end":18,"loc":{"start":{"line":1,"column":17,"index":17},"end":{"line":1,"column":18,"index":18}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + } + } + ], + "kind": "let" }, "alternate": null - }, - { - "type": "ExpressionStatement", - "start":13,"end":19,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":19,"index":19}}, - "expression": { - "type": "AssignmentExpression", - "start":13,"end":18,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":18,"index":18}}, - "operator": "=", - "left": { - "type": "Identifier", - "start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14},"identifierName":"a"}, - "name": "a" - }, - "right": { - "type": "NumericLiteral", - "start":17,"end":18,"loc":{"start":{"line":1,"column":17,"index":17},"end":{"line":1,"column":18,"index":18}}, - "extra": { - "rawValue": 1, - "raw": "1" - }, - "value": 1 - } - } } ], "directives": [] diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/let-context-3/output.json b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-3/output.json index 5e9657b671e0..77516aeb3ce6 100644 --- a/packages/babel-parser/test/fixtures/placeholders/variable/let-context-3/output.json +++ b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-3/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":32,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":32,"index":32}}, "errors": [ - "SyntaxError: Missing semicolon. (1:13)" + "SyntaxError: Lexical declaration cannot appear in a single-statement context. (1:10)" ], "program": { "type": "Program", @@ -12,51 +12,44 @@ "body": [ { "type": "IfStatement", - "start":0,"end":13,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":13,"index":13}}, + "start":0,"end":32,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":32,"index":32}}, "test": { "type": "Identifier", "start":4,"end":8,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":8,"index":8},"identifierName":"cond"}, "name": "cond" }, "consequent": { - "type": "ExpressionStatement", - "start":10,"end":13,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":13,"index":13}}, - "expression": { - "type": "Identifier", - "start":10,"end":13,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":13,"index":13},"identifierName":"let"}, - "name": "let" - } + "type": "VariableDeclaration", + "start":10,"end":32,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":32,"index":32}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":14,"end":31,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":31,"index":31}}, + "id": { + "type": "Placeholder", + "start":14,"end":21,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":21,"index":21}}, + "name": { + "type": "Identifier", + "start":16,"end":19,"loc":{"start":{"line":1,"column":16,"index":16},"end":{"line":1,"column":19,"index":19},"identifierName":"LHS"}, + "name": "LHS" + }, + "expectedNode": "Pattern" + }, + "init": { + "type": "Placeholder", + "start":24,"end":31,"loc":{"start":{"line":1,"column":24,"index":24},"end":{"line":1,"column":31,"index":31}}, + "name": { + "type": "Identifier", + "start":26,"end":29,"loc":{"start":{"line":1,"column":26,"index":26},"end":{"line":1,"column":29,"index":29},"identifierName":"RHS"}, + "name": "RHS" + }, + "expectedNode": "Expression" + } + } + ], + "kind": "let" }, "alternate": null - }, - { - "type": "ExpressionStatement", - "start":14,"end":32,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":32,"index":32}}, - "expression": { - "type": "AssignmentExpression", - "start":14,"end":31,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":31,"index":31}}, - "operator": "=", - "left": { - "type": "Placeholder", - "start":14,"end":21,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":21,"index":21}}, - "name": { - "type": "Identifier", - "start":16,"end":19,"loc":{"start":{"line":1,"column":16,"index":16},"end":{"line":1,"column":19,"index":19},"identifierName":"LHS"}, - "name": "LHS" - }, - "expectedNode": "Pattern" - }, - "right": { - "type": "Placeholder", - "start":24,"end":31,"loc":{"start":{"line":1,"column":24,"index":24},"end":{"line":1,"column":31,"index":31}}, - "name": { - "type": "Identifier", - "start":26,"end":29,"loc":{"start":{"line":1,"column":26,"index":26},"end":{"line":1,"column":29,"index":29},"identifierName":"RHS"}, - "name": "RHS" - }, - "expectedNode": "Expression" - } - } } ], "directives": [] From 7b949f461fb7c9aa05327e5060fdcf10461d4df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Sat, 26 Nov 2022 12:40:50 -0500 Subject: [PATCH 12/13] tweak: remove default parameter --- packages/babel-parser/src/parser/statement.ts | 4 ++-- packages/babel-parser/src/plugins/placeholders.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index 4974fd345a74..980323fdde86 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -381,7 +381,7 @@ export default abstract class StatementParser extends ExpressionParser { parseStatementOrFunctionDeclaration( this: Parser, - disallowLabeledFunction: boolean = false, + disallowLabeledFunction: boolean, ) { return this.parseStatementLike( ParseStatementFlag.AllowFunctionDeclaration | @@ -1248,7 +1248,7 @@ export default abstract class StatementParser extends ExpressionParser { // https://tc39.es/ecma262/#prod-LabelledItem node.body = flags & ParseStatementFlag.AllowLabeledFunction - ? this.parseStatementOrFunctionDeclaration() + ? this.parseStatementOrFunctionDeclaration(false) : this.parseStatement(); this.state.labels.pop(); diff --git a/packages/babel-parser/src/plugins/placeholders.ts b/packages/babel-parser/src/plugins/placeholders.ts index f1bf6af2db70..11fd8c911ab7 100644 --- a/packages/babel-parser/src/plugins/placeholders.ts +++ b/packages/babel-parser/src/plugins/placeholders.ts @@ -194,7 +194,7 @@ export default (superClass: typeof Parser) => const stmt: N.LabeledStatement = node; stmt.label = this.finishPlaceholder(expr, "Identifier"); this.next(); - stmt.body = super.parseStatementOrFunctionDeclaration(); + stmt.body = super.parseStatementOrFunctionDeclaration(false); return this.finishNode(stmt, "LabeledStatement"); } From b0f2ceee9b931c4fe37c89ec29ad5dfb9174f314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Sun, 27 Nov 2022 10:53:55 -0500 Subject: [PATCH 13/13] fix: avoid double report for labeled top level using --- packages/babel-parser/src/parser/statement.ts | 11 ++-- .../invalid-labeled-using-binding/input.js | 3 + .../options.json | 3 + .../invalid-labeled-using-binding/output.json | 58 +++++++++++++++++++ .../input.js | 1 + .../options.json | 3 + .../output.json | 51 ++++++++++++++++ 7 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-labeled-using-binding/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-labeled-using-binding/options.json create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-labeled-using-binding/output.json create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-script-top-level-labeled-using-binding/input.js create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-script-top-level-labeled-using-binding/options.json create mode 100644 packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-script-top-level-labeled-using-binding/output.json diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index 980323fdde86..4017819836d6 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -55,7 +55,7 @@ export const enum ParseFunctionFlag { } export const enum ParseStatementFlag { - StatementOnly = 0b000, + StatementOnly = 0b0000, AllowImportExport = 0b0001, AllowDeclaration = 0b0010, AllowFunctionDeclaration = 0b0100, @@ -493,15 +493,14 @@ export default abstract class StatementParser extends ExpressionParser { break; } this.expectPlugin("explicitResourceManagement"); - if (!allowDeclaration) { - this.raise(Errors.UnexpectedLexicalDeclaration, { - at: this.state.startLoc, - }); - } if (!this.scope.inModule && this.scope.inTopLevel) { this.raise(Errors.UnexpectedUsingDeclaration, { at: this.state.startLoc, }); + } else if (!allowDeclaration) { + this.raise(Errors.UnexpectedLexicalDeclaration, { + at: this.state.startLoc, + }); } return this.parseVarStatement( node as Undone, diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-labeled-using-binding/input.js b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-labeled-using-binding/input.js new file mode 100644 index 000000000000..28f36bb915bf --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-labeled-using-binding/input.js @@ -0,0 +1,3 @@ +{ + label: using x = bar(); +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-labeled-using-binding/options.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-labeled-using-binding/options.json new file mode 100644 index 000000000000..b412ffe6712f --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-labeled-using-binding/options.json @@ -0,0 +1,3 @@ +{ + "sourceType": "script" +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-labeled-using-binding/output.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-labeled-using-binding/output.json new file mode 100644 index 000000000000..068bb7949bae --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-labeled-using-binding/output.json @@ -0,0 +1,58 @@ +{ + "type": "File", + "start":0,"end":29,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":1,"index":29}}, + "errors": [ + "SyntaxError: Lexical declaration cannot appear in a single-statement context. (2:9)" + ], + "program": { + "type": "Program", + "start":0,"end":29,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":1,"index":29}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "BlockStatement", + "start":0,"end":29,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":1,"index":29}}, + "body": [ + { + "type": "LabeledStatement", + "start":4,"end":27,"loc":{"start":{"line":2,"column":2,"index":4},"end":{"line":2,"column":25,"index":27}}, + "body": { + "type": "VariableDeclaration", + "start":11,"end":27,"loc":{"start":{"line":2,"column":9,"index":11},"end":{"line":2,"column":25,"index":27}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":17,"end":26,"loc":{"start":{"line":2,"column":15,"index":17},"end":{"line":2,"column":24,"index":26}}, + "id": { + "type": "Identifier", + "start":17,"end":18,"loc":{"start":{"line":2,"column":15,"index":17},"end":{"line":2,"column":16,"index":18},"identifierName":"x"}, + "name": "x" + }, + "init": { + "type": "CallExpression", + "start":21,"end":26,"loc":{"start":{"line":2,"column":19,"index":21},"end":{"line":2,"column":24,"index":26}}, + "callee": { + "type": "Identifier", + "start":21,"end":24,"loc":{"start":{"line":2,"column":19,"index":21},"end":{"line":2,"column":22,"index":24},"identifierName":"bar"}, + "name": "bar" + }, + "arguments": [] + } + } + ], + "kind": "using" + }, + "label": { + "type": "Identifier", + "start":4,"end":9,"loc":{"start":{"line":2,"column":2,"index":4},"end":{"line":2,"column":7,"index":9},"identifierName":"label"}, + "name": "label" + } + } + ], + "directives": [] + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-script-top-level-labeled-using-binding/input.js b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-script-top-level-labeled-using-binding/input.js new file mode 100644 index 000000000000..6bdd84d62d6b --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-script-top-level-labeled-using-binding/input.js @@ -0,0 +1 @@ +label: using x = bar(); diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-script-top-level-labeled-using-binding/options.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-script-top-level-labeled-using-binding/options.json new file mode 100644 index 000000000000..b412ffe6712f --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-script-top-level-labeled-using-binding/options.json @@ -0,0 +1,3 @@ +{ + "sourceType": "script" +} diff --git a/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-script-top-level-labeled-using-binding/output.json b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-script-top-level-labeled-using-binding/output.json new file mode 100644 index 000000000000..b30d658b2b65 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/explicit-resource-management/invalid-script-top-level-labeled-using-binding/output.json @@ -0,0 +1,51 @@ +{ + "type": "File", + "start":0,"end":23,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":23,"index":23}}, + "errors": [ + "SyntaxError: Using declaration cannot appear in the top level when source type is `script`. (1:7)" + ], + "program": { + "type": "Program", + "start":0,"end":23,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":23,"index":23}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "LabeledStatement", + "start":0,"end":23,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":23,"index":23}}, + "body": { + "type": "VariableDeclaration", + "start":7,"end":23,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":23,"index":23}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":13,"end":22,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":22,"index":22}}, + "id": { + "type": "Identifier", + "start":13,"end":14,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":14,"index":14},"identifierName":"x"}, + "name": "x" + }, + "init": { + "type": "CallExpression", + "start":17,"end":22,"loc":{"start":{"line":1,"column":17,"index":17},"end":{"line":1,"column":22,"index":22}}, + "callee": { + "type": "Identifier", + "start":17,"end":20,"loc":{"start":{"line":1,"column":17,"index":17},"end":{"line":1,"column":20,"index":20},"identifierName":"bar"}, + "name": "bar" + }, + "arguments": [] + } + } + ], + "kind": "using" + }, + "label": { + "type": "Identifier", + "start":0,"end":5,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":5,"index":5},"identifierName":"label"}, + "name": "label" + } + } + ], + "directives": [] + } +}