fix(packages/codegen): preserve private-in left operand precedence (#26403)

The codegen npm package drops required parentheses when a private-in expression is the left operand of a higher-precedence binary operator. The generated JavaScript can evaluate a different expression and throw a runtime error.

For example, this input returns `2`:

```js
class C {
  #x;
  test(o) {
    return (#x in o) + 1;
  }
}
console.log(new C().test(new C()));
```

The JavaScript printer previously emitted the return expression as:

```js
return #x in o + 1;
```

Because `+` binds more tightly than `in`, that output is parsed as `#x in (o + 1)`. Adding `1` to the object produces a primitive, so the private-field check throws a `TypeError` instead of producing the boolean used by the original addition.

The printer now preserves the grouping:

```js
return (#x in o) + 1;
```

The iterative binary-expression printer already computes the precedence required for each left operand. Its private-in special case discarded that value and passed `PREC_LOWEST` to `printPrivateInExpression`. Passing `v.leftPrecedence` instead lets the existing private-in printer insert the required parentheses.

This preserves grouping for arithmetic, exponentiation, and shift operators. Equal- and lower-precedence operators, such as `<`, `===`, and `&&`, continue to omit unnecessary parentheses. The correction applies to both the ordinary and source-map-enabled JavaScript printer builds and brings this path in line with the Rust codegen fix in #26383.
This commit is contained in:
camc314
2026-09-07 15:21:31 +00:00
parent 7e9508d1e1
commit bbbb4bc5c9
2 changed files with 31 additions and 1 deletions
+1 -1
View File
@@ -65,7 +65,7 @@ export function printBinaryish(
if (left.type === "BinaryExpression" && left.left.type === "PrivateIdentifier") {
// Private-in expression as the left operand
typeAssertIs<ESTree.PrivateInExpression>(left);
printPrivateInExpression(left, state, PREC_LOWEST);
printPrivateInExpression(left, state, v.leftPrecedence);
binVisitRightAndFinish(v, state);
break;
}
@@ -0,0 +1,30 @@
class C {
#x;
test(o) {
return [
(#x in o) + 1,
(#x in o) - 1,
(#x in o) * 1,
(#x in o) / 1,
(#x in o) % 1,
(#x in o) ** 1,
(#x in o) << 1,
(#x in o) >> 1,
(#x in o) >>> 1,
(#x in o) < 1,
(#x in o) <= 1,
(#x in o) > 1,
(#x in o) >= 1,
(#x in o) == 1,
(#x in o) != 1,
(#x in o) === 1,
(#x in o) !== 1,
(#x in o) & 1,
(#x in o) ^ 1,
(#x in o) | 1,
(#x in o) && 1,
(#x in o) || 1,
(#x in o) ?? 1,
];
}
}