mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(parser): correct precedence handling for private-in expression (#18169)
## Summary - Fix incorrect parsing of `1 + #a in b` which was parsed as `1 + (#a in b)` instead of `(1 + #a) in b` - Add precedence check before parsing `#a in` as `PrivateInExpression` - Report "Unexpected private identifier" error when precedence doesn't allow `in` operator ## Details The parser was greedily parsing `#a in` as a `PrivateInExpression` without checking operator precedence. For example, when parsing the RHS of `+` in `1 + #a in b`, the `in` operator's precedence (`Compare = 13`) is lower than `+`'s precedence (`Add = 15`), so `#a in` should not be parsed as a unit. The fix extracts private-in parsing into `parse_private_in_expression` and adds a check: if `lhs_precedence >= Precedence::Compare`, we reject the private identifier with a syntax error, matching V8 and Babel behavior. ## Test plan - `cargo test -p oxc_parser` passes - Conformance improved: Negative Passed 1648 → 1649 for Babel tests - `invalid-private-followed-by-in-2` test case now correctly reports syntax error 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
5 files changed
+52
-29
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 │ }
|
||||
╰────
|
||||
|
||||
|
||||
@@ -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 │ }
|
||||
╰────
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user