diff --git a/crates/biome_formatter/src/printer/mod.rs b/crates/biome_formatter/src/printer/mod.rs index 864ca0749338..826cc5653372 100644 --- a/crates/biome_formatter/src/printer/mod.rs +++ b/crates/biome_formatter/src/printer/mod.rs @@ -162,6 +162,12 @@ impl<'a> Printer<'a> { _ => { self.state.measured_group_fits = true; + if let Some(id) = group.id() { + self.state + .group_modes + .insert_print_mode(id, PrintMode::Flat); + } + // Measure to see if the group fits up on a single line. If that's the case, // print the group in "flat" mode, otherwise continue in expanded mode stack.push(TagKind::Group, args.with_print_mode(PrintMode::Flat)); diff --git a/crates/biome_js_formatter/src/js/expressions/arrow_function_expression.rs b/crates/biome_js_formatter/src/js/expressions/arrow_function_expression.rs index 0edd7cc16c9e..eb859a76a92a 100644 --- a/crates/biome_js_formatter/src/js/expressions/arrow_function_expression.rs +++ b/crates/biome_js_formatter/src/js/expressions/arrow_function_expression.rs @@ -286,6 +286,15 @@ fn format_signature( }) } +/// Returns a `true` result if the arrow function contains any elements which +/// should force the chain to break onto multiple lines. This includes any kind +/// of return type annotation if the function also takes parameters (e.g., +/// `(a, b): bool => ...`), or any kind of rest/object/array binding parameter +/// (e.g., `({a, b: foo}) => ...`). +/// +/// The complexity of these expressions limits their legibility when printed +/// inline, so they force the chain to break to preserve clarity. Any other +/// cases are considered simple enough to print in a single line. fn should_break_chain(arrow: &JsArrowFunctionExpression) -> SyntaxResult { if arrow.type_parameters().is_some() { return Ok(true); @@ -438,6 +447,8 @@ impl Format for ArrowChain { } = self; let head_parent = head.syntax().parent(); + let tail_body = tail.body()?; + let is_assignment_rhs = self.options.assignment_layout.is_some(); let ancestor_call_expr_or_logical_expr = head.syntax().ancestors().any(|ancestor| { matches!( ancestor.kind(), @@ -445,24 +456,49 @@ impl Format for ArrowChain { ) }); - let tail_body = tail.body()?; - - let is_assignment_rhs = self.options.assignment_layout.is_some(); - + // If this chain is the callee in a parent call expression, then we + // want it to break onto a new line to clearly show that the arrow + // chain is distinct and the _result_ is what's being called. + // Example: + // (() => () => a)() + // becomes + // ( + // () => () => + // a + // )(); let is_callee = head_parent .as_ref() .map_or(false, |parent| is_callee(head.syntax(), parent)); + // With arrays, objects, sequence expressions, and block function bodies, + // the opening brace gives a convenient boundary to insert a line break, + // allowing that token to live immediately after the last arrow token + // and save a line from being printed with just the punctuation. + // + // (foo) => (bar) => [a, b] + // + // (foo) => (bar) => [ + // a, + // b + // ] + // + // If the body is _not_ one of those kinds, then we'll want to insert a + // soft line break before the body so that it prints on a separate line + // in its entirety. let body_on_separate_line = !matches!( tail_body, AnyJsFunctionBody::JsFunctionBody(_) | AnyJsFunctionBody::AnyJsExpression( AnyJsExpression::JsObjectExpression(_) + | AnyJsExpression::JsArrayExpression(_) | AnyJsExpression::JsSequenceExpression(_) ) ); - let break_before_chain = (is_callee && body_on_separate_line) + // If the arrow chain will break onto multiple lines, either because + // it's a callee or because the body is printed on its own line, then + // the signatures should be expanded first. + let break_signatures = (is_callee && body_on_separate_line) || matches!( self.options.assignment_layout, Some(AssignmentLikeLayout::ChainTailArrowFunction) @@ -602,7 +638,7 @@ impl Format for ArrowChain { [ group(&indent(&format_arrow_signatures)) .with_group_id(Some(group_id)) - .should_expand(break_before_chain), + .should_expand(break_signatures), space(), tail.fat_arrow_token().format(), indent_if_group_breaks(&format_tail_body, group_id), @@ -710,7 +746,7 @@ fn template_literal_contains_new_line(template: &JsTemplateExpression) -> bool { /// /// /// # Examples -// +/// /// ```javascript /// "test" + ` /// some content diff --git a/crates/biome_js_formatter/tests/specs/prettier/js/arrays/issue-10159.js.snap b/crates/biome_js_formatter/tests/specs/prettier/js/arrays/issue-10159.js.snap deleted file mode 100644 index 61add494c85e..000000000000 --- a/crates/biome_js_formatter/tests/specs/prettier/js/arrays/issue-10159.js.snap +++ /dev/null @@ -1,60 +0,0 @@ ---- -source: crates/biome_formatter_test/src/snapshot_builder.rs -info: js/arrays/issue-10159.js ---- - -# Input - -```js -{for (const srcPath of [src, `${src}.js`, `${src}/index`, `${src}/index.js`]) {}} -{for (const srcPath of [123, 123_123_123, 123_123_123_1, 13_123_3123_31_43]) {}} -{for (const srcPath of [123, 123_123_123, 123_123_123_1, 13_123_3123_31_432]) {}} -{for (const srcPath of [123, 123_123_123, 123_123_123_1, 13_123_3123_31_4321]) {}} - -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Biome -@@ -7,7 +7,9 @@ - } - } - { -- for (const srcPath of [123, 123_123_123, 123_123_123_1, 13_123_3123_31_432]) { -+ for (const srcPath of [ -+ 123, 123_123_123, 123_123_123_1, 13_123_3123_31_432, -+ ]) { - } - } - { -``` - -# Output - -```js -{ - for (const srcPath of [src, `${src}.js`, `${src}/index`, `${src}/index.js`]) { - } -} -{ - for (const srcPath of [123, 123_123_123, 123_123_123_1, 13_123_3123_31_43]) { - } -} -{ - for (const srcPath of [ - 123, 123_123_123, 123_123_123_1, 13_123_3123_31_432, - ]) { - } -} -{ - for (const srcPath of [ - 123, 123_123_123, 123_123_123_1, 13_123_3123_31_4321, - ]) { - } -} -``` - - diff --git a/crates/biome_js_formatter/tests/specs/prettier/js/arrows/currying-4.js.snap b/crates/biome_js_formatter/tests/specs/prettier/js/arrows/currying-4.js.snap index fa5aff9502a3..6f1c4c94b815 100644 --- a/crates/biome_js_formatter/tests/specs/prettier/js/arrows/currying-4.js.snap +++ b/crates/biome_js_formatter/tests/specs/prettier/js/arrows/currying-4.js.snap @@ -75,77 +75,7 @@ a( ```diff --- Prettier +++ Biome -@@ -41,47 +41,49 @@ - "sky.", - ]; - --const x2 = () => () => [ -- "The", -- "green", -- "dragon", -- "liked", -- "to", -- "knit", -- "sweaters", -- "for", -- "the", -- "fluffy", -- "clouds", -- "in", -- "the", -- "sky.", --]; -+const x2 = () => () => -+ [ -+ "The", -+ "green", -+ "dragon", -+ "liked", -+ "to", -+ "knit", -+ "sweaters", -+ "for", -+ "the", -+ "fluffy", -+ "clouds", -+ "in", -+ "the", -+ "sky.", -+ ]; - --const x3 = () => () => () => [ -- "The", -- "green", -- "dragon", -- "liked", -- "to", -- "knit", -- "sweaters", -- "for", -- "the", -- "fluffy", -- "clouds", -- "in", -- "the", -- "sky.", --]; -+const x3 = () => () => () => -+ [ -+ "The", -+ "green", -+ "dragon", -+ "liked", -+ "to", -+ "knit", -+ "sweaters", -+ "for", -+ "the", -+ "fluffy", -+ "clouds", -+ "in", -+ "the", -+ "sky.", -+ ]; +@@ -77,11 +77,11 @@ f((a) => (1, 2, 3) /* a */); f((a) => (b) => (1, 2, 3) /* b */ /* a */); @@ -207,41 +137,39 @@ const x1 = () => [ "sky.", ]; -const x2 = () => () => - [ - "The", - "green", - "dragon", - "liked", - "to", - "knit", - "sweaters", - "for", - "the", - "fluffy", - "clouds", - "in", - "the", - "sky.", - ]; - -const x3 = () => () => () => - [ - "The", - "green", - "dragon", - "liked", - "to", - "knit", - "sweaters", - "for", - "the", - "fluffy", - "clouds", - "in", - "the", - "sky.", - ]; +const x2 = () => () => [ + "The", + "green", + "dragon", + "liked", + "to", + "knit", + "sweaters", + "for", + "the", + "fluffy", + "clouds", + "in", + "the", + "sky.", +]; + +const x3 = () => () => () => [ + "The", + "green", + "dragon", + "liked", + "to", + "knit", + "sweaters", + "for", + "the", + "fluffy", + "clouds", + "in", + "the", + "sky.", +]; f((a) => (1, 2, 3) /* a */); f((a) => (b) => (1, 2, 3) /* b */ /* a */);