diff --git a/CHANGELOG.md b/CHANGELOG.md index 020fbe2d28b9..ef784a8f51db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b - Fix [#1827](https://github.com/biomejs/biome/issues/1827) by properly analyzing nested `try-finally` statements. Contributed by @ah-yu - Fix [#1924](https://github.com/biomejs/biome/issues/1924) Use the correct export name to sort in the import clause. Contributed by @ah-yu +- Fix [#1805](https://github.com/biomejs/biome/issues/1805) fix formatting arrow function which has conditional expression body Contributed by @mdm317 ### CLI diff --git a/crates/biome_formatter/src/builders.rs b/crates/biome_formatter/src/builders.rs index e0bad87b0240..49762ca8866a 100644 --- a/crates/biome_formatter/src/builders.rs +++ b/crates/biome_formatter/src/builders.rs @@ -601,6 +601,72 @@ pub const fn space() -> Space { Space } +/// Inserts a single space. +/// The main difference with space is that +/// it always adds a space even when it's the last element of a group. +/// +/// # Examples +/// +/// ``` +/// use biome_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; +/// use biome_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(20).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// group(&format_args![ +/// text("nineteen_characters"), +/// soft_line_break(), +/// text("1"), +/// hard_space(), +/// ]) +/// ])?; +/// assert_eq!( +/// "nineteen_characters\n1", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// # Examples +/// +/// Without HardSpace +/// +/// ``` +/// use biome_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; +/// use biome_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(20).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// group(&format_args![ +/// text("nineteen_characters"), +/// soft_line_break(), +/// text("1"), +/// space(), +/// ]) +/// ])?; +/// assert_eq!( +/// "nineteen_characters1", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` + +#[inline] +pub const fn hard_space() -> HardSpace { + HardSpace +} + /// Optionally inserts a single space if the given condition is true. /// /// # Examples @@ -636,6 +702,14 @@ impl Format for Space { } } +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct HardSpace; + +impl Format for HardSpace { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::HardSpace) + } +} /// It adds a level of indentation to the given content /// /// It doesn't add any line breaks at the edges of the content, meaning that @@ -1251,6 +1325,109 @@ pub fn soft_line_indent_or_space(content: &impl Format) -> Blo } } +/// It functions similarly to soft_line_indent_or_space, but instead of a regular space, it inserts a hard space. +/// +/// # Examples +/// +/// Indents the content by one level and puts in new lines if the enclosing `Group` doesn't +/// fit on a single line. Otherwise, just inserts a space. +/// +/// ``` +/// use biome_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; +/// use biome_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(10).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// group(&format_args![ +/// text("name"), +/// space(), +/// text("="), +/// soft_line_indent_or_hard_space(&format_args![ +/// text("firstName"), +/// space(), +/// text("+"), +/// space(), +/// text("lastName"), +/// ]), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "name =\n\tfirstName + lastName", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// Only adds a space if the enclosing `Group` fits on a single line +/// ``` +/// use biome_formatter::{format, format_args}; +/// use biome_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [ +/// group(&format_args![ +/// text("a"), +/// space(), +/// text("="), +/// soft_line_indent_or_hard_space(&text("10")), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "a = 10", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// It enforces a space after the "=" assignment operators +/// ``` +/// use biome_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; +/// use biome_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(8).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// group(&format_args![ +/// text("value"), +/// soft_line_break_or_space(), +/// text("="), +/// soft_line_indent_or_hard_space(&format_args![ +/// text("10"), +/// ]), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "value\n=\n\t10", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` + +#[inline] +pub fn soft_line_indent_or_hard_space( + content: &impl Format, +) -> BlockIndent { + BlockIndent { + content: Argument::new(content), + mode: IndentMode::HardSpace, + } +} + #[derive(Copy, Clone)] pub struct BlockIndent<'a, Context> { content: Argument<'a, Context>, @@ -1262,6 +1439,7 @@ enum IndentMode { Soft, Block, SoftSpace, + HardSpace, SoftLineOrSpace, } @@ -1277,6 +1455,7 @@ impl Format for BlockIndent<'_, Context> { IndentMode::SoftLineOrSpace | IndentMode::SoftSpace => { write!(f, [soft_line_break_or_space()])? } + IndentMode::HardSpace => write!(f, [hard_space(), soft_line_break()])?, } let is_empty = { @@ -1296,7 +1475,7 @@ impl Format for BlockIndent<'_, Context> { IndentMode::Soft => write!(f, [soft_line_break()]), IndentMode::Block => write!(f, [hard_line_break()]), IndentMode::SoftSpace => write!(f, [soft_line_break_or_space()]), - IndentMode::SoftLineOrSpace => Ok(()), + IndentMode::SoftLineOrSpace | IndentMode::HardSpace => Ok(()), } } } @@ -1308,6 +1487,7 @@ impl std::fmt::Debug for BlockIndent<'_, Context> { IndentMode::Block => "HardBlockIndent", IndentMode::SoftLineOrSpace => "SoftLineIndentOrSpace", IndentMode::SoftSpace => "SoftSpaceBlockIndent", + IndentMode::HardSpace => "HardSpaceBlockIndent", }; f.debug_tuple(name).field(&"{{content}}").finish() diff --git a/crates/biome_formatter/src/format_element.rs b/crates/biome_formatter/src/format_element.rs index 70a5c6bc9ece..f9767d26dcbf 100644 --- a/crates/biome_formatter/src/format_element.rs +++ b/crates/biome_formatter/src/format_element.rs @@ -19,7 +19,7 @@ use std::rc::Rc; pub enum FormatElement { /// A space token, see [crate::builders::space] for documentation. Space, - + HardSpace, /// A new line, see [crate::builders::soft_line_break], [crate::builders::hard_line_break], and [crate::builders::soft_line_break_or_space] for documentation. Line(LineMode), @@ -27,7 +27,9 @@ pub enum FormatElement { ExpandParent, /// Token constructed by the formatter from a static string - StaticText { text: &'static str }, + StaticText { + text: &'static str, + }, /// Token constructed from the input source as a dynamic /// string with its start position in the input document. @@ -66,7 +68,7 @@ pub enum FormatElement { impl std::fmt::Debug for FormatElement { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - FormatElement::Space => write!(fmt, "Space"), + FormatElement::Space | FormatElement::HardSpace => write!(fmt, "Space"), FormatElement::Line(mode) => fmt.debug_tuple("Line").field(mode).finish(), FormatElement::ExpandParent => write!(fmt, "ExpandParent"), FormatElement::StaticText { text } => { @@ -251,9 +253,10 @@ impl FormatElements for FormatElement { // Traverse into the most flat version because the content is guaranteed to expand when even // the most flat version contains some content that forces a break. FormatElement::BestFitting(best_fitting) => best_fitting.most_flat().will_break(), - FormatElement::LineSuffixBoundary | FormatElement::Space | FormatElement::Tag(_) => { - false - } + FormatElement::LineSuffixBoundary + | FormatElement::Space + | FormatElement::Tag(_) + | FormatElement::HardSpace => false, } } diff --git a/crates/biome_formatter/src/format_element/document.rs b/crates/biome_formatter/src/format_element/document.rs index b30aed8d911c..9dc4d0ea8132 100644 --- a/crates/biome_formatter/src/format_element/document.rs +++ b/crates/biome_formatter/src/format_element/document.rs @@ -236,6 +236,7 @@ impl Format for &[FormatElement] { match element { element @ (FormatElement::Space + | FormatElement::HardSpace | FormatElement::StaticText { .. } | FormatElement::DynamicText { .. } | FormatElement::LocatedTokenText { .. }) => { @@ -246,7 +247,7 @@ impl Format for &[FormatElement] { in_text = true; match element { - FormatElement::Space => { + FormatElement::Space | FormatElement::HardSpace => { write!(f, [text(" ")])?; } element if element.is_text() => f.write_element(element.clone())?, diff --git a/crates/biome_formatter/src/printer/mod.rs b/crates/biome_formatter/src/printer/mod.rs index a26dd6664ea0..506decacade1 100644 --- a/crates/biome_formatter/src/printer/mod.rs +++ b/crates/biome_formatter/src/printer/mod.rs @@ -88,7 +88,7 @@ impl<'a> Printer<'a> { let args = stack.top(); match element { - FormatElement::Space => { + FormatElement::Space | FormatElement::HardSpace => { if self.state.line_width > 0 { self.state.pending_space = true; } @@ -985,7 +985,12 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { self.state.pending_space = true; } } - + FormatElement::HardSpace => { + self.state.line_width += 1; + if self.state.line_width > self.options().print_width.into() { + return Ok(Fits::No); + } + } FormatElement::Line(line_mode) => { if args.mode().is_flat() { match line_mode { @@ -1628,7 +1633,6 @@ two lines`, assert_eq!(printed.as_code(), "[1, 2, 3]; // trailing") } - #[test] fn conditional_with_group_id_in_fits() { let content = format_with(|f| { 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 2e650866e6ec..4a0b79117ee8 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 @@ -133,7 +133,8 @@ impl FormatNodeRule for FormatJsArrowFunctionExpressi } _ => false, }; - + let body_is_condition_type = + matches!(body, AnyJsExpression(JsConditionalExpression(_))); if body_has_soft_line_break { write![f, [formatted_signature, space(), format_body]] } else { @@ -148,30 +149,57 @@ impl FormatNodeRule for FormatJsArrowFunctionExpressi // if it's inside a JSXExpression (e.g. an attribute) we should align the expression's closing } with the line with the opening {. || matches!(node.syntax().parent().kind(), Some(JsSyntaxKind::JSX_EXPRESSION_CHILD | JsSyntaxKind::JSX_EXPRESSION_ATTRIBUTE_VALUE))) && !f.context().comments().has_comments(node.syntax()); - - write!( - f, - [ - formatted_signature, - group(&format_args![ - soft_line_indent_or_space(&format_with(|f| { - if should_add_parens { - write!(f, [if_group_fits_on_line(&text("("))])?; - } - - write!(f, [format_body])?; - - if should_add_parens { - write!(f, [if_group_fits_on_line(&text(")"))])?; - } - - Ok(()) - })), - is_last_call_arg.then_some(format_args![FormatTrailingComma::All,]), - should_add_soft_line.then_some(format_args![soft_line_break()]) - ]) - ] - ) + if body_is_condition_type { + write!( + f, + [ + formatted_signature, + group(&format_args![ + soft_line_indent_or_hard_space(&format_with(|f| { + if should_add_parens { + write!(f, [if_group_fits_on_line(&text("("))])?; + } + + write!(f, [format_body])?; + + if should_add_parens { + write!(f, [if_group_fits_on_line(&text(")"))])?; + } + + Ok(()) + })), + is_last_call_arg + .then_some(format_args![FormatTrailingComma::All,]), + should_add_soft_line.then_some(format_args![soft_line_break()]) + ]) + ] + ) + } else { + write!( + f, + [ + formatted_signature, + group(&format_args![ + soft_line_indent_or_space(&format_with(|f| { + if should_add_parens { + write!(f, [if_group_fits_on_line(&text("("))])?; + } + + write!(f, [format_body])?; + + if should_add_parens { + write!(f, [if_group_fits_on_line(&text(")"))])?; + } + + Ok(()) + })), + is_last_call_arg + .then_some(format_args![FormatTrailingComma::All,]), + should_add_soft_line.then_some(format_args![soft_line_break()]) + ]) + ] + ) + } } } } diff --git a/crates/biome_js_formatter/tests/specs/prettier/js/arrows/issue-1805-with-body-conditional-expression.js b/crates/biome_js_formatter/tests/specs/prettier/js/arrows/issue-1805-with-body-conditional-expression.js new file mode 100644 index 000000000000..652cfa23ccf6 --- /dev/null +++ b/crates/biome_js_formatter/tests/specs/prettier/js/arrows/issue-1805-with-body-conditional-expression.js @@ -0,0 +1,5 @@ +(seventeenfourcharacterssssssssssssssssssssssssssssssssssssssssssssssssssss)=>a?b:c; + +(seventeenfivecharactersssssssssssssssssssssssssssssssssssssssssssssssssssss)=>a?b:c; + +(seventeensixcharactersssssssssssssssssssssssssssssssssssssssssssssssssssssss)=>a?b:c; \ No newline at end of file diff --git a/crates/biome_js_formatter/tests/specs/prettier/js/arrows/issue-1805-with-body-conditional-expression.js.prettier-snap b/crates/biome_js_formatter/tests/specs/prettier/js/arrows/issue-1805-with-body-conditional-expression.js.prettier-snap new file mode 100644 index 000000000000..bc9639b81c1d --- /dev/null +++ b/crates/biome_js_formatter/tests/specs/prettier/js/arrows/issue-1805-with-body-conditional-expression.js.prettier-snap @@ -0,0 +1,10 @@ +(seventeenfourcharacterssssssssssssssssssssssssssssssssssssssssssssssssssss) => + a ? b : c; + +( + seventeenfivecharactersssssssssssssssssssssssssssssssssssssssssssssssssssss, +) => (a ? b : c); + +( + seventeensixcharactersssssssssssssssssssssssssssssssssssssssssssssssssssssss, +) => (a ? b : c); diff --git a/website/src/content/docs/internals/changelog.md b/website/src/content/docs/internals/changelog.md index d2f9ba01f81a..99427a5c8dc6 100644 --- a/website/src/content/docs/internals/changelog.md +++ b/website/src/content/docs/internals/changelog.md @@ -105,6 +105,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b - Fix [#1827](https://github.com/biomejs/biome/issues/1827) by properly analyzing nested `try-finally` statements. Contributed by @ah-yu - Fix [#1924](https://github.com/biomejs/biome/issues/1924) Use the correct export name to sort in the import clause. Contributed by @ah-yu +- Fix [#1805](https://github.com/biomejs/biome/issues/1805) fix formatting arrow function which has conditional expression body Contributed by @mdm317 ### CLI