Skip to content

fix: parenthesize ?? when it is combined with || or && - #1442

Merged
eventualbuddha merged 1 commit into
benjamn:masterfrom
theRizwan:fix-nullish-logical-parens
Aug 21, 2026
Merged

eventualbuddha merged 1 commit into
benjamn:masterfrom
theRizwan:fix-nullish-logical-parens

Conversation

@theRizwan

Copy link
Copy Markdown
Contributor

Fixes #1422.

?? cannot be combined with || or && without parentheses. The CoalesceExpression production only admits BitwiseORExpression operands, plus a nested CoalesceExpression on the left, so a || b ?? c and a ?? b || c are both SyntaxErrors.

needsParens decides this by comparing operator precedence, and ?? sits at the bottom of the table since #1095. That covers a ?? nested inside || or &&, which is why (a ?? b) || c prints correctly today. It does not cover the other direction: with ?? as the parent, pp > np is false and nothing is emitted.

So printing a ?? whose operand is a || or && produces output that does not parse:

LogicalExpression("??", LogicalExpression("||", a, b), c)   ->  a || b ?? c;
LogicalExpression("??", a, LogicalExpression("||", b, c))   ->  a ?? b || c;
LogicalExpression("??", LogicalExpression("&&", a, b), c)   ->  a && b ?? c;
LogicalExpression("??", a, LogicalExpression("&&", b, c))   ->  a ?? b && c;

All four throw when parsed:

"a || b ?? c" -> SyntaxError: Unexpected token '??'
"a ?? b || c" -> SyntaxError: Unexpected token '||'
"a && b ?? c" -> SyntaxError: Unexpected token '??'
"a ?? b && c" -> SyntaxError: Unexpected token '&&'

With this change they print as (a || b) ?? c, a ?? (b || c), (a && b) ?? c and a ?? (b && c).

The check is on the operator pair rather than on precedence, because this restriction is a grammar production rather than an ordering. ?? alongside a non-logical operator is unaffected: a ?? b + c, a + b ?? c and a ?? b ?? c still print without parentheses.

Verified by feeding every printed form through new Function to confirm it parses, and by checking the opposite direction is not over-parenthesized. Round-tripping unmodified source containing each shape comes back byte-identical.

Two tests are added. The first covers the four broken shapes and fails on main. The second locks in that ?? is left alone when it is not combined with || or &&, and passes on main, so it guards against over-correction rather than restating the fix.

npm test is clean: 779 passing, lint and build included.

CoalesceExpression only admits BitwiseORExpression operands, plus a nested CoalesceExpression on the left, so a || b ?? c and a ?? b || c are both SyntaxErrors. needsParens decides this by comparing precedence, which parenthesizes a ?? nested inside || or && but never a || or && nested inside ??, so printing the latter produced output that does not parse.

Fixes benjamn#1422.
@eventualbuddha
eventualbuddha merged commit ed67bcf into benjamn:master Aug 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect Parentheses Emission With LogicalExpression/Nullish Coalescing With TSESTree AST

2 participants