fix(formatter): normalize ChainExpression with TSNonNullExpression to match Prettier#18061
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes the formatting of optional chain expressions combined with non-null assertions to match Prettier's output. The fix handles two AST patterns: ChainExpression > TSNonNullExpression and TSNonNullExpression > ChainExpression.
Changes:
- Extracted
chain_expression_needs_parensas a shared function to determine parenthesization needs - Updated
ChainExpression::write()to manually handle parentheses when the child is aTSNonNullExpression, printing(a?.b)!instead of(a?.b!) - Added recursive case in
chain_expression_needs_parensto handleTSNonNullExpressionparents by checking the grandparent context
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| tasks/prettier_conformance/snapshots/prettier.ts.snap.md | Updates conformance metrics showing improvement from 95.39% to 96.05%, with 4 previously failing test files now passing |
| crates/oxc_formatter/src/print/mod.rs | Adds special handling in ChainExpression::write() to reorder non-null assertions outside parentheses |
| crates/oxc_formatter/src/parentheses/mod.rs | Exports the new chain_expression_needs_parens function |
| crates/oxc_formatter/src/parentheses/expression.rs | Extracts parenthesization logic into shared function and adds recursive handling for TSNonNullExpression parents |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Merging this PR will not alter performance
Comparing Footnotes
|
1e33052 to
f1e1b25
Compare
Merge activity
|
…` to match Prettier (#18061) ## Summary Fixes the formatting of optional chain expressions combined with non-null assertions to match Prettier's output: - `(a?.b!).c` now prints as `(a?.b)!.c` (moves `!` outside parentheses) - `(a?.b)!.c` correctly preserves parentheses - `(a?.b)![c?.d!]` correctly handles computed member access ## Implementation The fix introduces a shared `chain_expression_needs_parens` function that handles two AST patterns: 1. **`ChainExpression > TSNonNullExpression`** - handled in `write()` by printing inner content with parens, then `!` 2. **`TSNonNullExpression > ChainExpression`** - handled in `needs_parentheses()` by recursively checking grandparent This matches Prettier's behavior documented in their [clean.js](https://github.com/prettier/prettier/blob/main/src/language-js/clean.js#L180-L188). ## Test plan - [x] All 18 test cases in `typescript/non-null/optional-chain.ts` now pass - [x] Additional chain-expression tests now pass - [x] TS Prettier conformance improved from **95.39%** to **96.05%** - [x] All 144 formatter unit tests pass 🤖 Generated with [Claude Code](https://claude.ai/code)
f1e1b25 to
3e141f0
Compare
|
FYI: I plan to change this to keep AST unchanged. |
Summary
Fixes the formatting of optional chain expressions combined with non-null assertions to match Prettier's output:
(a?.b!).cnow prints as(a?.b)!.c(moves!outside parentheses)(a?.b)!.ccorrectly preserves parentheses(a?.b)![c?.d!]correctly handles computed member accessImplementation
The fix introduces a shared
chain_expression_needs_parensfunction that handles two AST patterns:ChainExpression > TSNonNullExpression- handled inwrite()by printing inner content with parens, then!TSNonNullExpression > ChainExpression- handled inneeds_parentheses()by recursively checking grandparentThis matches Prettier's behavior documented in their clean.js.
Test plan
typescript/non-null/optional-chain.tsnow pass🤖 Generated with Claude Code