From 20664a430e5f201be9273f39fc71abbffb3eaae0 Mon Sep 17 00:00:00 2001 From: Stuart Cook Date: Tue, 2 Feb 2021 12:00:21 +1100 Subject: [PATCH] Permit %%placeholder%% in left-hand-side of a let declaration (#12725) * Permit %%placeholder%% in left-hand-side of a let declaration * Test that "let" followed by modulo is still treated as an identifier * More tests for edge-case handling of "let" with placeholders enabled --- .../babel-parser/src/plugins/placeholders.js | 22 +++++++ .../placeholders/variable/const-init/input.js | 1 + .../variable/const-init/output.json | 44 +++++++++++++ .../variable/let-context-1/input.js | 1 + .../variable/let-context-1/output.json | 32 ++++++++++ .../variable/let-context-2/input.js | 2 + .../variable/let-context-2/output.json | 61 ++++++++++++++++++ .../variable/let-context-3/input.js | 1 + .../variable/let-context-3/output.json | 64 +++++++++++++++++++ .../placeholders/variable/let-init/input.js | 1 + .../variable/let-init/output.json | 44 +++++++++++++ .../placeholders/variable/let-modulo/input.js | 1 + .../variable/let-modulo/output.json | 32 ++++++++++ .../placeholders/variable/var-init/input.js | 1 + .../variable/var-init/output.json | 44 +++++++++++++ packages/babel-template/test/index.js | 24 +++++++ 16 files changed, 375 insertions(+) create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/const-init/input.js create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/const-init/output.json create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/let-context-1/input.js create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/let-context-1/output.json create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/let-context-2/input.js create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/let-context-2/output.json create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/let-context-3/input.js create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/let-context-3/output.json create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/let-init/input.js create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/let-init/output.json create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/let-modulo/input.js create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/let-modulo/output.json create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/var-init/input.js create mode 100644 packages/babel-parser/test/fixtures/placeholders/variable/var-init/output.json diff --git a/packages/babel-parser/src/plugins/placeholders.js b/packages/babel-parser/src/plugins/placeholders.js index 63b99a9b03..3364a74ef1 100644 --- a/packages/babel-parser/src/plugins/placeholders.js +++ b/packages/babel-parser/src/plugins/placeholders.js @@ -150,6 +150,28 @@ export default (superClass: Class): Class => * parser/statement.js * * ============================================================ */ + isLet(context: ?string): boolean { + if (super.isLet(context)) { + return true; + } + + // Replicate the original checks that lead to looking ahead for an + // identifier. + if (!this.isContextual("let")) { + return false; + } + if (context) return false; + + // Accept "let %%" as the start of "let %%placeholder%%", as though the + // placeholder were an identifier. + const nextToken = this.lookahead(); + if (nextToken.type === tt.placeholder) { + return true; + } + + return false; + } + verifyBreakContinue(node: N.BreakStatement | N.ContinueStatement) { if (node.label && node.label.type === "Placeholder") return; super.verifyBreakContinue(...arguments); diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/const-init/input.js b/packages/babel-parser/test/fixtures/placeholders/variable/const-init/input.js new file mode 100644 index 0000000000..7f6e1a9987 --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/const-init/input.js @@ -0,0 +1 @@ +const %%LHS%% = %%RHS%%; diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/const-init/output.json b/packages/babel-parser/test/fixtures/placeholders/variable/const-init/output.json new file mode 100644 index 0000000000..893410a989 --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/const-init/output.json @@ -0,0 +1,44 @@ +{ + "type": "File", + "start":0,"end":24,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":24}}, + "program": { + "type": "Program", + "start":0,"end":24,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":24}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":24,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":24}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":6,"end":23,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":23}}, + "id": { + "type": "Placeholder", + "start":6,"end":13,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":13}}, + "name": { + "type": "Identifier", + "start":8,"end":11,"loc":{"start":{"line":1,"column":8},"end":{"line":1,"column":11},"identifierName":"LHS"}, + "name": "LHS" + }, + "expectedNode": "Pattern" + }, + "init": { + "type": "Placeholder", + "start":16,"end":23,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":23}}, + "name": { + "type": "Identifier", + "start":18,"end":21,"loc":{"start":{"line":1,"column":18},"end":{"line":1,"column":21},"identifierName":"RHS"}, + "name": "RHS" + }, + "expectedNode": "Expression" + } + } + ], + "kind": "const" + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/let-context-1/input.js b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-1/input.js new file mode 100644 index 0000000000..bf6a13277e --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-1/input.js @@ -0,0 +1 @@ +if (cond) let; diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/let-context-1/output.json b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-1/output.json new file mode 100644 index 0000000000..802a167efe --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-1/output.json @@ -0,0 +1,32 @@ +{ + "type": "File", + "start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}}, + "program": { + "type": "Program", + "start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "IfStatement", + "start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}}, + "test": { + "type": "Identifier", + "start":4,"end":8,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":8},"identifierName":"cond"}, + "name": "cond" + }, + "consequent": { + "type": "ExpressionStatement", + "start":10,"end":14,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":14}}, + "expression": { + "type": "Identifier", + "start":10,"end":13,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":13},"identifierName":"let"}, + "name": "let" + } + }, + "alternate": null + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/let-context-2/input.js b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-2/input.js new file mode 100644 index 0000000000..e95ede29ca --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-2/input.js @@ -0,0 +1,2 @@ +if (cond) let +%%LHS%% = %%RHS%% diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/let-context-2/output.json b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-2/output.json new file mode 100644 index 0000000000..7034064509 --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-2/output.json @@ -0,0 +1,61 @@ +{ + "type": "File", + "start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":17}}, + "program": { + "type": "Program", + "start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":2,"column":17}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "IfStatement", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":13}}, + "test": { + "type": "Identifier", + "start":4,"end":8,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":8},"identifierName":"cond"}, + "name": "cond" + }, + "consequent": { + "type": "ExpressionStatement", + "start":10,"end":13,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":13}}, + "expression": { + "type": "Identifier", + "start":10,"end":13,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":13},"identifierName":"let"}, + "name": "let" + } + }, + "alternate": null + }, + { + "type": "ExpressionStatement", + "start":14,"end":31,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":17}}, + "expression": { + "type": "AssignmentExpression", + "start":14,"end":31,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":17}}, + "operator": "=", + "left": { + "type": "Placeholder", + "start":14,"end":21,"loc":{"start":{"line":2,"column":0},"end":{"line":2,"column":7}}, + "name": { + "type": "Identifier", + "start":16,"end":19,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":5},"identifierName":"LHS"}, + "name": "LHS" + }, + "expectedNode": "Pattern" + }, + "right": { + "type": "Placeholder", + "start":24,"end":31,"loc":{"start":{"line":2,"column":10},"end":{"line":2,"column":17}}, + "name": { + "type": "Identifier", + "start":26,"end":29,"loc":{"start":{"line":2,"column":12},"end":{"line":2,"column":15},"identifierName":"RHS"}, + "name": "RHS" + }, + "expectedNode": "Expression" + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/let-context-3/input.js b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-3/input.js new file mode 100644 index 0000000000..fc13dfd5cc --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-3/input.js @@ -0,0 +1 @@ +if (cond) let %%LHS%% = %%RHS%%; 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 new file mode 100644 index 0000000000..d4fef28640 --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/let-context-3/output.json @@ -0,0 +1,64 @@ +{ + "type": "File", + "start":0,"end":32,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":32}}, + "errors": [ + "SyntaxError: Missing semicolon (1:13)" + ], + "program": { + "type": "Program", + "start":0,"end":32,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":32}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "IfStatement", + "start":0,"end":13,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":13}}, + "test": { + "type": "Identifier", + "start":4,"end":8,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":8},"identifierName":"cond"}, + "name": "cond" + }, + "consequent": { + "type": "ExpressionStatement", + "start":10,"end":13,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":13}}, + "expression": { + "type": "Identifier", + "start":10,"end":13,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":13},"identifierName":"let"}, + "name": "let" + } + }, + "alternate": null + }, + { + "type": "ExpressionStatement", + "start":14,"end":32,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":32}}, + "expression": { + "type": "AssignmentExpression", + "start":14,"end":31,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":31}}, + "operator": "=", + "left": { + "type": "Placeholder", + "start":14,"end":21,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":21}}, + "name": { + "type": "Identifier", + "start":16,"end":19,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":19},"identifierName":"LHS"}, + "name": "LHS" + }, + "expectedNode": "Pattern" + }, + "right": { + "type": "Placeholder", + "start":24,"end":31,"loc":{"start":{"line":1,"column":24},"end":{"line":1,"column":31}}, + "name": { + "type": "Identifier", + "start":26,"end":29,"loc":{"start":{"line":1,"column":26},"end":{"line":1,"column":29},"identifierName":"RHS"}, + "name": "RHS" + }, + "expectedNode": "Expression" + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/let-init/input.js b/packages/babel-parser/test/fixtures/placeholders/variable/let-init/input.js new file mode 100644 index 0000000000..09162e5ca4 --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/let-init/input.js @@ -0,0 +1 @@ +let %%LHS%% = %%RHS%%; diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/let-init/output.json b/packages/babel-parser/test/fixtures/placeholders/variable/let-init/output.json new file mode 100644 index 0000000000..332af35fe0 --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/let-init/output.json @@ -0,0 +1,44 @@ +{ + "type": "File", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "program": { + "type": "Program", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":4,"end":21,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":21}}, + "id": { + "type": "Placeholder", + "start":4,"end":11,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":11}}, + "name": { + "type": "Identifier", + "start":6,"end":9,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":9},"identifierName":"LHS"}, + "name": "LHS" + }, + "expectedNode": "Pattern" + }, + "init": { + "type": "Placeholder", + "start":14,"end":21,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":21}}, + "name": { + "type": "Identifier", + "start":16,"end":19,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":19},"identifierName":"RHS"}, + "name": "RHS" + }, + "expectedNode": "Expression" + } + } + ], + "kind": "let" + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/let-modulo/input.js b/packages/babel-parser/test/fixtures/placeholders/variable/let-modulo/input.js new file mode 100644 index 0000000000..3ad1fbcb59 --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/let-modulo/input.js @@ -0,0 +1 @@ +let %LHS; diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/let-modulo/output.json b/packages/babel-parser/test/fixtures/placeholders/variable/let-modulo/output.json new file mode 100644 index 0000000000..5b6933c1ed --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/let-modulo/output.json @@ -0,0 +1,32 @@ +{ + "type": "File", + "start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}}, + "program": { + "type": "Program", + "start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":9,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":9}}, + "expression": { + "type": "BinaryExpression", + "start":0,"end":8,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":8}}, + "left": { + "type": "Identifier", + "start":0,"end":3,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":3},"identifierName":"let"}, + "name": "let" + }, + "operator": "%", + "right": { + "type": "Identifier", + "start":5,"end":8,"loc":{"start":{"line":1,"column":5},"end":{"line":1,"column":8},"identifierName":"LHS"}, + "name": "LHS" + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/var-init/input.js b/packages/babel-parser/test/fixtures/placeholders/variable/var-init/input.js new file mode 100644 index 0000000000..22534dba2f --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/var-init/input.js @@ -0,0 +1 @@ +var %%LHS%% = %%RHS%%; diff --git a/packages/babel-parser/test/fixtures/placeholders/variable/var-init/output.json b/packages/babel-parser/test/fixtures/placeholders/variable/var-init/output.json new file mode 100644 index 0000000000..f184eac5ea --- /dev/null +++ b/packages/babel-parser/test/fixtures/placeholders/variable/var-init/output.json @@ -0,0 +1,44 @@ +{ + "type": "File", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "program": { + "type": "Program", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":22}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":4,"end":21,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":21}}, + "id": { + "type": "Placeholder", + "start":4,"end":11,"loc":{"start":{"line":1,"column":4},"end":{"line":1,"column":11}}, + "name": { + "type": "Identifier", + "start":6,"end":9,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":9},"identifierName":"LHS"}, + "name": "LHS" + }, + "expectedNode": "Pattern" + }, + "init": { + "type": "Placeholder", + "start":14,"end":21,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":21}}, + "name": { + "type": "Identifier", + "start":16,"end":19,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":19},"identifierName":"RHS"}, + "name": "RHS" + }, + "expectedNode": "Expression" + } + } + ], + "kind": "var" + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-template/test/index.js b/packages/babel-template/test/index.js index 7b893d7e46..ffd55c6f10 100644 --- a/packages/babel-template/test/index.js +++ b/packages/babel-template/test/index.js @@ -393,5 +393,29 @@ describe("@babel/template", function () { }); }); }); + + it("works in var declaration", () => { + const output = template("var %%LHS%% = %%RHS%%")({ + LHS: t.identifier("x"), + RHS: t.numericLiteral(7), + }); + expect(generator(output).code).toMatchInlineSnapshot(`"var x = 7;"`); + }); + + it("works in const declaration", () => { + const output = template("const %%LHS%% = %%RHS%%")({ + LHS: t.identifier("x"), + RHS: t.numericLiteral(7), + }); + expect(generator(output).code).toMatchInlineSnapshot(`"const x = 7;"`); + }); + + it("works in let declaration", () => { + const output = template("let %%LHS%% = %%RHS%%")({ + LHS: t.identifier("x"), + RHS: t.numericLiteral(7), + }); + expect(generator(output).code).toMatchInlineSnapshot(`"let x = 7;"`); + }); }); });