fix(codegen): parenthesize await expression as base of ** (#23001)

## What

The base of an exponentiation `**` must be an `UpdateExpression` (ECMA-262 §13.6 `ExponentiationExpression : UpdateExpression ** ExponentiationExpression`). A `UnaryExpression` base is therefore already parenthesized by codegen (`(-a) ** b`, `(typeof a) ** b`), but `AwaitExpression` — which has the same `Prefix` precedence — was missing from the special case.

As a result `(await a) ** b` was emitted as `await a ** b`, which is a **different AST** (`await (a ** b)`) and is in fact rejected by oxc's own parser (*"Wrap await expression in parentheses to enforce operator precedence"*), so the output did not round-trip.

`AwaitExpression` is now included alongside `UnaryExpression` in the exponentiation-base special case.

## Examples

| Input | Before | After |
| --- | --- | --- |
| `(await a) ** b` | `await a ** b` ❌ (un-parseable) | `(await a) ** b` ✅ |
| `(await a) ** (await b)` | `await a ** await b` ❌ | `(await a) ** await b` ✅ |

The right operand is correctly left unparenthesized — the `**` right side is an `ExponentiationExpression`, which permits a bare `UnaryExpression`/`await`.
This commit is contained in:
Boshen committed 2026-06-06 06:14:45 +00:00
1 parent b98e26ff91
commit d727b6b10c
2 files changed
+12 -2

No files matched your search

@@ -200,10 +200,13 @@ impl<'a> BinaryExpressionVisitor<'a> {
}
}
BinaryishOperator::Binary(BinaryOperator::Exponential) => {
// Negative numbers are printed using a unary operator
// The base of `**` must be an `UpdateExpression`, so a unary/await base
// must be parenthesized. Negative numbers print as a unary operator.
if matches!(
e.left(),
Expression::UnaryExpression(_) | Expression::NumericLiteral(_)
Expression::UnaryExpression(_)
| Expression::AwaitExpression(_)
| Expression::NumericLiteral(_)
) {
self.left_precedence = Precedence::Call;
}
@@ -105,6 +105,13 @@ fn expr() {
test("new (f().g.h)();", "new (f()).g.h();\n");
test("new (f[g()])();", "new f[g()]();\n");
test("new (new f())();", "new new f()();\n");
// The base of `**` must be an UpdateExpression, so a unary/await base keeps parens.
test_same("(-a) ** b;\n");
test_same("(typeof a) ** b;\n");
test_same("(await a) ** b;\n");
// Only the base needs wrapping; the `**` right operand may be a UnaryExpression.
test("(await a) ** (await b);", "(await a) ** await b;\n");
}
#[test]