diff --git a/crates/rome_formatter/src/builders.rs b/crates/rome_formatter/src/builders.rs index b7d81d34c5b1..c5b98734032e 100644 --- a/crates/rome_formatter/src/builders.rs +++ b/crates/rome_formatter/src/builders.rs @@ -1714,6 +1714,126 @@ impl std::fmt::Debug for IfGroupBreaks<'_, Context> { } } +/// Increases the indent level by one if the group with the specified id breaks. +/// +/// This IR has the same semantics as using [if_group_breaks] and [if_group_fits_on_line] together. +/// +/// ``` +/// # use rome_formatter::prelude::*; +/// # use rome_formatter::write; +/// # let format = format_with(|f: &mut Formatter| { +/// let id = f.group_id("head"); +/// +/// write!(f, [ +/// group(&text("Head")).with_group_id(Some(id)), +/// if_group_breaks(&indent(&text("indented"))).with_group_id(Some(id)), +/// if_group_fits_on_line(&text("indented")).with_group_id(Some(id)) +/// ]) +/// +/// # }); +/// ``` +/// +/// If you want to indent some content if the enclosing group breaks, use [`indent`]. +/// +/// Use [if_group_breaks] or [if_group_fits_on_line] if the fitting and breaking content differs more than just the +/// indention level. +/// +/// # Examples +/// +/// Indent the body of an arrow function if the group wrapping the signature breaks: +/// ``` +/// use rome_formatter::{format, format_args, LineWidth, SimpleFormatOptions, write}; +/// use rome_formatter::prelude::*; +/// +/// let content = format_with(|f| { +/// let group_id = f.group_id("header"); +/// +/// write!(f, [ +/// group(&text("(aLongHeaderThatBreaksForSomeReason) =>")).with_group_id(Some(group_id)), +/// indent_if_break(&format_args![hard_line_break(), text("a => b")], group_id) +/// ]) +/// }); +/// +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(20).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let formatted = format!(context, [content]).unwrap(); +/// +/// assert_eq!( +/// "(aLongHeaderThatBreaksForSomeReason) =>\n\ta => b", +/// formatted.print().as_code() +/// ); +/// ``` +/// +/// It doesn't add an indent if the group wrapping the signature doesn't break: +/// ``` +/// use rome_formatter::{format, format_args, LineWidth, SimpleFormatOptions, write}; +/// use rome_formatter::prelude::*; +/// +/// let content = format_with(|f| { +/// let group_id = f.group_id("header"); +/// +/// write!(f, [ +/// group(&text("(aLongHeaderThatBreaksForSomeReason) =>")).with_group_id(Some(group_id)), +/// indent_if_break(&format_args![hard_line_break(), text("a => b")], group_id) +/// ]) +/// }); +/// +/// let formatted = format!(SimpleFormatContext::default(), [content]).unwrap(); +/// +/// assert_eq!( +/// "(aLongHeaderThatBreaksForSomeReason) =>\na => b", +/// formatted.print().as_code() +/// ); +/// ``` +#[inline] +pub fn indent_if_break( + content: &Content, + group_id: GroupId, +) -> IndentIfBreak +where + Content: Format, +{ + IndentIfBreak { + group_id, + content: Argument::new(content), + } +} + +#[derive(Copy, Clone)] +pub struct IndentIfBreak<'a, Context> { + content: Argument<'a, Context>, + group_id: GroupId, +} + +impl Format for IndentIfBreak<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let mut buffer = VecBuffer::new(f.state_mut()); + + buffer.write_fmt(Arguments::from(&self.content))?; + + if buffer.is_empty() { + return Ok(()); + } + + let content = buffer.into_vec(); + f.write_element(FormatElement::IndentIfBreaks( + format_element::IndentIfBreak::new(content.into_boxed_slice(), self.group_id), + )) + } +} + +impl std::fmt::Debug for IndentIfBreak<'_, Context> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("IndentIfBreak") + .field("group_id", &self.group_id) + .field("content", &"{{content}}") + .finish() + } +} + /// Utility for formatting some content with an inline lambda function. #[derive(Copy, Clone)] pub struct FormatWith { diff --git a/crates/rome_formatter/src/format_element.rs b/crates/rome_formatter/src/format_element.rs index d7f8e800eb8d..37d86b7d4133 100644 --- a/crates/rome_formatter/src/format_element.rs +++ b/crates/rome_formatter/src/format_element.rs @@ -59,6 +59,10 @@ pub enum FormatElement { /// is printed on a single line or multiple lines. See [crate::if_group_breaks] for examples. ConditionalGroupContent(ConditionalGroupContent), + /// Optimized version of [FormatElement::ConditionalGroupContent] for the case where some content + /// should be indented if the specified group breaks. + IndentIfBreaks(IndentIfBreak), + /// Concatenates multiple elements together. See [crate::Formatter::join_with] for examples. List(List), @@ -177,6 +181,7 @@ impl std::fmt::Debug for FormatElement { content.fmt(fmt) } FormatElement::ConditionalGroupContent(content) => content.fmt(fmt), + FormatElement::IndentIfBreaks(content) => content.fmt(fmt), FormatElement::List(content) => { write!(fmt, "List ")?; content.fmt(fmt) @@ -509,6 +514,19 @@ impl std::fmt::Debug for Label { } } +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct IndentIfBreak { + pub(crate) content: Content, + + pub(crate) group_id: GroupId, +} + +impl IndentIfBreak { + pub fn new(content: Content, group_id: GroupId) -> Self { + Self { content, group_id } + } +} + #[derive(Debug, Clone, Eq, PartialEq)] pub struct ConditionalGroupContent { pub(crate) content: Content, @@ -669,6 +687,7 @@ impl FormatElement { FormatElement::Line(line_mode) => matches!(line_mode, LineMode::Hard | LineMode::Empty), FormatElement::Group(Group { content, .. }) | FormatElement::ConditionalGroupContent(ConditionalGroupContent { content, .. }) + | FormatElement::IndentIfBreaks(IndentIfBreak { content, .. }) | FormatElement::Comment(content) | FormatElement::Fill(Fill { content, .. }) | FormatElement::Verbatim(Verbatim { content, .. }) @@ -889,6 +908,30 @@ impl Format for FormatElement { write!(f, [text(")")]) } + FormatElement::IndentIfBreaks(content) => { + write!( + f, + [ + text("indent_if_break("), + group(&soft_block_indent(&format_args![ + content.content.as_ref(), + text(","), + soft_line_break_or_space(), + text("{"), + group(&format_args![soft_line_indent_or_space(&format_args![ + text("group-id:"), + space(), + dynamic_text( + &std::format!("{:?}", content.group_id), + TextSize::default() + ), + soft_line_break_or_space() + ])]), + text("}") + ])) + ] + ) + } FormatElement::ConditionalGroupContent(content) => { match content.mode { PrintMode::Flat => { diff --git a/crates/rome_formatter/src/printer/mod.rs b/crates/rome_formatter/src/printer/mod.rs index 091c76209d2d..67669b48f37f 100644 --- a/crates/rome_formatter/src/printer/mod.rs +++ b/crates/rome_formatter/src/printer/mod.rs @@ -3,7 +3,8 @@ mod printer_options; pub use printer_options::*; use crate::format_element::{ - Align, ConditionalGroupContent, DedentMode, Group, LineMode, PrintMode, VerbatimKind, + Align, ConditionalGroupContent, DedentMode, Group, IndentIfBreak, LineMode, PrintMode, + VerbatimKind, }; use crate::intersperse::Intersperse; use crate::{FormatElement, GroupId, IndentStyle, Printed, SourceMarker, TextRange}; @@ -212,6 +213,18 @@ impl<'a> Printer<'a> { } } + FormatElement::IndentIfBreaks(IndentIfBreak { content, group_id }) => { + let group_mode = self.state.group_modes.unwrap_print_mode(*group_id, element); + + match group_mode { + PrintMode::Flat => queue.extend_with_args(content.iter(), args), + PrintMode::Expanded => queue.extend_with_args( + content.iter(), + args.increment_indent_level(self.options.indent_style), + ), + } + } + FormatElement::Line(line_mode) => { if args.mode.is_flat() && matches!(line_mode, LineMode::Soft | LineMode::SoftOrSpace) @@ -922,6 +935,21 @@ fn fits_element_on_line<'a, 'rest>( } } + FormatElement::IndentIfBreaks(indent) => { + let group_mode = state + .group_modes + .get_print_mode(indent.group_id) + .unwrap_or(args.mode); + + match group_mode { + PrintMode::Flat => queue.extend(indent.content.iter(), args), + PrintMode::Expanded => queue.extend( + indent.content.iter(), + args.increment_indent_level(options.indent_style()), + ), + } + } + FormatElement::List(list) => queue.extend(list.iter(), args), FormatElement::Fill(fill) => queue.queue.0.extend( diff --git a/crates/rome_js_formatter/src/utils/assignment_like.rs b/crates/rome_js_formatter/src/utils/assignment_like.rs index bf76a7b2be7a..6527d02ef609 100644 --- a/crates/rome_js_formatter/src/utils/assignment_like.rs +++ b/crates/rome_js_formatter/src/utils/assignment_like.rs @@ -929,7 +929,7 @@ impl Format for JsAnyAssignmentLike { } }); - let right = format_with(|f| self.write_right(f)).memoized(); + let right = format_with(|f| self.write_right(f)); let inner_content = format_with(|f| { write!(f, [left])?; @@ -949,8 +949,7 @@ impl Format for JsAnyAssignmentLike { group(&indent(&soft_line_break_or_space()),) .with_group_id(Some(group_id)), line_suffix_boundary(), - if_group_breaks(&indent(&right)).with_group_id(Some(group_id)), - if_group_fits_on_line(&right).with_group_id(Some(group_id)), + indent_if_break(&right, group_id) ] ] } diff --git a/crates/rome_js_formatter/src/utils/binary_like_expression.rs b/crates/rome_js_formatter/src/utils/binary_like_expression.rs index 25e9b3a3a68e..ddfc22566acd 100644 --- a/crates/rome_js_formatter/src/utils/binary_like_expression.rs +++ b/crates/rome_js_formatter/src/utils/binary_like_expression.rs @@ -149,13 +149,12 @@ impl Format for JsAnyBinaryLikeExpression { if last_is_jsx { // SAFETY: `last_is_jsx` is only true if parts is not empty - let jsx_element = parts.last().unwrap().memoized(); + let jsx_element = parts.last().unwrap(); write!( f, [group(&format_args![ format_non_jsx_parts, - if_group_breaks(&block_indent(&jsx_element)).with_group_id(Some(group_id)), - if_group_fits_on_line(&jsx_element).with_group_id(Some(group_id)) + indent_if_break(&jsx_element, group_id), ])] ) } else {