diff --git a/crates/oxc_parser/src/diagnostics.rs b/crates/oxc_parser/src/diagnostics.rs index d9cab916b3348..8cab36614f40e 100644 --- a/crates/oxc_parser/src/diagnostics.rs +++ b/crates/oxc_parser/src/diagnostics.rs @@ -730,6 +730,11 @@ pub fn private_in_private(span: Span) -> OxcDiagnostic { OxcDiagnostic::error("Unexpected right-hand side of private-in expression").with_label(span) } +#[cold] +pub fn unexpected_private_identifier(span: Span) -> OxcDiagnostic { + OxcDiagnostic::error("Unexpected private identifier").with_label(span) +} + #[cold] pub fn import_arguments(span: Span) -> OxcDiagnostic { OxcDiagnostic::error("Dynamic imports can only accept a module specifier and an optional set of attributes as arguments").with_label(span) diff --git a/crates/oxc_parser/src/js/expression.rs b/crates/oxc_parser/src/js/expression.rs index 69d8db7dc06a8..38a9cd09cb784 100644 --- a/crates/oxc_parser/src/js/expression.rs +++ b/crates/oxc_parser/src/js/expression.rs @@ -157,6 +157,27 @@ impl<'a> ParserImpl<'a> { self.ast.private_identifier(span, name) } + /// [+In] PrivateIdentifier in ShiftExpression[?Yield, ?Await] + fn parse_private_in_expression( + &mut self, + lhs_span: u32, + lhs_precedence: Precedence, + ) -> Expression<'a> { + let left = self.parse_private_identifier(); + // Check if `in` operator precedence is allowed at current level. + // For `1 + #a in b`, when parsing RHS of `+`, lhs_precedence is `Add` which is + // higher than `Compare` (the precedence of `in`), so `#a in` cannot be parsed here. + if lhs_precedence >= Precedence::Compare { + return self.fatal_error(diagnostics::unexpected_private_identifier(left.span)); + } + self.expect(Kind::In); + let right = self.parse_binary_expression_or_higher(Precedence::Compare); + if let Expression::PrivateInExpression(private_in_expr) = right { + return self.fatal_error(diagnostics::private_in_private(private_in_expr.span)); + } + self.ast.expression_private_in(self.end_span(lhs_span), left, right) + } + /// Section [Primary Expression](https://tc39.es/ecma262/#sec-primary-expression) /// `PrimaryExpression`[Yield, Await] : /// this @@ -1143,14 +1164,7 @@ impl<'a> ParserImpl<'a> { let lhs_parenthesized = self.at(Kind::LParen); // [+In] PrivateIdentifier in ShiftExpression[?Yield, ?Await] let lhs = if self.ctx.has_in() && self.at(Kind::PrivateIdentifier) { - let left = self.parse_private_identifier(); - self.expect(Kind::In); - let right = self.parse_binary_expression_or_higher(Precedence::Compare); - if let Expression::PrivateInExpression(private_in_expr) = right { - let error = diagnostics::private_in_private(private_in_expr.span); - return self.fatal_error(error); - } - self.ast.expression_private_in(self.end_span(lhs_span), left, right) + self.parse_private_in_expression(lhs_span, lhs_precedence) } else { let has_pure_comment = self.lexer.trivia_builder.previous_token_has_pure_comment(); let mut expr = self.parse_unary_expression_or_higher(lhs_span); diff --git a/tasks/coverage/snapshots/parser_babel.snap b/tasks/coverage/snapshots/parser_babel.snap index 4095a43a1398b..f8c29eb3c98a7 100644 --- a/tasks/coverage/snapshots/parser_babel.snap +++ b/tasks/coverage/snapshots/parser_babel.snap @@ -3,9 +3,7 @@ commit: fc58af40 parser_babel Summary: AST Parsed : 2224/2230 (99.73%) Positive Passed: 2207/2230 (98.97%) -Negative Passed: 1648/1697 (97.11%) -Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2022/private-in/invalid-private-followed-by-in-2/input.js - +Negative Passed: 1649/1697 (97.17%) Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2026/explicit-resource-management/invalid-for-using-of-no-initializer/input.js Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/estree/class-private-property/typescript-invalid-abstract/input.ts @@ -7370,12 +7368,11 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc · ──────────────── ╰──── - × Expected `in` but found `(` - ╭─[babel/packages/babel-parser/test/fixtures/es2022/class-private-methods/asi-failure-generator/input.js:3:7] + × Unexpected private identifier + ╭─[babel/packages/babel-parser/test/fixtures/es2022/class-private-methods/asi-failure-generator/input.js:3:4] 2 │ p = x 3 │ *#m () {} - · ┬ - · ╰── `in` expected + · ── 4 │ } ╰──── @@ -8538,14 +8535,22 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc ╰──── help: Did you mean `export { "吾道一以貫之。" as "忠恕。" } from 'some-module'`? - × Unexpected right-hand side of private-in expression + × Unexpected private identifier ╭─[babel/packages/babel-parser/test/fixtures/es2022/private-in/invalid-private-followed-by-in-1/input.js:5:11] 4 │ method() { 5 │ #a in #b in c - · ─────── + · ── 6 │ } ╰──── + × Unexpected private identifier + ╭─[babel/packages/babel-parser/test/fixtures/es2022/private-in/invalid-private-followed-by-in-2/input.js:4:9] + 3 │ method() { + 4 │ 1 + #a in b + · ── + 5 │ } + ╰──── + × Unexpected token ╭─[babel/packages/babel-parser/test/fixtures/es2022/private-in/invalid-private-followed-by-in-3/input.js:5:18] 4 │ method() { @@ -8571,12 +8576,11 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc 5 │ } ╰──── - × Expected `in` but found `;` - ╭─[babel/packages/babel-parser/test/fixtures/es2022/private-in/private-binary-expression-right/input.js:4:11] + × Unexpected private identifier + ╭─[babel/packages/babel-parser/test/fixtures/es2022/private-in/private-binary-expression-right/input.js:4:9] 3 │ test() { 4 │ 1 + #x; - · ┬ - · ╰── `in` expected + · ── 5 │ } ╰──── diff --git a/tasks/coverage/snapshots/parser_test262.snap b/tasks/coverage/snapshots/parser_test262.snap index a61d0ab54ab4a..487ffd47b8c8c 100644 --- a/tasks/coverage/snapshots/parser_test262.snap +++ b/tasks/coverage/snapshots/parser_test262.snap @@ -18123,11 +18123,11 @@ Negative Passed: 4581/4581 (100.00%) · ─────────── ╰──── - × Unexpected right-hand side of private-in expression + × Unexpected private identifier ╭─[test262/test/language/expressions/in/private-field-in-nested.js:26:15] 25 │ constructor() { 26 │ #field in #field in this; - · ────────────── + · ────── 27 │ } ╰──── diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index dcaf3a3aa97f0..909eb345a0895 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -14345,12 +14345,12 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va 26 │ ╰──── - × Cannot assign to this expression - ╭─[typescript/tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts:29:9] - 28 │ invalidLHS(v: any) { - 29 │ 'prop' in v = 10; - · ─────────── - 30 │ #field in v = 10; + × Unexpected private identifier + ╭─[typescript/tests/cases/conformance/classes/members/privateNames/privateNameInInExpressionTransform.ts:20:14] + 19 │ + 20 │ v << #field in v << v; // Good precedence (SyntaxError): (v << #field) in (v << v) + · ────── + 21 │ ╰──── × Unexpected token