diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index a7daba941ff1..65507e2193de 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -364,6 +364,14 @@ export default class ExpressionParser extends LValParser { return expr; } + parseMaybeUnaryOrPrivate( + refExpressionErrors?: ExpressionErrors, + ): N.Expression | N.PrivateName { + return this.match(tt.privateName) + ? this.parsePrivateName() + : this.parseMaybeUnary(refExpressionErrors); + } + // Start the precedence parser. // https://tc39.es/ecma262/#prod-ShortCircuitExpression @@ -371,7 +379,7 @@ export default class ExpressionParser extends LValParser { const startPos = this.state.start; const startLoc = this.state.startLoc; const potentialArrowAt = this.state.potentialArrowAt; - const expr = this.parseMaybeUnary(refExpressionErrors); + const expr = this.parseMaybeUnaryOrPrivate(refExpressionErrors); if (this.shouldExitDescending(expr, potentialArrowAt)) { return expr; @@ -387,11 +395,31 @@ export default class ExpressionParser extends LValParser { // operator that has a lower precedence than the set it is parsing. parseExprOp( - left: N.Expression, + left: N.Expression | N.PrivateName, leftStartPos: number, leftStartLoc: Position, minPrec: number, ): N.Expression { + if (this.isPrivateName(left)) { + // https://tc39.es/proposal-private-fields-in-in + // RelationalExpression [In, Yield, Await] + // [+In] PrivateIdentifier in ShiftExpression[?Yield, ?Await] + + const value = this.getPrivateNameSV(left); + const { start } = left; + + if ( + // TODO: When migrating to TS, use tt._in.binop! + minPrec >= ((tt._in.binop: any): number) || + !this.prodParam.hasIn || + !this.match(tt._in) + ) { + this.raise(start, Errors.PrivateInExpectedIn, value); + } + + this.classScope.usePrivateName(value, start); + } + let prec = this.state.type.binop; if (prec != null && (this.prodParam.hasIn || !this.match(tt._in))) { if (prec > minPrec) { @@ -504,7 +532,7 @@ export default class ExpressionParser extends LValParser { const startLoc = this.state.startLoc; return this.parseExprOp( - this.parseMaybeUnary(), + this.parseMaybeUnaryOrPrivate(), startPos, startLoc, op.rightAssociative ? prec - 1 : prec, @@ -1213,17 +1241,18 @@ export default class ExpressionParser extends LValParser { } case tt.privateName: { - // https://tc39.es/proposal-private-fields-in-in - // RelationalExpression [In, Yield, Await] - // [+In] PrivateIdentifier in ShiftExpression[?Yield, ?Await] - const { value, start } = this.state; - node = this.parsePrivateName(); - if (this.match(tt._in)) { - this.classScope.usePrivateName(value, start); - } else { - this.raise(start, Errors.PrivateInExpectedIn, value); - } - return node; + // Standalone private names are only allowed in "#x in obj" + // expressions, and they are directly handled by callers of + // parseExprOp. If we reach this, the input is always invalid. + // We can throw a better error message and recover, rather than + // just throwing "Unexpected token" (which is the default + // behavior of this big switch statement). + this.raise( + this.state.start, + Errors.PrivateInExpectedIn, + this.state.value, + ); + return this.parsePrivateName(); } case tt.moduloAssign: @@ -2904,7 +2933,7 @@ export default class ExpressionParser extends LValParser { this.state.inFSharpPipelineDirectBody = true; const ret = this.parseExprOp( - this.parseMaybeUnary(), + this.parseMaybeUnaryOrPrivate(), startPos, startLoc, prec, diff --git a/packages/babel-parser/test/fixtures/es2022/class-private-methods/asi-failure-generator/options.json b/packages/babel-parser/test/fixtures/es2022/class-private-methods/asi-failure-generator/options.json index 8dde42aa3f55..7261fe1a5aff 100644 --- a/packages/babel-parser/test/fixtures/es2022/class-private-methods/asi-failure-generator/options.json +++ b/packages/babel-parser/test/fixtures/es2022/class-private-methods/asi-failure-generator/options.json @@ -1,3 +1,3 @@ { - "throws": "Unexpected token (3:9)" + "throws": "Unexpected token (3:6)" } \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-1/input.js b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-1/input.js new file mode 100644 index 000000000000..45db8afc4da7 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-1/input.js @@ -0,0 +1,7 @@ +class Foo { + #a; + #b; + method() { + #a in #b in c + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-1/output.json b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-1/output.json new file mode 100644 index 000000000000..d58cdfa359bf --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-1/output.json @@ -0,0 +1,122 @@ +{ + "type": "File", + "start":0,"end":60,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":1}}, + "errors": [ + "SyntaxError: Private names are only allowed in property accesses (`obj.#b`) or in `in` expressions (`#b in obj`). (5:10)" + ], + "program": { + "type": "Program", + "start":0,"end":60,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":1}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ClassDeclaration", + "start":0,"end":60,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":1}}, + "id": { + "type": "Identifier", + "start":6,"end":9,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":9},"identifierName":"Foo"}, + "name": "Foo" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start":10,"end":60,"loc":{"start":{"line":1,"column":10},"end":{"line":7,"column":1}}, + "body": [ + { + "type": "ClassPrivateProperty", + "start":14,"end":17,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":5}}, + "static": false, + "key": { + "type": "PrivateName", + "start":14,"end":16,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":4}}, + "id": { + "type": "Identifier", + "start":15,"end":16,"loc":{"start":{"line":2,"column":3},"end":{"line":2,"column":4},"identifierName":"a"}, + "name": "a" + } + }, + "value": null + }, + { + "type": "ClassPrivateProperty", + "start":20,"end":23,"loc":{"start":{"line":3,"column":2},"end":{"line":3,"column":5}}, + "static": false, + "key": { + "type": "PrivateName", + "start":20,"end":22,"loc":{"start":{"line":3,"column":2},"end":{"line":3,"column":4}}, + "id": { + "type": "Identifier", + "start":21,"end":22,"loc":{"start":{"line":3,"column":3},"end":{"line":3,"column":4},"identifierName":"b"}, + "name": "b" + } + }, + "value": null + }, + { + "type": "ClassMethod", + "start":26,"end":58,"loc":{"start":{"line":4,"column":2},"end":{"line":6,"column":3}}, + "static": false, + "key": { + "type": "Identifier", + "start":26,"end":32,"loc":{"start":{"line":4,"column":2},"end":{"line":4,"column":8},"identifierName":"method"}, + "name": "method" + }, + "computed": false, + "kind": "method", + "id": null, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start":35,"end":58,"loc":{"start":{"line":4,"column":11},"end":{"line":6,"column":3}}, + "body": [ + { + "type": "ExpressionStatement", + "start":41,"end":54,"loc":{"start":{"line":5,"column":4},"end":{"line":5,"column":17}}, + "expression": { + "type": "BinaryExpression", + "start":41,"end":54,"loc":{"start":{"line":5,"column":4},"end":{"line":5,"column":17}}, + "left": { + "type": "BinaryExpression", + "start":41,"end":49,"loc":{"start":{"line":5,"column":4},"end":{"line":5,"column":12}}, + "left": { + "type": "PrivateName", + "start":41,"end":43,"loc":{"start":{"line":5,"column":4},"end":{"line":5,"column":6}}, + "id": { + "type": "Identifier", + "start":42,"end":43,"loc":{"start":{"line":5,"column":5},"end":{"line":5,"column":6},"identifierName":"a"}, + "name": "a" + } + }, + "operator": "in", + "right": { + "type": "PrivateName", + "start":47,"end":49,"loc":{"start":{"line":5,"column":10},"end":{"line":5,"column":12}}, + "id": { + "type": "Identifier", + "start":48,"end":49,"loc":{"start":{"line":5,"column":11},"end":{"line":5,"column":12},"identifierName":"b"}, + "name": "b" + } + } + }, + "operator": "in", + "right": { + "type": "Identifier", + "start":53,"end":54,"loc":{"start":{"line":5,"column":16},"end":{"line":5,"column":17},"identifierName":"c"}, + "name": "c" + } + } + } + ], + "directives": [] + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-2/input.js b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-2/input.js new file mode 100644 index 000000000000..d35caba84ed2 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-2/input.js @@ -0,0 +1,6 @@ +class Foo { + #a; + method() { + 1 + #a in b + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-2/output.json b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-2/output.json new file mode 100644 index 000000000000..7998581fe587 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-2/output.json @@ -0,0 +1,107 @@ +{ + "type": "File", + "start":0,"end":52,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":1}}, + "errors": [ + "SyntaxError: Private names are only allowed in property accesses (`obj.#a`) or in `in` expressions (`#a in obj`). (4:8)" + ], + "program": { + "type": "Program", + "start":0,"end":52,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":1}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ClassDeclaration", + "start":0,"end":52,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":1}}, + "id": { + "type": "Identifier", + "start":6,"end":9,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":9},"identifierName":"Foo"}, + "name": "Foo" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start":10,"end":52,"loc":{"start":{"line":1,"column":10},"end":{"line":6,"column":1}}, + "body": [ + { + "type": "ClassPrivateProperty", + "start":14,"end":17,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":5}}, + "static": false, + "key": { + "type": "PrivateName", + "start":14,"end":16,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":4}}, + "id": { + "type": "Identifier", + "start":15,"end":16,"loc":{"start":{"line":2,"column":3},"end":{"line":2,"column":4},"identifierName":"a"}, + "name": "a" + } + }, + "value": null + }, + { + "type": "ClassMethod", + "start":20,"end":50,"loc":{"start":{"line":3,"column":2},"end":{"line":5,"column":3}}, + "static": false, + "key": { + "type": "Identifier", + "start":20,"end":26,"loc":{"start":{"line":3,"column":2},"end":{"line":3,"column":8},"identifierName":"method"}, + "name": "method" + }, + "computed": false, + "kind": "method", + "id": null, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start":29,"end":50,"loc":{"start":{"line":3,"column":11},"end":{"line":5,"column":3}}, + "body": [ + { + "type": "ExpressionStatement", + "start":35,"end":46,"loc":{"start":{"line":4,"column":4},"end":{"line":4,"column":15}}, + "expression": { + "type": "BinaryExpression", + "start":35,"end":46,"loc":{"start":{"line":4,"column":4},"end":{"line":4,"column":15}}, + "left": { + "type": "BinaryExpression", + "start":35,"end":41,"loc":{"start":{"line":4,"column":4},"end":{"line":4,"column":10}}, + "left": { + "type": "NumericLiteral", + "start":35,"end":36,"loc":{"start":{"line":4,"column":4},"end":{"line":4,"column":5}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + }, + "operator": "+", + "right": { + "type": "PrivateName", + "start":39,"end":41,"loc":{"start":{"line":4,"column":8},"end":{"line":4,"column":10}}, + "id": { + "type": "Identifier", + "start":40,"end":41,"loc":{"start":{"line":4,"column":9},"end":{"line":4,"column":10},"identifierName":"a"}, + "name": "a" + } + } + }, + "operator": "in", + "right": { + "type": "Identifier", + "start":45,"end":46,"loc":{"start":{"line":4,"column":14},"end":{"line":4,"column":15},"identifierName":"b"}, + "name": "b" + } + } + } + ], + "directives": [] + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-3/input.js b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-3/input.js new file mode 100644 index 000000000000..d91e022aba10 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-3/input.js @@ -0,0 +1,7 @@ +class Foo { + #a; + + method() { + for (var x = #a in y); + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-3/output.json b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-3/output.json new file mode 100644 index 000000000000..e48404c3c5e5 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-3/output.json @@ -0,0 +1,109 @@ +{ + "type": "File", + "start":0,"end":64,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":1}}, + "errors": [ + "SyntaxError: Private names are only allowed in property accesses (`obj.#a`) or in `in` expressions (`#a in obj`). (5:17)", + "SyntaxError: 'for-in' loop variable declaration may not have an initializer. (5:9)" + ], + "program": { + "type": "Program", + "start":0,"end":64,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":1}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ClassDeclaration", + "start":0,"end":64,"loc":{"start":{"line":1,"column":0},"end":{"line":7,"column":1}}, + "id": { + "type": "Identifier", + "start":6,"end":9,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":9},"identifierName":"Foo"}, + "name": "Foo" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start":10,"end":64,"loc":{"start":{"line":1,"column":10},"end":{"line":7,"column":1}}, + "body": [ + { + "type": "ClassPrivateProperty", + "start":14,"end":17,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":5}}, + "static": false, + "key": { + "type": "PrivateName", + "start":14,"end":16,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":4}}, + "id": { + "type": "Identifier", + "start":15,"end":16,"loc":{"start":{"line":2,"column":3},"end":{"line":2,"column":4},"identifierName":"a"}, + "name": "a" + } + }, + "value": null + }, + { + "type": "ClassMethod", + "start":21,"end":62,"loc":{"start":{"line":4,"column":2},"end":{"line":6,"column":3}}, + "static": false, + "key": { + "type": "Identifier", + "start":21,"end":27,"loc":{"start":{"line":4,"column":2},"end":{"line":4,"column":8},"identifierName":"method"}, + "name": "method" + }, + "computed": false, + "kind": "method", + "id": null, + "generator": false, + "async": false, + "params": [], + "body": { + "type": "BlockStatement", + "start":30,"end":62,"loc":{"start":{"line":4,"column":11},"end":{"line":6,"column":3}}, + "body": [ + { + "type": "ForInStatement", + "start":36,"end":58,"loc":{"start":{"line":5,"column":4},"end":{"line":5,"column":26}}, + "left": { + "type": "VariableDeclaration", + "start":41,"end":51,"loc":{"start":{"line":5,"column":9},"end":{"line":5,"column":19}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":45,"end":51,"loc":{"start":{"line":5,"column":13},"end":{"line":5,"column":19}}, + "id": { + "type": "Identifier", + "start":45,"end":46,"loc":{"start":{"line":5,"column":13},"end":{"line":5,"column":14},"identifierName":"x"}, + "name": "x" + }, + "init": { + "type": "PrivateName", + "start":49,"end":51,"loc":{"start":{"line":5,"column":17},"end":{"line":5,"column":19}}, + "id": { + "type": "Identifier", + "start":50,"end":51,"loc":{"start":{"line":5,"column":18},"end":{"line":5,"column":19},"identifierName":"a"}, + "name": "a" + } + } + } + ], + "kind": "var" + }, + "right": { + "type": "Identifier", + "start":55,"end":56,"loc":{"start":{"line":5,"column":23},"end":{"line":5,"column":24},"identifierName":"y"}, + "name": "y" + }, + "body": { + "type": "EmptyStatement", + "start":57,"end":58,"loc":{"start":{"line":5,"column":25},"end":{"line":5,"column":26}} + } + } + ], + "directives": [] + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-4/input.js b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-4/input.js new file mode 100644 index 000000000000..6248ead0cb4b --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-4/input.js @@ -0,0 +1,6 @@ +class C { + #x; + async test() { + await #x in this + } +} diff --git a/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-4/output.json b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-4/output.json new file mode 100644 index 000000000000..7b5475e5eacb --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/private-in/invalid-private-followed-by-in-4/output.json @@ -0,0 +1,96 @@ +{ + "type": "File", + "start":0,"end":59,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":1}}, + "errors": [ + "SyntaxError: Private names are only allowed in property accesses (`obj.#x`) or in `in` expressions (`#x in obj`). (4:10)" + ], + "program": { + "type": "Program", + "start":0,"end":59,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":1}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ClassDeclaration", + "start":0,"end":59,"loc":{"start":{"line":1,"column":0},"end":{"line":6,"column":1}}, + "id": { + "type": "Identifier", + "start":6,"end":7,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":7},"identifierName":"C"}, + "name": "C" + }, + "superClass": null, + "body": { + "type": "ClassBody", + "start":8,"end":59,"loc":{"start":{"line":1,"column":8},"end":{"line":6,"column":1}}, + "body": [ + { + "type": "ClassPrivateProperty", + "start":12,"end":15,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":5}}, + "static": false, + "key": { + "type": "PrivateName", + "start":12,"end":14,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":4}}, + "id": { + "type": "Identifier", + "start":13,"end":14,"loc":{"start":{"line":2,"column":3},"end":{"line":2,"column":4},"identifierName":"x"}, + "name": "x" + } + }, + "value": null + }, + { + "type": "ClassMethod", + "start":18,"end":57,"loc":{"start":{"line":3,"column":2},"end":{"line":5,"column":3}}, + "static": false, + "key": { + "type": "Identifier", + "start":24,"end":28,"loc":{"start":{"line":3,"column":8},"end":{"line":3,"column":12},"identifierName":"test"}, + "name": "test" + }, + "computed": false, + "kind": "method", + "id": null, + "generator": false, + "async": true, + "params": [], + "body": { + "type": "BlockStatement", + "start":31,"end":57,"loc":{"start":{"line":3,"column":15},"end":{"line":5,"column":3}}, + "body": [ + { + "type": "ExpressionStatement", + "start":37,"end":53,"loc":{"start":{"line":4,"column":4},"end":{"line":4,"column":20}}, + "expression": { + "type": "BinaryExpression", + "start":37,"end":53,"loc":{"start":{"line":4,"column":4},"end":{"line":4,"column":20}}, + "left": { + "type": "AwaitExpression", + "start":37,"end":45,"loc":{"start":{"line":4,"column":4},"end":{"line":4,"column":12}}, + "argument": { + "type": "PrivateName", + "start":43,"end":45,"loc":{"start":{"line":4,"column":10},"end":{"line":4,"column":12}}, + "id": { + "type": "Identifier", + "start":44,"end":45,"loc":{"start":{"line":4,"column":11},"end":{"line":4,"column":12},"identifierName":"x"}, + "name": "x" + } + } + }, + "operator": "in", + "right": { + "type": "ThisExpression", + "start":49,"end":53,"loc":{"start":{"line":4,"column":16},"end":{"line":4,"column":20}} + } + } + } + ], + "directives": [] + } + } + ] + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/scripts/parser-tests/test262/allowlist.txt b/scripts/parser-tests/test262/allowlist.txt index ee02d35010e6..8fd07d652582 100644 --- a/scripts/parser-tests/test262/allowlist.txt +++ b/scripts/parser-tests/test262/allowlist.txt @@ -1,5 +1,3 @@ -language/expressions/in/private-field-in-nested.js(default) -language/expressions/in/private-field-in-nested.js(strict mode) language/import/json-invalid.js(default) language/import/json-invalid.js(strict mode) language/import/json-named-bindings.js(default)