feat(rome_formatter): `IndentIfGroupBreaks` IR element
MODIFY
crates/rome_formatter/src/builders.rs
crates/rome_formatter/src/builders.rs
@@ -1714,6 +1714,126 @@ impl<Context> 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<SimpleFormatContext>| {
+/// 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_group_breaks(&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_group_breaks(&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_group_breaks<Content, Context>(
+ content: &Content,
+ group_id: GroupId,
+) -> IndentIfGroupBreaks<Context>
+where
+ Content: Format<Context>,
+{
+ IndentIfGroupBreaks {
+ group_id,
+ content: Argument::new(content),
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct IndentIfGroupBreaks<'a, Context> {
+ content: Argument<'a, Context>,
+ group_id: GroupId,
+}
+
+impl<Context> Format<Context> for IndentIfGroupBreaks<'_, Context> {
+ fn fmt(&self, f: &mut Formatter<Context>) -> 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::IndentIfGroupBreaks(
+ format_element::IndentIfGroupBreaks::new(content.into_boxed_slice(), self.group_id),
+ ))
+ }
+}
+
+impl<Context> std::fmt::Debug for IndentIfGroupBreaks<'_, Context> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("IndentIfGroupBreaks")
+ .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<Context, T> {

MODIFY
crates/rome_formatter/src/format_element.rs
crates/rome_formatter/src/format_element.rs
@@ -58,6 +58,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.
+ IndentIfGroupBreaks(IndentIfGroupBreaks),
+
/// Concatenates multiple elements together. See [crate::Formatter::join_with] for examples.
List(List),
@@ -176,6 +180,7 @@ impl std::fmt::Debug for FormatElement {
content.fmt(fmt)
}
FormatElement::ConditionalGroupContent(content) => content.fmt(fmt),
+ FormatElement::IndentIfGroupBreaks(content) => content.fmt(fmt),
FormatElement::List(content) => {
write!(fmt, "List ")?;
content.fmt(fmt)
@@ -508,6 +513,19 @@ impl std::fmt::Debug for Label {
}
}
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct IndentIfGroupBreaks {
+ pub(crate) content: Content,
+
+ pub(crate) group_id: GroupId,
+}
+
+impl IndentIfGroupBreaks {
+ 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,
@@ -668,6 +686,7 @@ impl FormatElement {
FormatElement::Line(line_mode) => matches!(line_mode, LineMode::Hard | LineMode::Empty),
FormatElement::Group(Group { content, .. })
| FormatElement::ConditionalGroupContent(ConditionalGroupContent { content, .. })
+ | FormatElement::IndentIfGroupBreaks(IndentIfGroupBreaks { content, .. })
| FormatElement::Comment(content)
| FormatElement::Fill(Fill { content, .. })
| FormatElement::Verbatim(Verbatim { content, .. })
@@ -884,6 +903,30 @@ impl Format<IrFormatContext> for FormatElement {
write!(f, [text(")")])
}
+ FormatElement::IndentIfGroupBreaks(content) => {
+ write!(
+ f,
+ [
+ text("indent_if_group_breaks("),
+ 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 => {

MODIFY
crates/rome_formatter/src/printer/mod.rs
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, IndentIfGroupBreaks, LineMode, PrintMode,
+ VerbatimKind,
};
use crate::intersperse::Intersperse;
use crate::{FormatElement, GroupId, IndentStyle, Printed, SourceMarker, TextRange};
@@ -212,6 +213,18 @@ impl<'a> Printer<'a> {
}
}
+ FormatElement::IndentIfGroupBreaks(IndentIfGroupBreaks { 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::IndentIfGroupBreaks(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(

MODIFY
crates/rome_js_formatter/src/utils/assignment_like.rs
crates/rome_js_formatter/src/utils/assignment_like.rs
@@ -936,7 +936,7 @@ impl Format<JsFormatContext> 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])?;
@@ -956,8 +956,7 @@ impl Format<JsFormatContext> 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_group_breaks(&right, group_id)
]
]
}

MODIFY
crates/rome_js_formatter/src/utils/binary_like_expression.rs
crates/rome_js_formatter/src/utils/binary_like_expression.rs
@@ -150,13 +150,12 @@ impl Format<JsFormatContext> 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_group_breaks(&jsx_element, group_id),
])]
)
} else {

feat(rome_formatter): `IndentIfGroupBreaks` IR element (#3116)