From 3c26949819b8e8a7b1e9e08f5f4a9735135e68ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Sat, 18 Feb 2023 12:49:36 +0100 Subject: [PATCH] Add `annexb: false` parser option to disable Annex B (#15320) --- packages/babel-parser/src/options.ts | 8 ++ .../src/parse-error/standard-errors.ts | 2 + packages/babel-parser/src/parser/statement.ts | 56 +++++--- .../babel-parser/src/plugins/placeholders.ts | 2 +- packages/babel-parser/src/tokenizer/index.ts | 12 +- packages/babel-parser/src/util/scope.ts | 2 + .../disabled/1.1-html-comments-close/input.js | 1 + .../1.1-html-comments-close/options.json | 3 + .../disabled/1.1-html-comments-open/input.js | 1 + .../1.1-html-comments-open/output.json | 44 ++++++ .../input.js | 1 + .../output.json | 57 ++++++++ .../input.js | 1 + .../output.json | 52 ++++++++ .../3.1-sloppy-labeled-functions/input.js | 1 + .../3.1-sloppy-labeled-functions/output.json | 43 ++++++ .../input.js | 4 + .../output.json | 59 ++++++++ .../input.js | 6 + .../output.json | 97 ++++++++++++++ .../disabled/3.3-function-in-if-body/input.js | 2 + .../3.3-function-in-if-body/output.json | 84 ++++++++++++ .../input.js | 2 + .../output.json | 126 ++++++++++++++++++ .../disabled/3.5-for-in-initializer/input.js | 1 + .../3.5-for-in-initializer/output.json | 54 ++++++++ .../fixtures/annex-b/disabled/options.json | 3 + .../enabled/1.1-html-comments-close/input.js | 1 + .../1.1-html-comments-close/output.json | 26 ++++ .../enabled/1.1-html-comments-open/input.js | 1 + .../1.1-html-comments-open/output.json | 36 +++++ .../input.js | 1 + .../output.json | 57 ++++++++ .../input.js | 1 + .../output.json | 49 +++++++ .../3.1-sloppy-labeled-functions/input.js | 1 + .../3.1-sloppy-labeled-functions/output.json | 40 ++++++ .../input.js | 4 + .../output.json | 56 ++++++++ .../input.js | 6 + .../output.json | 94 +++++++++++++ .../enabled/3.3-function-in-if-body/input.js | 2 + .../3.3-function-in-if-body/output.json | 80 +++++++++++ .../input.js | 2 + .../output.json | 125 +++++++++++++++++ .../enabled/3.5-for-in-initializer/input.js | 1 + .../3.5-for-in-initializer/output.json | 51 +++++++ .../fixtures/annex-b/enabled/options.json | 1 + .../core/scope/undecl-export-if/output.json | 3 +- 49 files changed, 1335 insertions(+), 27 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-close/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-close/options.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-open/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-open/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.1-sloppy-labeled-functions-if-body/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.1-sloppy-labeled-functions-if-body/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.1-sloppy-labeled-functions-multiple-labels/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.1-sloppy-labeled-functions-multiple-labels/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.1-sloppy-labeled-functions/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.1-sloppy-labeled-functions/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.2.4-duplicate-function-in-block/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.2.4-duplicate-function-in-block/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.2.5-duplicate-function-in-switch/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.2.5-duplicate-function-in-switch/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.3-function-in-if-body/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.3-function-in-if-body/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.4-var-redeclaration-catch-binding/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.4-var-redeclaration-catch-binding/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.5-for-in-initializer/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/3.5-for-in-initializer/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/disabled/options.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-close/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-close/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-open/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-open/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.1-sloppy-labeled-functions-if-body/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.1-sloppy-labeled-functions-if-body/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.1-sloppy-labeled-functions-multiple-labels/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.1-sloppy-labeled-functions-multiple-labels/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.1-sloppy-labeled-functions/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.1-sloppy-labeled-functions/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.2.4-duplicate-function-in-block/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.2.4-duplicate-function-in-block/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.2.5-duplicate-function-in-switch/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.2.5-duplicate-function-in-switch/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.3-function-in-if-body/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.3-function-in-if-body/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.4-var-redeclaration-catch-binding/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.4-var-redeclaration-catch-binding/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.5-for-in-initializer/input.js create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/3.5-for-in-initializer/output.json create mode 100644 packages/babel-parser/test/fixtures/annex-b/enabled/options.json diff --git a/packages/babel-parser/src/options.ts b/packages/babel-parser/src/options.ts index ec729544d8c3..7abf8ba5d56a 100644 --- a/packages/babel-parser/src/options.ts +++ b/packages/babel-parser/src/options.ts @@ -22,6 +22,7 @@ export type Options = { createParenthesizedExpressions: boolean; errorRecovery: boolean; attachComment: boolean; + annexB: boolean; }; export const defaultOptions: Options = { @@ -74,11 +75,18 @@ export const defaultOptions: Options = { // is vital to preserve comments after transform. If you don't print AST back, // consider set this option to `false` for performance attachComment: true, + // When enabled, the parser will support Annex B syntax. + // https://tc39.es/ecma262/#sec-additional-ecmascript-features-for-web-browsers + annexB: true, }; // Interpret and default an options object export function getOptions(opts?: Options | null): Options { + if (opts && opts.annexB != null && opts.annexB !== false) { + throw new Error("The `annexB` option can only be set to `false`."); + } + const options: any = {}; for (const key of Object.keys(defaultOptions)) { // @ts-expect-error key may not exist in opts diff --git a/packages/babel-parser/src/parse-error/standard-errors.ts b/packages/babel-parser/src/parse-error/standard-errors.ts index 8221a6a53d4f..11550e0e11d7 100644 --- a/packages/babel-parser/src/parse-error/standard-errors.ts +++ b/packages/babel-parser/src/parse-error/standard-errors.ts @@ -226,6 +226,8 @@ export default { RecordNoProto: "'__proto__' is not allowed in Record expressions.", RestTrailingComma: "Unexpected trailing comma after rest element.", SloppyFunction: + "In non-strict mode code, functions can only be declared at top level or inside a block.", + SloppyFunctionAnnexB: "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.", StaticPrototype: "Classes may not have static property named prototype.", SuperNotAllowed: diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index 5a8d1a39194b..6fa0049c5910 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -354,6 +354,8 @@ export default abstract class StatementParser extends ExpressionParser { ParseStatementFlag.AllowImportExport | ParseStatementFlag.AllowDeclaration | ParseStatementFlag.AllowFunctionDeclaration | + // This function is actually also used to parse StatementItems, + // which with Annex B enabled allows labeled functions. ParseStatementFlag.AllowLabeledFunction, ); } @@ -363,18 +365,24 @@ export default abstract class StatementParser extends ExpressionParser { return this.parseStatementLike( ParseStatementFlag.AllowDeclaration | ParseStatementFlag.AllowFunctionDeclaration | - ParseStatementFlag.AllowLabeledFunction, + (!this.options.annexB || this.state.strict + ? 0 + : ParseStatementFlag.AllowLabeledFunction), ); } - parseStatementOrFunctionDeclaration( + parseStatementOrSloppyAnnexBFunctionDeclaration( this: Parser, - disallowLabeledFunction: boolean, + allowLabeledFunction: boolean = false, ) { - return this.parseStatementLike( - ParseStatementFlag.AllowFunctionDeclaration | - (disallowLabeledFunction ? 0 : ParseStatementFlag.AllowLabeledFunction), - ); + let flags: ParseStatementFlag = ParseStatementFlag.StatementOnly; + if (this.options.annexB && !this.state.strict) { + flags |= ParseStatementFlag.AllowFunctionDeclaration; + if (allowLabeledFunction) { + flags |= ParseStatementFlag.AllowLabeledFunction; + } + } + return this.parseStatementLike(flags); } // Parse a single statement. @@ -438,12 +446,15 @@ export default abstract class StatementParser extends ExpressionParser { return this.parseForStatement(node as Undone); case tt._function: if (this.lookaheadCharCode() === charCodes.dot) break; - if (!allowDeclaration) { - if (this.state.strict) { - this.raise(Errors.StrictFunction, { at: this.state.startLoc }); - } else if (!allowFunctionDeclaration) { - this.raise(Errors.SloppyFunction, { at: this.state.startLoc }); - } + if (!allowFunctionDeclaration) { + this.raise( + this.state.strict + ? Errors.StrictFunction + : this.options.annexB + ? Errors.SloppyFunctionAnnexB + : Errors.SloppyFunction, + { at: this.state.startLoc }, + ); } return this.parseFunctionStatement( node as Undone, @@ -981,12 +992,9 @@ 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( - // https://tc39.es/ecma262/#sec-if-statement-static-semantics-early-errors - true, - ); + node.consequent = this.parseStatementOrSloppyAnnexBFunctionDeclaration(); node.alternate = this.eat(tt._else) - ? this.parseStatementOrFunctionDeclaration(true) + ? this.parseStatementOrSloppyAnnexBFunctionDeclaration() : null; return this.finishNode(node, "IfStatement"); } @@ -1074,8 +1082,11 @@ export default abstract class StatementParser extends ExpressionParser { parseCatchClauseParam(this: Parser): N.Pattern { const param = this.parseBindingAtom(); - const simple = param.type === "Identifier"; - this.scope.enter(simple ? SCOPE_SIMPLE_CATCH : 0); + this.scope.enter( + this.options.annexB && param.type === "Identifier" + ? SCOPE_SIMPLE_CATCH + : 0, + ); this.checkLVal(param, { in: { type: "CatchClause" }, binding: BIND_CATCH_PARAM, @@ -1234,7 +1245,7 @@ export default abstract class StatementParser extends ExpressionParser { // https://tc39.es/ecma262/#prod-LabelledItem node.body = flags & ParseStatementFlag.AllowLabeledFunction - ? this.parseStatementOrFunctionDeclaration(false) + ? this.parseStatementOrSloppyAnnexBFunctionDeclaration(true) : this.parseStatement(); this.state.labels.pop(); @@ -1419,6 +1430,7 @@ export default abstract class StatementParser extends ExpressionParser { init.type === "VariableDeclaration" && init.declarations[0].init != null && (!isForIn || + !this.options.annexB || this.state.strict || init.kind !== "var" || init.declarations[0].id.type !== "Identifier") @@ -1626,7 +1638,7 @@ export default abstract class StatementParser extends ExpressionParser { // treatFunctionsAsVar). this.scope.declareName( node.id.name, - this.state.strict || node.generator || node.async + !this.options.annexB || this.state.strict || node.generator || node.async ? this.scope.treatFunctionsAsVar ? BIND_VAR : BIND_LEXICAL diff --git a/packages/babel-parser/src/plugins/placeholders.ts b/packages/babel-parser/src/plugins/placeholders.ts index 6ba9f23f8c8d..7e7eb5b2350c 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(false); + stmt.body = super.parseStatementOrSloppyAnnexBFunctionDeclaration(); return this.finishNode(stmt, "LabeledStatement"); } diff --git a/packages/babel-parser/src/tokenizer/index.ts b/packages/babel-parser/src/tokenizer/index.ts index 7c3fec7f690d..9d3096d942c0 100644 --- a/packages/babel-parser/src/tokenizer/index.ts +++ b/packages/babel-parser/src/tokenizer/index.ts @@ -373,7 +373,11 @@ export default abstract class Tokenizer extends CommentsParser { default: if (isWhitespace(ch)) { ++this.state.pos; - } else if (ch === charCodes.dash && !this.inModule) { + } else if ( + ch === charCodes.dash && + !this.inModule && + this.options.annexB + ) { const pos = this.state.pos; if ( this.input.charCodeAt(pos + 1) === charCodes.dash && @@ -389,7 +393,11 @@ export default abstract class Tokenizer extends CommentsParser { } else { break loop; } - } else if (ch === charCodes.lessThan && !this.inModule) { + } else if ( + ch === charCodes.lessThan && + !this.inModule && + this.options.annexB + ) { const pos = this.state.pos; if ( this.input.charCodeAt(pos + 1) === charCodes.exclamationMark && diff --git a/packages/babel-parser/src/util/scope.ts b/packages/babel-parser/src/util/scope.ts index 516130ab6ab7..8e99ecab04bc 100644 --- a/packages/babel-parser/src/util/scope.ts +++ b/packages/babel-parser/src/util/scope.ts @@ -184,6 +184,8 @@ export default class ScopeHandler { return ( (scope.lexical.has(name) && + // Annex B.3.4 + // https://tc39.es/ecma262/#sec-variablestatements-in-catch-blocks !( scope.flags & SCOPE_SIMPLE_CATCH && scope.lexical.values().next().value === name diff --git a/packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-close/input.js b/packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-close/input.js new file mode 100644 index 000000000000..1e130823c875 --- /dev/null +++ b/packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-close/input.js @@ -0,0 +1 @@ +-->b; diff --git a/packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-close/options.json b/packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-close/options.json new file mode 100644 index 000000000000..10bd5624f0e7 --- /dev/null +++ b/packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-close/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Unexpected token (1:2)" +} diff --git a/packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-open/input.js b/packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-open/input.js new file mode 100644 index 000000000000..f025e0c3b4e4 --- /dev/null +++ b/packages/babel-parser/test/fixtures/annex-b/disabled/1.1-html-comments-open/input.js @@ -0,0 +1 @@ +ab; diff --git a/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-close/output.json b/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-close/output.json new file mode 100644 index 000000000000..b67f5c12631a --- /dev/null +++ b/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-close/output.json @@ -0,0 +1,26 @@ +{ + "type": "File", + "start":0,"end":5,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":5,"index":5}}, + "program": { + "type": "Program", + "start":0,"end":5,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":5,"index":5}}, + "sourceType": "script", + "interpreter": null, + "body": [], + "directives": [], + "innerComments": [ + { + "type": "CommentLine", + "value": "b;", + "start":0,"end":5,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":5,"index":5}} + } + ] + }, + "comments": [ + { + "type": "CommentLine", + "value": "b;", + "start":0,"end":5,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":5,"index":5}} + } + ] +} diff --git a/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-open/input.js b/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-open/input.js new file mode 100644 index 000000000000..f025e0c3b4e4 --- /dev/null +++ b/packages/babel-parser/test/fixtures/annex-b/enabled/1.1-html-comments-open/input.js @@ -0,0 +1 @@ +a