Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(js_formatter): array printing and grouping in arrows #917

Merged
merged 3 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/biome_formatter/src/printer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> {
if arrow.type_parameters().is_some() {
return Ok(true);
Expand Down Expand Up @@ -438,31 +447,58 @@ impl Format<JsFormatContext> 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(),
JsSyntaxKind::JS_CALL_EXPRESSION | JsSyntaxKind::JS_LOGICAL_EXPRESSION
)
});

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)
Expand Down Expand Up @@ -602,7 +638,7 @@ impl Format<JsFormatContext> 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),
Expand Down Expand Up @@ -710,7 +746,7 @@ fn template_literal_contains_new_line(template: &JsTemplateExpression) -> bool {
///
///
/// # Examples
//
///
/// ```javascript
/// "test" + `
/// some content
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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 */);
Expand Down Expand Up @@ -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 */);
Expand Down