mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-15 06:39:17 +00:00
fix(codegen): preserve private-in left operand precedence (#26383)
Private-in expressions lose required parentheses when they appear on the left of a higher-precedence binary operator. This affects both default and minified code generation and can change the result or cause a runtime error.
For example:
```js
class C {
#x;
test(o) {
return (#x in o) + 1;
}
}
console.log(new C().test(new C()));
```
The input prints `2`. Previously, codegen emitted this return expression:
```js
return #x in o + 1;
```
Because `+` binds more tightly than `in`, this is parsed as `#x in (o + 1)`. The addition converts the object to a primitive, so the private-field check throws a `TypeError` instead of producing the boolean used by the original addition.
With this change, the required grouping is preserved:
```js
// Default output
return (#x in o) + 1;
// Minified output
return(#x in o)+1
```
The binary expression visitor already computes the precedence required for its left operand. However, its `PrivateInExpression` special case discarded that value and called `gen_expr` with `Precedence::Lowest` and an empty context. Passing `self.left_precedence` and `self.ctx` lets the existing private-in printer apply its parentheses rule using the same inputs as other left operands.
This preserves parentheses for arithmetic, exponentiation, and shift operators. Equal- and lower-precedence operators, such as `<`, `===`, and `&&`, continue to omit unnecessary parentheses.
This commit is contained in:
1 parent
2ca7d29b7a
commit
0a81d293f9
2 files changed
+34
-1
No files matched your search
@@ -226,7 +226,7 @@ impl<'a> BinaryExpressionVisitor<'a> {
|
||||
}
|
||||
|
||||
if let Expression::PrivateInExpression(e) = self.e.left() {
|
||||
e.gen_expr(p, Precedence::Lowest, Context::empty());
|
||||
e.gen_expr(p, self.left_precedence, self.ctx);
|
||||
self.visit_right_and_finish(p);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -142,6 +142,39 @@ fn private_in() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_in_binary_left() {
|
||||
fn wrap_case_equal_higher_precedence(op: &str) -> (String, String, String) {
|
||||
(
|
||||
format!("class C {{ #x; test(o) {{ return (#x in o) {op} 1; }} }}"),
|
||||
format!("class C {{\n\t#x;\n\ttest(o) {{\n\t\treturn (#x in o) {op} 1;\n\t}}\n}}\n"),
|
||||
format!("class C{{#x;test(o){{return(#x in o){op}1}}}}"),
|
||||
)
|
||||
}
|
||||
fn wrap_case_equal_lower_precedence(op: &str) -> (String, String, String) {
|
||||
(
|
||||
format!("class C {{ #x; test(o) {{ return (#x in o) {op} 1; }} }}"),
|
||||
format!("class C {{\n\t#x;\n\ttest(o) {{\n\t\treturn #x in o {op} 1;\n\t}}\n}}\n"),
|
||||
format!("class C{{#x;test(o){{return#x in o{op}1}}}}"),
|
||||
)
|
||||
}
|
||||
|
||||
for (source, expected, expected_minified) in
|
||||
["+", "-", "*", "/", "%", "**", "<<", ">>", ">>>"].map(wrap_case_equal_higher_precedence)
|
||||
{
|
||||
test(&source, &expected);
|
||||
test_minify(&source, &expected_minified);
|
||||
}
|
||||
|
||||
for (source, expected, expected_minified) in
|
||||
["<", "<=", ">", ">=", "==", "!=", "===", "!==", "&", "^", "|", "&&", "||", "??"]
|
||||
.map(wrap_case_equal_lower_precedence)
|
||||
{
|
||||
test(&source, &expected);
|
||||
test_minify(&source, &expected_minified);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class() {
|
||||
test(
|
||||
|
||||
Reference in new issue
Block a user