From 6025e6c53680c698d345d7e7a4650af93afb7a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 24 Mar 2021 14:45:04 -0400 Subject: [PATCH 1/2] fix: the LHS in for-of loop should not start with let --- .../babel-parser/src/parser/error-message.js | 1 + packages/babel-parser/src/parser/statement.js | 26 +++- .../for-of/invalid-let-as-identifier/input.js | 3 + .../invalid-let-as-identifier/output.json | 130 ++++++++++++++++++ 4 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 packages/babel-parser/test/fixtures/es2015/for-of/invalid-let-as-identifier/input.js create mode 100644 packages/babel-parser/test/fixtures/es2015/for-of/invalid-let-as-identifier/output.json diff --git a/packages/babel-parser/src/parser/error-message.js b/packages/babel-parser/src/parser/error-message.js index c7b6fb413271..2a46b8b7be00 100644 --- a/packages/babel-parser/src/parser/error-message.js +++ b/packages/babel-parser/src/parser/error-message.js @@ -57,6 +57,7 @@ export const ErrorMessages = Object.freeze({ "'from' is not allowed as an identifier after 'export default'", ForInOfLoopInitializer: "%0 loop variable declaration may not have an initializer", + ForOfLet: "The left-hand side of a for-of loop may not start with 'let'.", GeneratorInSingleStatementContext: "Generators can only be declared at the top level or inside a block", IllegalBreakContinue: "Unsyntactic %0", diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 521b28574bbe..d817d3bdea0c 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -125,6 +125,19 @@ export default class StatementParser extends ExpressionParser { if (!this.isContextual("let")) { return false; } + return this.isLetKeyword(context); + } + + /** + * Assuming we have seen a contextual `let`, check if it starts a variable declaration + so that `left` should be interpreted as a `let` keyword. + * + * @param {?string} context When `context` is non nullish, it will return early and _skip_ checking + if the next token after `let` is `{` or a keyword relational operator + * @returns {boolean} + * @memberof StatementParser + */ + isLetKeyword(context: ?string): boolean { const next = this.nextTokenStart(); const nextCh = this.input.charCodeAt(next); // For ambiguous cases, determine if a LexicalDeclaration (or only a @@ -511,7 +524,8 @@ export default class StatementParser extends ExpressionParser { return this.parseFor(node, null); } - const isLet = this.isLet(); + const startsWithLet = this.isContextual("let"); + const isLet = startsWithLet && this.isLetKeyword(); if (this.match(tt._var) || this.match(tt._const) || isLet) { const init = this.startNode(); const kind = isLet ? "let" : this.state.value; @@ -533,11 +547,13 @@ export default class StatementParser extends ExpressionParser { const refExpressionErrors = new ExpressionErrors(); const init = this.parseExpression(true, refExpressionErrors); - if (this.match(tt._in) || this.isContextual("of")) { + const isForOf = this.isContextual("of"); + if (this.match(tt._in) || isForOf) { + if (isForOf && startsWithLet) { + this.raise(init.start, Errors.ForOfLet); + } this.toAssignable(init, /* isLHS */ true); - const description = this.isContextual("of") - ? "for-of statement" - : "for-in statement"; + const description = isForOf ? "for-of statement" : "for-in statement"; this.checkLVal(init, description); return this.parseForIn(node, init, awaitAt); } else { diff --git a/packages/babel-parser/test/fixtures/es2015/for-of/invalid-let-as-identifier/input.js b/packages/babel-parser/test/fixtures/es2015/for-of/invalid-let-as-identifier/input.js new file mode 100644 index 000000000000..0c22b214977f --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/for-of/invalid-let-as-identifier/input.js @@ -0,0 +1,3 @@ +for (let.foo of []); +for (let().bar of []); +for (let``.bar of []); diff --git a/packages/babel-parser/test/fixtures/es2015/for-of/invalid-let-as-identifier/output.json b/packages/babel-parser/test/fixtures/es2015/for-of/invalid-let-as-identifier/output.json new file mode 100644 index 000000000000..cd35bc895843 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/for-of/invalid-let-as-identifier/output.json @@ -0,0 +1,130 @@ +{ + "type": "File", + "start":0,"end":66,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":22}}, + "errors": [ + "SyntaxError: The left-hand side of a for-of loop may not start with 'let'. (1:5)", + "SyntaxError: The left-hand side of a for-of loop may not start with 'let'. (2:5)", + "SyntaxError: The left-hand side of a for-of loop may not start with 'let'. (3:5)" + ], + "program": { + "type": "Program", + "start":0,"end":66,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":22}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ForOfStatement", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":20}}, + "await": false, + "left": { + "type": "MemberExpression", + "start":5,"end":12,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":12}}, + "object": { + "type": "Identifier", + "start":5,"end":8,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":8},"identifierName":"let"}, + "name": "let" + }, + "computed": false, + "property": { + "type": "Identifier", + "start":9,"end":12,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":12},"identifierName":"foo"}, + "name": "foo" + } + }, + "right": { + "type": "ArrayExpression", + "start":16,"end":18,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":18}}, + "elements": [] + }, + "body": { + "type": "EmptyStatement", + "start":19,"end":20,"loc":{"start":{"line":1,"column":19},"end":{"line":1,"column":20}} + } + }, + { + "type": "ForOfStatement", + "start":21,"end":43,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":22}}, + "await": false, + "left": { + "type": "MemberExpression", + "start":26,"end":35,"loc":{"start":{"line":2,"column":5},"end":{"line":2,"column":14}}, + "object": { + "type": "CallExpression", + "start":26,"end":31,"loc":{"start":{"line":2,"column":5},"end":{"line":2,"column":10}}, + "callee": { + "type": "Identifier", + "start":26,"end":29,"loc":{"start":{"line":2,"column":5},"end":{"line":2,"column":8},"identifierName":"let"}, + "name": "let" + }, + "arguments": [] + }, + "computed": false, + "property": { + "type": "Identifier", + "start":32,"end":35,"loc":{"start":{"line":2,"column":11},"end":{"line":2,"column":14},"identifierName":"bar"}, + "name": "bar" + } + }, + "right": { + "type": "ArrayExpression", + "start":39,"end":41,"loc":{"start":{"line":2,"column":18},"end":{"line":2,"column":20}}, + "elements": [] + }, + "body": { + "type": "EmptyStatement", + "start":42,"end":43,"loc":{"start":{"line":2,"column":21},"end":{"line":2,"column":22}} + } + }, + { + "type": "ForOfStatement", + "start":44,"end":66,"loc":{"start":{"line":3,"column":0},"end":{"line":3,"column":22}}, + "await": false, + "left": { + "type": "MemberExpression", + "start":49,"end":58,"loc":{"start":{"line":3,"column":5},"end":{"line":3,"column":14}}, + "object": { + "type": "TaggedTemplateExpression", + "start":49,"end":54,"loc":{"start":{"line":3,"column":5},"end":{"line":3,"column":10}}, + "tag": { + "type": "Identifier", + "start":49,"end":52,"loc":{"start":{"line":3,"column":5},"end":{"line":3,"column":8},"identifierName":"let"}, + "name": "let" + }, + "quasi": { + "type": "TemplateLiteral", + "start":52,"end":54,"loc":{"start":{"line":3,"column":8},"end":{"line":3,"column":10}}, + "expressions": [], + "quasis": [ + { + "type": "TemplateElement", + "start":53,"end":53,"loc":{"start":{"line":3,"column":9},"end":{"line":3,"column":9}}, + "value": { + "raw": "", + "cooked": "" + }, + "tail": true + } + ] + } + }, + "computed": false, + "property": { + "type": "Identifier", + "start":55,"end":58,"loc":{"start":{"line":3,"column":11},"end":{"line":3,"column":14},"identifierName":"bar"}, + "name": "bar" + } + }, + "right": { + "type": "ArrayExpression", + "start":62,"end":64,"loc":{"start":{"line":3,"column":18},"end":{"line":3,"column":20}}, + "elements": [] + }, + "body": { + "type": "EmptyStatement", + "start":65,"end":66,"loc":{"start":{"line":3,"column":21},"end":{"line":3,"column":22}} + } + } + ], + "directives": [] + } +} \ No newline at end of file From 7faa9841f1c156654bd2be84303f50f48e4aaf46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hu=C3=A1ng=20J=C3=B9nli=C3=A0ng?= Date: Wed, 24 Mar 2021 18:58:07 -0400 Subject: [PATCH 2/2] Update packages/babel-parser/src/parser/statement.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nicolò Ribaudo --- packages/babel-parser/src/parser/statement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index d817d3bdea0c..28813bb6de8b 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -548,7 +548,7 @@ export default class StatementParser extends ExpressionParser { const refExpressionErrors = new ExpressionErrors(); const init = this.parseExpression(true, refExpressionErrors); const isForOf = this.isContextual("of"); - if (this.match(tt._in) || isForOf) { + if (isForOf || this.match(tt._in)) { if (isForOf && startsWithLet) { this.raise(init.start, Errors.ForOfLet); }