mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
fix(codegen): preserve private-in right operand precedence (#26411)
Private-in expressions can lose required parentheses around their right-hand operand in both default and minified code generation. When the operand is another relational expression, removing the grouping changes which value the private-field check receives and can change observable behavior.
For example:
```js
class D {
#x;
test(a, b) {
return #x in (a instanceof b);
}
}
new D().test({}, Object);
```
The original throws a `TypeError`: `a instanceof b` evaluates to `true`, and the private-field check requires an object as its right-hand operand. Previously, codegen emitted:
```js
return #x in a instanceof b;
```
Relational operators associate to the left, so this is parsed as `(#x in a) instanceof b`. The private-field check on `{}` produces `false`, and `false instanceof Object` then returns `false` instead of throwing.
The corrected output preserves the grouping in both modes:
```js
// Default output
return #x in (a instanceof b);
// Minified output
return#x in (a instanceof b)
```
`PrivateInExpression::gen_expr` now prints its right-hand operand with `Precedence::Compare` instead of `Precedence::Equals`. The printer wraps expressions whose precedence is equal to or lower than the requested precedence. Using `Compare` therefore preserves parentheses around relational operands, including `<`, `<=`, `>`, `>=`, `instanceof`, and nested private-in expressions. Equality and lower-precedence expressions continue to retain their required grouping, while shift, arithmetic, and exponentiation operands can still omit unnecessary parentheses.
This addresses the RHS grouping issue separately from #26383, which fixed the precedence passed to private-in expressions appearing as the left operand of another binary expression. The change is confined to the Rust `oxc_codegen` crate.
This commit is contained in:
1 parent
81fd4825a1
commit
42ac916b21
2 files changed
+62
-1
No files matched your search
@@ -2006,7 +2006,7 @@ impl GenExpr for PrivateInExpression<'_> {
|
||||
p.add_source_mapping(self.span);
|
||||
self.left.print(p, ctx);
|
||||
p.print_str(" in ");
|
||||
self.right.print_expr(p, Precedence::Equals, Context::FORBID_IN);
|
||||
self.right.print_expr(p, Precedence::Compare, Context::FORBID_IN);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,67 @@ fn private_in() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_in_binary_right() {
|
||||
fn wrap_case_equal_lower_precedence(op: &str) -> (String, String, String) {
|
||||
let minified_op =
|
||||
if matches!(op, "in" | "instanceof") { format!(" {op} ") } else { op.to_string() };
|
||||
(
|
||||
format!("class C {{ #x; test(a, b) {{ return #x in (a {op} b); }} }}"),
|
||||
format!("class C {{\n\t#x;\n\ttest(a, b) {{\n\t\treturn #x in (a {op} b);\n\t}}\n}}\n"),
|
||||
format!("class C{{#x;test(a,b){{return#x in (a{minified_op}b)}}}}"),
|
||||
)
|
||||
}
|
||||
fn wrap_case_higher_precedence(op: &str) -> (String, String, String) {
|
||||
(
|
||||
format!("class C {{ #x; test(a, b) {{ return #x in (a {op} b); }} }}"),
|
||||
format!("class C {{\n\t#x;\n\ttest(a, b) {{\n\t\treturn #x in a {op} b;\n\t}}\n}}\n"),
|
||||
format!("class C{{#x;test(a,b){{return#x in a{op}b}}}}"),
|
||||
)
|
||||
}
|
||||
|
||||
for (source, expected, expected_minified) in [
|
||||
"instanceof",
|
||||
"in",
|
||||
"<",
|
||||
"<=",
|
||||
">",
|
||||
">=",
|
||||
"==",
|
||||
"!=",
|
||||
"===",
|
||||
"!==",
|
||||
"&",
|
||||
"^",
|
||||
"|",
|
||||
"&&",
|
||||
"||",
|
||||
"??",
|
||||
"=",
|
||||
]
|
||||
.map(wrap_case_equal_lower_precedence)
|
||||
{
|
||||
test(&source, &expected);
|
||||
test_minify(&source, &expected_minified);
|
||||
}
|
||||
|
||||
for (source, expected, expected_minified) in
|
||||
["<<", ">>", ">>>", "+", "-", "*", "/", "%", "**"].map(wrap_case_higher_precedence)
|
||||
{
|
||||
test(&source, &expected);
|
||||
test_minify(&source, &expected_minified);
|
||||
}
|
||||
|
||||
for (rhs, minified_rhs) in [("#x in a", "#x in a"), ("a ? a : b", "a?a:b"), ("a, b", "a,b")] {
|
||||
let source = format!("class C {{ #x; test(a, b) {{ return #x in ({rhs}); }} }}");
|
||||
test(
|
||||
&source,
|
||||
&format!("class C {{\n\t#x;\n\ttest(a, b) {{\n\t\treturn #x in ({rhs});\n\t}}\n}}\n"),
|
||||
);
|
||||
test_minify(&source, &format!("class C{{#x;test(a,b){{return#x in ({minified_rhs})}}}}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_in_binary_left() {
|
||||
fn wrap_case_equal_higher_precedence(op: &str) -> (String, String, String) {
|
||||
|
||||
Reference in new issue
Block a user