fix(formatter): do not indent logical chains whose right sides all inline (#27667)

```js
// Before
const one = $abilities ?? [
  "foo",
  "bar",
  "veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey",
];
const two = $abilities ?? ["foo"] ?? [
    "foo",
    "bar",
    "veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey",
  ];

// After
const one = $abilities ?? [
  "foo",
  "bar",
  "veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey",
];
const two = $abilities ?? ["foo"] ?? [
  "foo",
  "bar",
  "veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey",
];
```
This commit is contained in:
leaysgur committed 2026-10-14 01:45:32 +00:00
1 parent a07a0fbb8f
commit 644ca1c04f
4 files changed
+108 -6

No files matched your search

+31
View File
@@ -865,3 +865,34 @@ const get = (key: K) => (({ a: 1, b: 2 }) as const)[key];
When an ancestor gets its own parens, a leading `{`/`function`/`class`/`let[` inside it needs none.
Prettier already does this after `export default` (`` export default (function () {} + foo)`` ``) and for a sequence arrow body (`() => ({}, 1)`),
but not at statement start or for other arrow bodies, e.g. the object lookup idiom above.
## inline-logical-chain-indent
- Why: uniform-rule (same construct, same output: a single-link inline chain `a ?? [...]`; prettier/prettier#5183, prettier/prettier#19792)
- Pin: `tests/fixtures/js/logicals/inline-logical-chain-indent.js`
```js
// input
const x = $abilities ?? ["foo", "bar", "bar"] ?? ["foo", "bar", "bar", "veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey"];
// ours
const x = $abilities ?? ["foo", "bar", "bar"] ?? [
"foo",
"bar",
"bar",
"veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey",
];
// prettier
const x = $abilities ?? ["foo", "bar", "bar"] ?? [
"foo",
"bar",
"bar",
"veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey",
];
```
When every right side of a logical chain is an inlined object/array/JSX literal, the chain never breaks at an operator.
Prettier prints a single-link chain without an indent, but a flattened one indents its tail, which only shifts the last literal.
A chain with a non-inlined right side breaks at the operators and keeps the indent.
Prettier `main` drops the indent since #19792; the pin (3.9.10) still indents.
@@ -239,18 +239,23 @@ impl<'a> Format<'a, JsFormatContext<'a>> for BinaryLikeExpression<'a, '_> {
);
}
// Check other conditions that require knowing if the expression is flattened
let inline_logical_expression = self.should_inline_logical_expression();
let should_indent_if_inlines = should_indent_if_parent_inlines(self.parent());
// We need to know if it's flattened to make this decision, so we must build parts
// is_inside_condition is always false here (we returned early if true)
let parts = split_into_left_and_right_sides(*self, false);
let flattened = parts.len() > 2;
// Every operator of the flattened chain inlines its right side (`a ?? [..] ?? [..]`),
// so the chain never breaks at an operator and an indent would only shift the last literal.
// See DIVERGENCES.md#inline-logical-chain-indent
let inline_chain = inline_logical_expression
&& parts.iter().all(|part| match part {
BinaryLeftOrRightSide::Left { .. } => true,
BinaryLeftOrRightSide::Right { parent, .. } => {
parent.should_inline_logical_expression()
}
});
if (inline_logical_expression && !flattened)
|| (!inline_logical_expression && should_indent_if_inlines)
{
if inline_chain || (!inline_logical_expression && should_indent_if_inlines) {
return write!(
f,
[group(&format_once(|f| {
@@ -0,0 +1,6 @@
const x = $abilities ?? ["foo", "bar", "bar"] ?? ["foo", "bar", "bar", "veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey"];
const y = $abilities ?? { a: 1, b: 2 } ?? { a: 1, b: 2, c: 3, veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey: true };
// Not every right side inlines, the chain breaks at an operator and keeps the indent
const z = $abilities ?? foo ?? ["foo", "bar", "bar", "veryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongKey"];
@@ -0,0 +1,60 @@
---
source: crates/oxc_formatter/tests/fixtures/mod.rs
---
==================== Input ====================
const x = $abilities ?? ["foo", "bar", "bar"] ?? ["foo", "bar", "bar", "veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey"];
const y = $abilities ?? { a: 1, b: 2 } ?? { a: 1, b: 2, c: 3, veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey: true };
// Not every right side inlines, the chain breaks at an operator and keeps the indent
const z = $abilities ?? foo ?? ["foo", "bar", "bar", "veryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongKey"];
==================== Output ====================
------------------
{ printWidth: 80 }
------------------
const x = $abilities ?? ["foo", "bar", "bar"] ?? [
"foo",
"bar",
"bar",
"veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey",
];
const y = $abilities ?? { a: 1, b: 2 } ?? {
a: 1,
b: 2,
c: 3,
veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey: true,
};
// Not every right side inlines, the chain breaks at an operator and keeps the indent
const z = $abilities ??
foo ?? [
"foo",
"bar",
"bar",
"veryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongKey",
];
-------------------
{ printWidth: 100 }
-------------------
const x = $abilities ?? ["foo", "bar", "bar"] ?? [
"foo",
"bar",
"bar",
"veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey",
];
const y = $abilities ?? { a: 1, b: 2 } ?? {
a: 1,
b: 2,
c: 3,
veryVeryVeryVeryVeryVeryVeryVeryVeryLongKey: true,
};
// Not every right side inlines, the chain breaks at an operator and keeps the indent
const z = $abilities ??
foo ?? ["foo", "bar", "bar", "veryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongKey"];
===================== End =====================