Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add annexb: false parser option to disable Annex B #15320

Merged
merged 2 commits into from Feb 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/babel-parser/src/options.ts
Expand Up @@ -22,6 +22,7 @@ export type Options = {
createParenthesizedExpressions: boolean;
errorRecovery: boolean;
attachComment: boolean;
annexB: boolean;
};

export const defaultOptions: Options = {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions packages/babel-parser/src/parse-error/standard-errors.ts
Expand Up @@ -223,6 +223,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:
Expand Down
56 changes: 34 additions & 22 deletions packages/babel-parser/src/parser/statement.ts
Expand Up @@ -352,6 +352,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,
);
}
Expand All @@ -361,18 +363,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.
Expand Down Expand Up @@ -436,12 +444,15 @@ export default abstract class StatementParser extends ExpressionParser {
return this.parseForStatement(node as Undone<N.ForStatement>);
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<N.FunctionDeclaration>,
Expand Down Expand Up @@ -979,12 +990,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");
}
Expand Down Expand Up @@ -1072,8 +1080,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_LEXICAL,
Expand Down Expand Up @@ -1233,7 +1244,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();
Expand Down Expand Up @@ -1418,6 +1429,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")
Expand Down Expand Up @@ -1620,7 +1632,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
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-parser/src/plugins/placeholders.ts
Expand Up @@ -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");
}

Expand Down
12 changes: 10 additions & 2 deletions packages/babel-parser/src/tokenizer/index.ts
Expand Up @@ -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 &&
Expand All @@ -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 &&
Expand Down
2 changes: 2 additions & 0 deletions packages/babel-parser/src/util/scope.ts
Expand Up @@ -184,6 +184,8 @@ export default class ScopeHandler<IScope extends Scope = Scope> {

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
Expand Down
@@ -0,0 +1 @@
-->b;
@@ -0,0 +1,3 @@
{
"throws": "Unexpected token (1:2)"
}
@@ -0,0 +1 @@
a<!--b;
@@ -0,0 +1,44 @@
{
"type": "File",
"start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}},
"program": {
"type": "Program",
"start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}},
"expression": {
"type": "BinaryExpression",
"start":0,"end":6,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":6,"index":6}},
"left": {
"type": "Identifier",
"start":0,"end":1,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":1,"index":1},"identifierName":"a"},
"name": "a"
},
"operator": "<",
"right": {
"type": "UnaryExpression",
"start":2,"end":6,"loc":{"start":{"line":1,"column":2,"index":2},"end":{"line":1,"column":6,"index":6}},
"operator": "!",
"prefix": true,
"argument": {
"type": "UpdateExpression",
"start":3,"end":6,"loc":{"start":{"line":1,"column":3,"index":3},"end":{"line":1,"column":6,"index":6}},
"operator": "--",
"prefix": true,
"argument": {
"type": "Identifier",
"start":5,"end":6,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":6,"index":6},"identifierName":"b"},
"name": "b"
}
}
}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
if (0) x: function f() {}
@@ -0,0 +1,57 @@
{
"type": "File",
"start":0,"end":25,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":25,"index":25}},
"errors": [
"SyntaxError: In non-strict mode code, functions can only be declared at top level or inside a block. (1:10)"
],
"program": {
"type": "Program",
"start":0,"end":25,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":25,"index":25}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "IfStatement",
"start":0,"end":25,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":25,"index":25}},
"test": {
"type": "NumericLiteral",
"start":4,"end":5,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":5,"index":5}},
"extra": {
"rawValue": 0,
"raw": "0"
},
"value": 0
},
"consequent": {
"type": "LabeledStatement",
"start":7,"end":25,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":25,"index":25}},
"body": {
"type": "FunctionDeclaration",
"start":10,"end":25,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":25,"index":25}},
"id": {
"type": "Identifier",
"start":19,"end":20,"loc":{"start":{"line":1,"column":19,"index":19},"end":{"line":1,"column":20,"index":20},"identifierName":"f"},
"name": "f"
},
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start":23,"end":25,"loc":{"start":{"line":1,"column":23,"index":23},"end":{"line":1,"column":25,"index":25}},
"body": [],
"directives": []
}
},
"label": {
"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"
}
},
"alternate": null
}
],
"directives": []
}
}
@@ -0,0 +1 @@
x: y: function fn() {}
@@ -0,0 +1,52 @@
{
"type": "File",
"start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}},
"errors": [
"SyntaxError: In non-strict mode code, functions can only be declared at top level or inside a block. (1:6)"
],
"program": {
"type": "Program",
"start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "LabeledStatement",
"start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}},
"body": {
"type": "LabeledStatement",
"start":3,"end":22,"loc":{"start":{"line":1,"column":3,"index":3},"end":{"line":1,"column":22,"index":22}},
"body": {
"type": "FunctionDeclaration",
"start":6,"end":22,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":22,"index":22}},
"id": {
"type": "Identifier",
"start":15,"end":17,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":17,"index":17},"identifierName":"fn"},
"name": "fn"
},
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start":20,"end":22,"loc":{"start":{"line":1,"column":20,"index":20},"end":{"line":1,"column":22,"index":22}},
"body": [],
"directives": []
}
},
"label": {
"type": "Identifier",
"start":3,"end":4,"loc":{"start":{"line":1,"column":3,"index":3},"end":{"line":1,"column":4,"index":4},"identifierName":"y"},
"name": "y"
}
},
"label": {
"type": "Identifier",
"start":0,"end":1,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":1,"index":1},"identifierName":"x"},
"name": "x"
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
x: function fn() {}