feat(rome_js_formatter): object pattern formatting
MODIFY
crates/rome_formatter/src/buffer.rs
crates/rome_formatter/src/buffer.rs
@@ -199,6 +199,11 @@ impl<'a, Context> VecBuffer<'a, Context> {
FormatElement::List(List::new(std::mem::take(&mut self.elements)))
}
}
+
+ /// Returns a reference to the current context
+ pub fn context(&self) -> &Context {
+ &self.state.context
+ }
}
impl<Context> Deref for VecBuffer<'_, Context> {

MODIFY
crates/rome_js_formatter/src/js/assignments/object_assignment_pattern.rs
crates/rome_js_formatter/src/js/assignments/object_assignment_pattern.rs
use crate::prelude::*;
-
+use crate::utils::JsObjectPatternLike;
use rome_formatter::write;
use rome_js_syntax::JsObjectAssignmentPattern;
-use rome_js_syntax::JsObjectAssignmentPatternFields;
#[derive(Debug, Clone, Default)]
pub struct FormatJsObjectAssignmentPattern;
@@ -13,18 +12,6 @@ impl FormatNodeRule<JsObjectAssignmentPattern> for FormatJsObjectAssignmentPatte
node: &JsObjectAssignmentPattern,
f: &mut JsFormatter,
) -> FormatResult<()> {
- let JsObjectAssignmentPatternFields {
- l_curly_token,
- properties,
- r_curly_token,
- } = node.as_fields();
-
- write!(
- f,
- [
- format_delimited(&l_curly_token?, &properties.format(), &r_curly_token?,)
- .soft_block_spaces()
- ]
- )
+ write!(f, [JsObjectPatternLike::from(node.clone())])
}
}

MODIFY
crates/rome_js_formatter/src/js/assignments/object_assignment_pattern_property.rs
crates/rome_js_formatter/src/js/assignments/object_assignment_pattern_property.rs
use crate::prelude::*;
+use crate::utils::JsAnyAssignmentLike;
use rome_formatter::write;
-
-use crate::utils::FormatMemberName;
use rome_js_syntax::JsObjectAssignmentPatternProperty;
-use rome_js_syntax::JsObjectAssignmentPatternPropertyFields;
#[derive(Debug, Clone, Default)]
pub struct FormatJsObjectAssignmentPatternProperty;
@@ -14,27 +12,6 @@ impl FormatNodeRule<JsObjectAssignmentPatternProperty> for FormatJsObjectAssignm
node: &JsObjectAssignmentPatternProperty,
f: &mut JsFormatter,
) -> FormatResult<()> {
- let JsObjectAssignmentPatternPropertyFields {
- member,
- colon_token,
- pattern,
- init,
- } = node.as_fields();
-
- write!(
- f,
- [
- FormatMemberName::from(member?),
- colon_token.format(),
- space_token(),
- pattern.format(),
- ]
- )?;
-
- if let Some(init) = init {
- write!(f, [space_token(), init.format()])?;
- }
-
- Ok(())
+ write!(f, [JsAnyAssignmentLike::from(node.clone())])
}
}

MODIFY
crates/rome_js_formatter/src/js/bindings/object_binding_pattern.rs
crates/rome_js_formatter/src/js/bindings/object_binding_pattern.rs
use crate::prelude::*;
-
+use crate::utils::JsObjectPatternLike;
use rome_formatter::write;
use rome_js_syntax::JsObjectBindingPattern;
-use rome_js_syntax::JsObjectBindingPatternFields;
#[derive(Debug, Clone, Default)]
pub struct FormatJsObjectBindingPattern;
impl FormatNodeRule<JsObjectBindingPattern> for FormatJsObjectBindingPattern {
fn fmt_fields(&self, node: &JsObjectBindingPattern, f: &mut JsFormatter) -> FormatResult<()> {
- let JsObjectBindingPatternFields {
- l_curly_token,
- properties,
- r_curly_token,
- } = node.as_fields();
-
- write!(
- f,
- [
- format_delimited(&l_curly_token?, &properties.format(), &r_curly_token?,)
- .soft_block_spaces()
- ]
- )
+ write!(f, [JsObjectPatternLike::from(node.clone())])
}
}

MODIFY
crates/rome_js_formatter/src/lib.rs
crates/rome_js_formatter/src/lib.rs
@@ -511,8 +511,9 @@ mod test {
// use this test check if your snippet prints as you wish, without using a snapshot
fn quick_test() {
let src = r#"
-bifornCringerMoshedPerplex =
- bifornCringerMoshedPerplexSawder = arrayOfNumb = a = "test"; "#;
+({aaaaaaaaaa,bbbbbbbbbb=cccccccccc,dddddddddd:eeeeeeeeee,ffffffffff:gggggggggg=hhhhhhhhhh,...jjjjjjjjjj} = x)
+
+ "#;
let syntax = SourceType::ts();
let tree = parse(src, 0, syntax);
let result = format_node(JsFormatContext::default(), &tree.syntax())

MODIFY
crates/rome_js_formatter/src/utils/assignment_like.rs
crates/rome_js_formatter/src/utils/assignment_like.rs
use crate::prelude::*;
use crate::utils::object::write_member_name;
use crate::utils::JsAnyBinaryLikeExpression;
-use rome_formatter::{format_args, write};
+use rome_formatter::{format_args, write, VecBuffer};
use rome_js_syntax::{
- JsAnyAssignmentPattern, JsAnyExpression, JsAnyObjectMemberName, JsAssignmentExpression,
+ JsAnyAssignmentPattern, JsAnyExpression, JsAnyFunctionBody, JsAnyObjectAssignmentPatternMember,
+ JsAnyObjectBindingPatternMember, JsAnyObjectMemberName, JsAssignmentExpression,
+ JsObjectAssignmentPattern, JsObjectAssignmentPatternProperty, JsObjectBindingPattern,
JsPropertyObjectMember, JsSyntaxKind,
};
use rome_js_syntax::{JsAnyLiteralExpression, JsSyntaxNode};
use rome_rowan::{declare_node_union, AstNode, SyntaxResult};
declare_node_union! {
- pub(crate) JsAnyAssignmentLike = JsPropertyObjectMember | JsAssignmentExpression
+ pub(crate) JsAnyAssignmentLike = JsPropertyObjectMember |
+ JsAssignmentExpression |
+ JsObjectAssignmentPatternProperty
}
@@ -18,12 +22,100 @@ declare_node_union! {
pub(crate) LeftAssignmentLike = JsAnyAssignmentPattern | JsAnyObjectMemberName
}
+declare_node_union! {
+ pub(crate) RightAssignmentLike = JsAnyExpression | JsAnyAssignmentPattern
+}
+
+declare_node_union! {
+ /// This is a convenient enum to map object patterns.
+ pub(crate) AnyObjectPattern = JsObjectAssignmentPattern | JsObjectBindingPattern
+}
+
+impl AnyObjectPattern {
+ fn is_complex(&self) -> SyntaxResult<bool> {
+ match self {
+ AnyObjectPattern::JsObjectAssignmentPattern(assignment_pattern) => {
+ let properties_len = assignment_pattern.properties().len();
+ if properties_len <= 2 {
+ return Ok(false);
+ }
+ // A binding is complex when we have at least one [JsObjectBindingPatternProperty]
+ // e.g. a = { a: c = f } = a
+ // The `c = f` will trigger the complex binding
+ let has_at_least_a_complex_binding = assignment_pattern
+ .properties()
+ .iter()
+ .map(|p| p.ok())
+ .any(|property| {
+ let property = property;
+
+ matches!(
+ property,
+ Some(
+ JsAnyObjectAssignmentPatternMember::JsObjectAssignmentPatternProperty(_),
+ )
+ )
+ });
+ Ok(has_at_least_a_complex_binding)
+ }
+ AnyObjectPattern::JsObjectBindingPattern(binding_pattern) => {
+ let properties_len = binding_pattern.properties().len();
+ if properties_len <= 2 {
+ return Ok(false);
+ }
+ // A binding is complex when we have at least one [JsObjectBindingPatternProperty]
+ // e.g. const a = { a: c = f } = a
+ // The `c = f` will trigger the complex binding
+ let has_at_least_a_complex_binding = binding_pattern
+ .properties()
+ .iter()
+ .map(|p| p.ok())
+ .any(|property| {
+ let property = property;
+
+ matches!(
+ property,
+ Some(
+ JsAnyObjectBindingPatternMember::JsObjectBindingPatternProperty(_),
+ )
+ )
+ });
+ Ok(has_at_least_a_complex_binding)
+ }
+ }
+ }
+}
+
+impl LeftAssignmentLike {
+ fn as_object_assignment_pattern(&self) -> Option<AnyObjectPattern> {
+ match self {
+ LeftAssignmentLike::JsAnyAssignmentPattern(
+ JsAnyAssignmentPattern::JsObjectAssignmentPattern(node),
+ ) => Some(AnyObjectPattern::from(node.clone())),
+ _ => None,
+ }
+ }
+}
+
+impl Format<JsFormatContext> for RightAssignmentLike {
+ fn fmt(&self, f: &mut Formatter<JsFormatContext>) -> FormatResult<()> {
+ match self {
+ RightAssignmentLike::JsAnyExpression(expression) => {
+ write!(f, [expression.format()])
+ }
+ RightAssignmentLike::JsAnyAssignmentPattern(assignment) => {
+ write!(f, [assignment.format()])
+ }
+ }
+ }
+}
+
/// Determines how a assignment like be formatted
///
/// Assignment like are:
/// - Assignment
/// - Object property member
-#[derive(Debug)]
+#[derive(Debug, Eq, PartialEq)]
pub(crate) enum AssignmentLikeLayout {
/// First break right-hand side, then after operator.
/// ```js
@@ -82,13 +174,60 @@ pub(crate) enum AssignmentLikeLayout {
///
/// Given the previous snippet, then `"foo"` formatted using the [ChainTail] layout.
ChainTail,
+
+ /// This layout is used in cases where we want to "break" the left hand side
+ /// of assignment like expression, but only when the group decides to do it.
+ ///
+ /// ```js
+ /// const a {
+ /// loreum: { ipsum },
+ /// something_else,
+ /// happy_days: { fonzy }
+ /// } = obj;
+ /// ```
+ ///
+ /// The snippet triggers the layout because the left hand side contains a "complex destructuring"
+ /// which requires having the properties broke on different lines.
+ BreakLeftHandSide,
+
+ /// This is a special case of the "chain" layout collection. This is triggered when there's
+ /// a series of simple assignments (at least three) and in the middle we have an arrow function
+ /// and this function followed by two more arrow functions.
+ ///
+ /// This layout will break the right hand side of the tail on a new line and add a new level
+ /// of indentation
+ ///
+ /// ```js
+ /// lorem =
+ /// fff =
+ /// ee =
+ /// () => (fff) => () => (fefef) => () => fff;
+ /// ```
+ ChainTailArrowFunction,
}
impl JsAnyAssignmentLike {
- fn right(&self) -> SyntaxResult<JsAnyExpression> {
+ fn right(&self) -> SyntaxResult<RightAssignmentLike> {
match self {
- JsAnyAssignmentLike::JsPropertyObjectMember(property) => property.value(),
- JsAnyAssignmentLike::JsAssignmentExpression(assignment) => assignment.right(),
+ JsAnyAssignmentLike::JsPropertyObjectMember(property) => Ok(property.value()?.into()),
+ JsAnyAssignmentLike::JsAssignmentExpression(assignment) => {
+ Ok(assignment.right()?.into())
+ }
+ JsAnyAssignmentLike::JsObjectAssignmentPatternProperty(assignment_pattern) => {
+ Ok(assignment_pattern.pattern()?.into())
+ }
+ }
+ }
+
+ fn left(&self) -> SyntaxResult<LeftAssignmentLike> {
+ match self {
+ JsAnyAssignmentLike::JsPropertyObjectMember(property) => Ok(property.name()?.into()),
+ JsAnyAssignmentLike::JsAssignmentExpression(assignment) => {
+ Ok(assignment.left()?.into())
+ }
+ JsAnyAssignmentLike::JsObjectAssignmentPatternProperty(property) => {
+ Ok(property.pattern()?.into())
+ }
}
}
}
@@ -96,19 +235,25 @@ impl JsAnyAssignmentLike {
const MIN_OVERLAP_FOR_BREAK: u8 = 3;
impl JsAnyAssignmentLike {
- fn write_left(&self, f: &mut JsFormatter) -> FormatResult<bool> {
+ fn write_left(&self, buffer: &mut VecBuffer<JsFormatContext>) -> FormatResult<bool> {
match self {
JsAnyAssignmentLike::JsPropertyObjectMember(property) => {
- let width = write_member_name(&property.name()?, f)?;
+ let width = write_member_name(&property.name()?, buffer)?;
let text_width_for_break =
- (f.context().tab_width() + MIN_OVERLAP_FOR_BREAK) as usize;
+ (buffer.context().tab_width() + MIN_OVERLAP_FOR_BREAK) as usize;
Ok(width < text_width_for_break)
}
JsAnyAssignmentLike::JsAssignmentExpression(assignment) => {
let left = assignment.left()?;
- write!(f, [group_elements(&left.format())])?;
+ write!(buffer, [&left.format()])?;
Ok(false)
}
+ JsAnyAssignmentLike::JsObjectAssignmentPatternProperty(property) => {
+ let width = write_member_name(&property.member()?, buffer)?;
+ let text_width_for_break =
+ (buffer.context().tab_width() + MIN_OVERLAP_FOR_BREAK) as usize;
+ Ok(width < text_width_for_break)
+ }
}
}
@@ -122,6 +267,32 @@ impl JsAnyAssignmentLike {
let operator_token = assignment.operator_token()?;
write!(f, [space_token(), operator_token.format()])
}
+ JsAnyAssignmentLike::JsObjectAssignmentPatternProperty(property) => {
+ let colon_token = property.colon_token()?;
+ write!(f, [colon_token.format()])
+ }
+ }
+ }
+
+ fn write_right(&self, f: &mut JsFormatter) -> FormatResult<()> {
+ match self {
+ JsAnyAssignmentLike::JsPropertyObjectMember(property) => {
+ let value = property.value()?;
+ write!(f, [value.format()])
+ }
+ JsAnyAssignmentLike::JsAssignmentExpression(assignment) => {
+ let right = assignment.right()?;
+ write!(f, [space_token(), right.format()])
+ }
+ JsAnyAssignmentLike::JsObjectAssignmentPatternProperty(property) => {
+ let pattern = property.pattern()?;
+ let init = property.init();
+ write!(f, [pattern.format()])?;
+ if let Some(init) = init {
+ write!(f, [space_token(), init.format()])?;
+ }
+ Ok(())
+ }
}
}
@@ -130,30 +301,45 @@ impl JsAnyAssignmentLike {
fn layout(&self, is_left_short: bool) -> FormatResult<AssignmentLikeLayout> {
let right = self.right()?;
if let Some(layout) = self.chain_formatting_layout()? {
- Ok(layout)
- } else if is_break_after_operator(&right)? {
- Ok(AssignmentLikeLayout::BreakAfterOperator)
- } else if is_left_short {
- Ok(AssignmentLikeLayout::NeverBreakAfterOperator)
- } else if matches!(
+ return Ok(layout);
+ }
+
+ if self.should_break_left_hand_side()? {
+ return Ok(AssignmentLikeLayout::BreakLeftHandSide);
+ }
+
+ if let RightAssignmentLike::JsAnyExpression(expression) = &right {
+ if is_break_after_operator(expression)? {
+ return Ok(AssignmentLikeLayout::BreakAfterOperator);
+ }
+ }
+ if is_left_short {
+ return Ok(AssignmentLikeLayout::NeverBreakAfterOperator);
+ }
+
+ if matches!(
right,
- JsAnyExpression::JsAnyLiteralExpression(
+ RightAssignmentLike::JsAnyExpression(JsAnyExpression::JsAnyLiteralExpression(
JsAnyLiteralExpression::JsStringLiteralExpression(_)
- )
+ )),
) {
- Ok(AssignmentLikeLayout::BreakAfterOperator)
- } else if self.is_never_break_after_operator()? {
- Ok(AssignmentLikeLayout::NeverBreakAfterOperator)
- } else {
- Ok(AssignmentLikeLayout::Fluid)
+ return Ok(AssignmentLikeLayout::BreakAfterOperator);
}
+
+ if self.is_never_break_after_operator()? {
+ return Ok(AssignmentLikeLayout::NeverBreakAfterOperator);
+ }
+ Ok(AssignmentLikeLayout::Fluid)
}
/// Checks if the right node is entitled of the chain formatting,
/// and if so, it return the layout type
fn chain_formatting_layout(&self) -> SyntaxResult<Option<AssignmentLikeLayout>> {
let right = self.right()?;
- let right_is_tail = !matches!(right, JsAnyExpression::JsAssignmentExpression(_));
+ let right_is_tail = !matches!(
+ right,
+ RightAssignmentLike::JsAnyExpression(JsAnyExpression::JsAssignmentExpression(_))
+ );
// The chain goes up two levels, by checking up to the great parent if all the conditions
// are correctly met.
let upper_chain_is_eligible =
@@ -188,10 +374,28 @@ impl JsAnyAssignmentLike {
};
let result = if upper_chain_is_eligible {
- if right_is_tail {
- Some(AssignmentLikeLayout::ChainTail)
- } else {
+ if !right_is_tail {
Some(AssignmentLikeLayout::Chain)
+ } else {
+ match right {
+ RightAssignmentLike::JsAnyExpression(
+ JsAnyExpression::JsArrowFunctionExpression(arrow),
+ ) => {
+ let this_body = arrow.body()?;
+ if matches!(
+ this_body,
+ JsAnyFunctionBody::JsAnyExpression(
+ JsAnyExpression::JsArrowFunctionExpression(_)
+ )
+ ) {
+ Some(AssignmentLikeLayout::ChainTailArrowFunction)
+ } else {
+ Some(AssignmentLikeLayout::ChainTail)
+ }
+ }
+
+ _ => Some(AssignmentLikeLayout::ChainTail),
+ }
}
} else {
None
@@ -202,6 +406,7 @@ impl JsAnyAssignmentLike {
fn is_never_break_after_operator(&self) -> SyntaxResult<bool> {
let right = self.right()?;
+ if let RightAssignmentLike::JsAnyExpression(right) = &right {
if let JsAnyExpression::JsCallExpression(call_expression) = &right {
if call_expression.callee()?.syntax().text() == "require" {
return Ok(true);
@@ -221,9 +426,27 @@ impl JsAnyAssignmentLike {
) {
return Ok(true);
}
+ }
Ok(false)
}
+
+ /// Particular function that checks if the left hand side of a [JsAnyAssignmentLike] should
+ /// be broken on multiple lines
+ fn should_break_left_hand_side(&self) -> SyntaxResult<bool> {
+ // TODO: here we have to add the check for variable declarator too
+ let is_complex_destructuring = if let JsAnyAssignmentLike::JsAssignmentExpression(_) = self
+ {
+ self.left()?
+ .as_object_assignment_pattern()
+ .and_then(|pattern| pattern.is_complex().ok())
+ .unwrap_or(false)
+ } else {
+ false
+ };
+
+ Ok(is_complex_destructuring)
+ }
}
/// Checks if the function is entitled to be printed with layout [AssignmentLikeLayout::BreakAfterOperator]
@@ -278,22 +501,50 @@ pub(crate) fn has_new_line_before_comment(node: &JsSyntaxNode) -> bool {
impl Format<JsFormatContext> for JsAnyAssignmentLike {
fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> {
- let right = self.right()?;
let format_content = format_with(|f| {
+ // We create a temporary buffer because the left hand side has to conditionally add
+ // a group based on the layout, but the layout can only be computed by knowing the
+ // width of the left hand side. The left hand side can be a member, and that has a width
+ // can can be known only when it's formatted (it can incur in some transformation,
+ // like removing some escapes, etc.).
+ //
+ // 1. we crate a temporary buffer
+ // 2. we write the left hand side into the buffer and retrieve the `is_left_short` info
+ // which is computed only when we format it
+ // 3. we compute the layout
+ // 4. we write the left node inside the main buffer based on the layout
+ let mut buffer = VecBuffer::new(f.state_mut());
+ let is_left_short = self.write_left(&mut buffer)?;
+
// Compare name only if we are in a position of computing it.
// If not (for example, left is not an identifier), then let's fallback to false,
// so we can continue the chain of checks
- let is_left_short = self.write_left(f)?;
+ let layout = self.layout(is_left_short)?;
+
+ let formatted_element = buffer.into_element();
+
+ if layout == AssignmentLikeLayout::BreakLeftHandSide {
+ write!(
+ f,
+ [&format_once(|f| { f.write_element(formatted_element) })]
+ )?;
+ } else {
+ write!(
+ f,
+ [group_elements(&format_once(|f| {
+ f.write_element(formatted_element)
+ }))]
+ )?;
+ }
+
self.write_operator(f)?;
- let layout = self.layout(is_left_short)?;
+ let right = &format_with(|f| self.write_right(f)).memoized();
let inner_content = format_with(|f| match &layout {
AssignmentLikeLayout::Fluid => {
let group_id = f.group_id("assignment_like");
- let right = right.format().memoized();
-
write![
f,
[
@@ -310,25 +561,39 @@ impl Format<JsFormatContext> for JsAnyAssignmentLike {
f,
[group_elements(&indent(&format_args![
soft_line_break_or_space(),
- right.format()
+ right,
])),]
]
}
AssignmentLikeLayout::NeverBreakAfterOperator => {
- write![f, [space_token(), right.format(),]]
+ write![f, [space_token(), right,]]
+ }
+
+ AssignmentLikeLayout::BreakLeftHandSide => {
+ write![f, [space_token(), group_elements(right),]]
}
AssignmentLikeLayout::Chain => {
- write!(f, [soft_line_break_or_space(), right.format()])
+ write!(f, [soft_line_break_or_space(), right,])
}
AssignmentLikeLayout::ChainTail => {
write!(
f,
- [&indent(&format_args![
- soft_line_break_or_space(),
- right.format()
- ])]
+ [&indent(&format_args![soft_line_break_or_space(), right,])]
+ )
+ }
+
+ AssignmentLikeLayout::ChainTailArrowFunction => {
+ let group_id = f.group_id("arrow_chain");
+
+ write!(
+ f,
+ [
+ space_token(),
+ group_elements(&indent(&format_args![hard_line_break(), right]))
+ .with_group_id(Some(group_id)),
+ ]
)
}
});

MODIFY
crates/rome_js_formatter/src/utils/mod.rs
crates/rome_js_formatter/src/utils/mod.rs
@@ -8,6 +8,7 @@ pub mod string_utils;
pub(crate) mod format_class;
mod member_chain;
mod object;
+mod object_pattern_like;
#[cfg(test)]
mod quickcheck_utils;
@@ -16,6 +17,7 @@ pub(crate) use assignment_like::{is_break_after_operator, JsAnyAssignmentLike};
pub(crate) use binary_like_expression::{format_binary_like_expression, JsAnyBinaryLikeExpression};
pub(crate) use format_conditional::{format_conditional, Conditional};
pub(crate) use member_chain::format_call_expression;
+pub(crate) use object_pattern_like::JsObjectPatternLike;
use rome_formatter::{format_args, normalize_newlines, write, Buffer, VecBuffer};
use rome_js_syntax::suppression::{has_suppressions_category, SuppressionCategory};
use rome_js_syntax::JsSyntaxKind::JS_STRING_LITERAL;

MODIFY
crates/rome_js_formatter/src/utils/object.rs
crates/rome_js_formatter/src/utils/object.rs
@@ -2,7 +2,7 @@ use crate::prelude::*;
use crate::utils::FormatLiteralStringToken;
use crate::utils::StringLiteralParentKind;
-use rome_formatter::write;
+use rome_formatter::{write, VecBuffer};
use rome_js_syntax::JsAnyObjectMemberName;
use rome_js_syntax::JsSyntaxKind::JS_STRING_LITERAL;
use rome_rowan::AstNode;
@@ -10,7 +10,7 @@ use unicode_width::UnicodeWidthStr;
pub(crate) fn write_member_name(
name: &JsAnyObjectMemberName,
- f: &mut JsFormatter,
+ buffer: &mut VecBuffer<JsFormatContext>,
) -> FormatResult<usize> {
match name {
name @ JsAnyObjectMemberName::JsLiteralMemberName(literal) => {
@@ -18,19 +18,19 @@ pub(crate) fn write_member_name(
if value.kind() == JS_STRING_LITERAL {
let format = FormatLiteralStringToken::new(&value, StringLiteralParentKind::Member);
- let cleaned = format.clean_text(f.context());
+ let cleaned = format.clean_text(buffer.context());
- cleaned.fmt(f)?;
+ write!(buffer, [cleaned])?;
Ok(cleaned.width())
} else {
- name.format().fmt(f)?;
+ write!(buffer, [name.format()])?;
Ok(value.text_trimmed().width())
}
}
name => {
- write!(f, [group_elements(&name.format())])?;
+ write!(buffer, [&name.format()])?;
Ok(name.text().width())
}
}

ADD
crates/rome_js_formatter/src/utils/object_pattern_like.rs
+use crate::prelude::*;
+use crate::JsFormatContext;
+use rome_formatter::formatter::Formatter;
+use rome_formatter::write;
+use rome_formatter::{Format, FormatResult};
+use rome_js_syntax::{
+ JsAnyAssignmentPattern, JsAnyBindingPattern, JsAnyObjectAssignmentPatternMember,
+ JsAnyObjectBindingPatternMember, JsObjectAssignmentPattern, JsObjectBindingPattern,
+ JsSyntaxKind, JsSyntaxToken,
+};
+use rome_rowan::{declare_node_union, AstNode, SyntaxResult};
+
+declare_node_union! {
+ pub (crate) JsObjectPatternLike = JsObjectAssignmentPattern | JsObjectBindingPattern
+}
+
+impl JsObjectPatternLike {
+ fn l_curly_token(&self) -> SyntaxResult<JsSyntaxToken> {
+ match self {
+ JsObjectPatternLike::JsObjectAssignmentPattern(node) => node.l_curly_token(),
+ JsObjectPatternLike::JsObjectBindingPattern(node) => node.l_curly_token(),
+ }
+ }
+
+ fn r_curly_token(&self) -> SyntaxResult<JsSyntaxToken> {
+ match self {
+ JsObjectPatternLike::JsObjectAssignmentPattern(node) => node.r_curly_token(),
+ JsObjectPatternLike::JsObjectBindingPattern(node) => node.r_curly_token(),
+ }
+ }
+
+ fn properties_len(&self) -> usize {
+ match self {
+ JsObjectPatternLike::JsObjectAssignmentPattern(node) => node.properties().len(),
+ JsObjectPatternLike::JsObjectBindingPattern(node) => node.properties().len(),
+ }
+ }
+
+ fn write_properties(&self, f: &mut JsFormatter) -> FormatResult<()> {
+ match self {
+ JsObjectPatternLike::JsObjectAssignmentPattern(node) => {
+ write!(f, [node.properties().format()])
+ }
+ JsObjectPatternLike::JsObjectBindingPattern(node) => {
+ write!(f, [node.properties().format()])
+ }
+ }
+ }
+
+ fn should_break_properties(&self) -> SyntaxResult<bool> {
+ let has_at_least_a_complex_property = match self {
+ JsObjectPatternLike::JsObjectAssignmentPattern(node) => {
+ node.properties().iter().any(|property| {
+ if let Ok(
+ JsAnyObjectAssignmentPatternMember::JsObjectAssignmentPatternProperty(node),
+ ) = property
+ {
+ let pattern = node.pattern();
+ matches!(
+ pattern,
+ Ok(JsAnyAssignmentPattern::JsObjectAssignmentPattern(_)
+ | JsAnyAssignmentPattern::JsArrayAssignmentPattern(_))
+ )
+ } else {
+ false
+ }
+ })
+ }
+ JsObjectPatternLike::JsObjectBindingPattern(node) => {
+ node.properties().iter().any(|property| {
+ if let Ok(JsAnyObjectBindingPatternMember::JsObjectBindingPatternProperty(
+ node,
+ )) = property
+ {
+ let pattern = node.pattern();
+ matches!(
+ pattern,
+ Ok(JsAnyBindingPattern::JsObjectBindingPattern(_)
+ | JsAnyBindingPattern::JsArrayBindingPattern(_))
+ )
+ } else {
+ false
+ }
+ })
+ }
+ };
+
+ let parent_kind = self.syntax().parent().map(|p| p.kind());
+
+ let parent_where_not_to_break = !matches!(
+ parent_kind,
+ Some(
+ // These parents are the kinds where we want to prevent
+ // to go to multiple lines.
+ JsSyntaxKind::JS_FUNCTION_EXPRESSION
+ | JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION
+ | JsSyntaxKind::JS_OBJECT_ASSIGNMENT_PATTERN_PROPERTY
+ | JsSyntaxKind::JS_CATCH_DECLARATION
+ | JsSyntaxKind::JS_FUNCTION_DECLARATION
+ | JsSyntaxKind::JS_OBJECT_BINDING_PATTERN_PROPERTY
+ | JsSyntaxKind::JS_FORMAL_PARAMETER
+ )
+ );
+
+ Ok(parent_where_not_to_break && has_at_least_a_complex_property)
+ }
+
+ fn is_in_assignment_like(&self) -> bool {
+ if let JsObjectPatternLike::JsObjectAssignmentPattern(pattern) = self {
+ let parent_kind = pattern.syntax().parent().map(|p| p.kind());
+ matches!(
+ parent_kind,
+ Some(JsSyntaxKind::JS_ASSIGNMENT_EXPRESSION | JsSyntaxKind::JS_VARIABLE_DECLARATOR)
+ )
+ } else {
+ false
+ }
+ }
+}
+
+impl Format<JsFormatContext> for JsObjectPatternLike {
+ fn fmt(&self, f: &mut Formatter<JsFormatContext>) -> FormatResult<()> {
+ let should_break_properties = self.should_break_properties()?;
+ let right = &format_with(|f| self.write_properties(f));
+ let is_in_assignment_like = self.is_in_assignment_like();
+ let properties_len = self.properties_len();
+
+ if should_break_properties {
+ write!(
+ f,
+ [
+ &self.l_curly_token()?.format(),
+ &block_indent(right),
+ &self.r_curly_token()?.format(),
+ ]
+ )
+ } else if !should_break_properties && is_in_assignment_like {
+ // no need to add a group if we know the parent already does that
+ write!(f, [&self.l_curly_token()?.format()])?;
+ if properties_len > 0 {
+ write!(f, [soft_line_break_or_space()])?;
+ write!(f, [soft_block_indent(right)])?;
+ write!(f, [soft_line_break_or_space()])?;
+ } else {
+ write!(f, [right])?;
+ }
+ write!(f, [&self.r_curly_token()?.format()])
+ } else {
+ write!(
+ f,
+ [
+ format_delimited(&self.l_curly_token()?, right, &self.r_curly_token()?,)
+ .soft_block_spaces()
+ ]
+ )
+ }
+ }
+}

MODIFY
crates/rome_js_formatter/tests/specs/js/module/arrow/params.js.snap
crates/rome_js_formatter/tests/specs/js/module/arrow/params.js.snap
@@ -364,9 +364,21 @@ foo(({ a: { b: { c, d } } }) => a);
foo(({ a: { b: { c: { d, e } } } }) => a);
-foo(([{ a: { b: { c: { d, e } } } }]) => {});
+foo((
+ [
+ {
+ a: { b: { c: { d, e } } },
+ },
+ ],
+) => {});
-foo(([...{ a: { b: { c: { d, e } } } }]) => {});
+foo((
+ [
+ ...{
+ a: { b: { c: { d, e } } },
+ }
+ ],
+) => {});
foo((
n = {
@@ -398,7 +410,20 @@ foo(
foo(([[{ a, b }]]) => {});
-foo(([[[[{ a, b: { c, d: { e, f } } }]]]]) => {});
+foo((
+ [
+ [
+ [
+ [
+ {
+ a,
+ b: { c, d: { e, f } },
+ },
+ ],
+ ],
+ ],
+ ],
+) => {});
foo((...{ a, b }) => {});

MODIFY
crates/rome_js_formatter/tests/specs/js/module/assignment/assignment.js
crates/rome_js_formatter/tests/specs/js/module/assignment/assignment.js
@@ -116,3 +116,23 @@ bifornCringerMoshedPerplex = bifornCringerMoshedPerplexSawder = arrayOfNumb = a
// chain and chain tail where it doesn't break
loreum = ipsum = arrayOfNumb = a = "test"
+// chain tail arrow function
+lorem = fff = ee = () => (fff) => () => (fefef) => () => fff;
+
+// complex destructuring, break left hand
+a = {
+ a: { t: c = b }, loreum, ipsurm
+} = {}
+
+a =
+// rome-ignore format: test
+{
+ a: { t: c = b }, loreum, ipsurm
+} = {}
+
+a =
+ {
+// rome-ignore format: test
+ a: { t: c = b }, loreum, ipsurm
+ } = {}
+

MODIFY
crates/rome_js_formatter/tests/specs/js/module/assignment/assignment.js.snap
crates/rome_js_formatter/tests/specs/js/module/assignment/assignment.js.snap
@@ -122,6 +122,26 @@ bifornCringerMoshedPerplex = bifornCringerMoshedPerplexSawder = arrayOfNumb = a
// chain and chain tail where it doesn't break
loreum = ipsum = arrayOfNumb = a = "test"
+// chain tail arrow function
+lorem = fff = ee = () => (fff) => () => (fefef) => () => fff;
+
+// complex destructuring, break left hand
+a = {
+ a: { t: c = b }, loreum, ipsurm
+} = {}
+
+a =
+// rome-ignore format: test
+{
+ a: { t: c = b }, loreum, ipsurm
+} = {}
+
+a =
+ {
+// rome-ignore format: test
+ a: { t: c = b }, loreum, ipsurm
+ } = {}
+
=============================
# Outputs
@@ -299,6 +319,32 @@ bifornCringerMoshedPerplex =
// chain and chain tail where it doesn't break
loreum = ipsum = arrayOfNumb = a = "test";
+// chain tail arrow function
+lorem =
+ fff =
+ ee =
+ () => (fff) => () => (fefef) => () => fff;
+
+// complex destructuring, break left hand
+a = {
+ a: { t: c = b },
+ loreum,
+ ipsurm,
+} = {};
+
+a =
+ // rome-ignore format: test
+{
+ a: { t: c = b }, loreum, ipsurm
+} = {};
+
+a = {
+ // rome-ignore format: test
+ a: { t: c = b },
+ loreum,
+ ipsurm,
+} = {};
+
## Lines exceeding width of 80 characters

MODIFY
crates/rome_js_formatter/tests/specs/prettier/js/destructuring-private-fields/nested-bindings.js.snap
crates/rome_js_formatter/tests/specs/prettier/js/destructuring-private-fields/nested-bindings.js.snap
---
source: crates/rome_js_formatter/tests/prettier_tests.rs
+assertion_line: 184
expression: nested-bindings.js
---
# Input
@@ -18,7 +19,10 @@ class C {
class C {
#x = 1;
m() {
- const { x: { #x: [x] }, y: [...{ #x: y }] } = this;
+ const {
+ x: { #x: [x] },
+ y: [...{ #x: y }],
+ } = this;
}
}

MODIFY
crates/rome_js_formatter/tests/specs/prettier/js/destructuring/destructuring.js.snap
crates/rome_js_formatter/tests/specs/prettier/js/destructuring/destructuring.js.snap
---
source: crates/rome_js_formatter/tests/prettier_tests.rs
+assertion_line: 184
expression: destructuring.js
---
# Input
@@ -58,7 +59,9 @@ const [one, two = null, three = null] = arr;
a = ([s = 1]) => 1;
const { children, ...props } = this.props;
-const { user: { firstName, lastName } } = this.props;
+const {
+ user: { firstName, lastName },
+} = this.props;
const {
name: { first, last },
@@ -80,7 +83,12 @@ const UserComponent = function (
return;
};
-const { a, b, c, d: { e } } = someObject;
+const {
+ a,
+ b,
+ c,
+ d: { e },
+} = someObject;
try {
// code

feat(rome_js_formatter): object pattern formatting (#2729)