From 80321df0287c35b19b76576642c1e86258711309 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 12 Aug 2022 09:45:21 +0200 Subject: [PATCH] feat(rome_js_formatter): member chain parentheses --- .../expressions/arrow_function_expression.rs | 114 ++-- .../js/expressions/assignment_expression.rs | 4 +- .../src/js/expressions/await_expression.rs | 2 +- .../src/js/expressions/call_expression.rs | 86 ++-- .../src/js/expressions/class_expression.rs | 4 +- .../expressions/computed_member_expression.rs | 40 +- .../js/expressions/conditional_expression.rs | 4 +- .../src/js/expressions/function_expression.rs | 7 +- .../expressions/number_literal_expression.rs | 2 +- .../src/js/expressions/object_expression.rs | 12 +- .../expressions/parenthesized_expression.rs | 7 +- .../js/expressions/post_update_expression.rs | 2 +- .../js/expressions/pre_update_expression.rs | 2 +- .../src/js/expressions/sequence_expression.rs | 18 +- .../expressions/static_member_expression.rs | 66 ++- .../expressions/string_literal_expression.rs | 2 +- .../src/js/expressions/unary_expression.rs | 2 +- .../src/js/expressions/yield_expression.rs | 2 +- .../src/jsx/expressions/tag_expression.rs | 69 ++- crates/rome_js_formatter/src/parentheses.rs | 221 +++++--- .../non_null_assertion_expression.rs | 17 +- crates/rome_js_formatter/src/utils/jsx.rs | 50 +- .../{flatten_item.rs => chain_member.rs} | 192 +++++-- .../src/utils/member_chain/groups.rs | 237 +++++---- .../src/utils/member_chain/mod.rs | 485 ++++++++++-------- crates/rome_js_formatter/src/utils/mod.rs | 2 +- .../expression/sequence_expression.js.snap | 16 +- .../prettier/js/assignment/issue-2184.js.snap | 20 +- .../prettier/js/async/inline-await.js.snap | 8 +- .../js/binary-expressions/call.js.snap | 123 ++--- .../prettier/js/classes/assignment.js.snap | 18 +- .../closure-compiler-type-cast.js.snap | 49 +- .../comment-in-the-middle.js.snap | 9 +- .../js/comments-closure-typecast/iife.js.snap | 21 +- .../issue-4124.js.snap | 31 +- .../comments-closure-typecast/member.js.snap | 4 +- .../comments-closure-typecast/nested.js.snap | 4 +- .../styled-components.js.snap | 5 +- .../specs/prettier/js/comments/issues.js.snap | 37 +- .../js/comments/return-statement.js.snap | 35 +- .../js/export-default/class_instance.js.snap | 4 +- .../prettier/js/function/issue-10277.js.snap | 40 -- .../prettier/js/method-chain/logical.js.snap | 69 +-- .../prettier/js/method-chain/test.js.snap | 4 +- .../js/new-expression/new_expression.js.snap | 4 +- .../specs/prettier/js/no-semi/no-semi.js.snap | 9 +- .../js/optional-chaining/chaining.js.snap | 145 +++--- .../js/preserve-line/member-chain.js.snap | 33 +- .../prettier/js/sequence-break/break.js.snap | 50 +- .../prettier/js/ternaries/binary.js.snap | 48 +- .../js/ternaries/indent-after-paren.js.snap | 112 ++-- .../prettier/js/ternaries/nested.js.snap | 35 +- .../js/unary-expression/comments.js.snap | 98 ++-- .../typescript/arrow/arrow_regression.ts.snap | 4 +- .../typescript/as/nested-await-and-as.ts.snap | 21 +- .../functionCalls/callWithSpreadES6.ts.snap | 137 ----- .../typescript/non-null/braces.ts.snap | 20 +- .../non-null/optional-chain.ts.snap | 27 +- .../typescript/non-null/parens.ts.snap | 83 --- crates/rome_js_syntax/src/expr_ext.rs | 74 ++- 60 files changed, 1574 insertions(+), 1472 deletions(-) rename crates/rome_js_formatter/src/utils/member_chain/{flatten_item.rs => chain_member.rs} (54%) delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/function/issue-10277.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/expressions/functionCalls/callWithSpreadES6.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/non-null/parens.ts.snap diff --git a/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs b/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs index c0187fe79ad..21a0d589a54 100644 --- a/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/arrow_function_expression.rs @@ -6,8 +6,7 @@ use crate::parentheses::{ NeedsParentheses, }; use crate::utils::{ - is_simple_expression, resolve_expression, resolve_left_most_expression, - JsAnyBinaryLikeLeftExpression, + resolve_expression, resolve_left_most_expression, JsAnyBinaryLikeLeftExpression, }; use rome_js_syntax::{ JsAnyArrowFunctionParameters, JsAnyExpression, JsAnyFunctionBody, JsArrowFunctionExpression, @@ -35,36 +34,37 @@ impl FormatNodeRule for FormatJsArrowFunctionExpressi body, } = node.as_fields(); - if let Some(async_token) = async_token { - write!(f, [async_token.format(), space()])?; - } + let format_signature = format_with(|f| { + if let Some(async_token) = &async_token { + write!(f, [async_token.format(), space()])?; + } - write!(f, [type_parameters.format()])?; + write!(f, [type_parameters.format()])?; - match parameters? { - JsAnyArrowFunctionParameters::JsAnyBinding(binding) => write!( - f, - [format_parenthesize( - binding.syntax().first_token().as_ref(), - &format_args![binding.format(), if_group_breaks(&text(",")),], - binding.syntax().last_token().as_ref(), - ) - .grouped_with_soft_block_indent()] - )?, - JsAnyArrowFunctionParameters::JsParameters(params) => { - write![f, [group(¶ms.format())]]? + match parameters.as_ref()? { + JsAnyArrowFunctionParameters::JsAnyBinding(binding) => write!( + f, + [format_parenthesize( + binding.syntax().first_token().as_ref(), + &format_args![binding.format(), if_group_breaks(&text(",")),], + binding.syntax().last_token().as_ref(), + ) + .grouped_with_soft_block_indent()] + )?, + JsAnyArrowFunctionParameters::JsParameters(params) => { + write![f, [group(¶ms.format())]]? + } } - } - write![ - f, - [ - return_type_annotation.format(), - space(), - fat_arrow_token.format(), - space() + write![ + f, + [ + return_type_annotation.format(), + space(), + fat_arrow_token.format(), + ] ] - ]?; + }); let body = body?; @@ -87,13 +87,34 @@ impl FormatNodeRule for FormatJsArrowFunctionExpressi // going to get broken anyways. let body_has_soft_line_break = match &body { JsFunctionBody(_) => true, - JsAnyExpression(expr) => match expr { + JsAnyExpression(expr) => match resolve_expression(expr.clone()) { JsArrowFunctionExpression(_) | JsArrayExpression(_) | JsObjectExpression(_) - | JsParenthesizedExpression(_) | JsxTagExpression(_) => true, - expr => is_simple_expression(expr)?, + JsSequenceExpression(_) => { + return write!( + f, + [group(&format_args![ + format_signature, + group(&format_args![ + space(), + text("("), + soft_block_indent(&body.format()), + text(")") + ]) + ])] + ); + // // We handle sequence expressions as the body of arrows specially, + // // so that the required parentheses end up on their own lines. + // if (node.body.type === "SequenceExpression") { + // return group([ + // ...parts, + // group([" (", indent([softline, body]), softline, ")"]), + // ]); + // } + } + _ => false, }, }; @@ -118,23 +139,26 @@ impl FormatNodeRule for FormatJsArrowFunctionExpressi }; if body_has_soft_line_break && !should_add_parens { - write![f, [body.format()]] + write![f, [format_signature, space(), body.format()]] } else { write!( f, - [group(&soft_line_indent_or_space(&format_with(|f| { - if should_add_parens { - write!(f, [if_group_fits_on_line(&text("("))])?; - } - - write!(f, [body.format()])?; - - if should_add_parens { - write!(f, [if_group_fits_on_line(&text(")"))])?; - } - - Ok(()) - })))] + [ + format_signature, + group(&soft_line_indent_or_space(&format_with(|f| { + if should_add_parens { + write!(f, [if_group_fits_on_line(&text("("))])?; + } + + write!(f, [body.format()])?; + + if should_add_parens { + write!(f, [if_group_fits_on_line(&text(")"))])?; + } + + Ok(()) + }))) + ] ) } } @@ -163,7 +187,7 @@ impl NeedsParentheses for JsArrowFunctionExpression { #[cfg(test)] mod tests { - use crate::parentheses::NeedsParentheses; + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; use rome_js_syntax::{JsArrowFunctionExpression, SourceType}; diff --git a/crates/rome_js_formatter/src/js/expressions/assignment_expression.rs b/crates/rome_js_formatter/src/js/expressions/assignment_expression.rs index f95312acb98..29b5634318f 100644 --- a/crates/rome_js_formatter/src/js/expressions/assignment_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/assignment_expression.rs @@ -63,7 +63,7 @@ impl NeedsParentheses for JsAssignmentExpression { JsSyntaxKind::JS_EXPRESSION_STATEMENT => { // Parenthesize `{ a } = { a: 5 }` is_first_in_statement( - self.syntax(), + self.clone().into(), FirstInStatementMode::ExpressionStatementOrArrow, ) && matches!( self.left(), @@ -95,7 +95,7 @@ impl NeedsParentheses for JsAssignmentExpression { #[cfg(test)] mod tests { - use crate::parentheses::NeedsParentheses; + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; use rome_js_syntax::JsAssignmentExpression; diff --git a/crates/rome_js_formatter/src/js/expressions/await_expression.rs b/crates/rome_js_formatter/src/js/expressions/await_expression.rs index f887366fad6..56e8a2b5273 100644 --- a/crates/rome_js_formatter/src/js/expressions/await_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/await_expression.rs @@ -47,7 +47,7 @@ pub(super) fn await_or_yield_needs_parens(parent: &JsSyntaxNode, node: &JsSyntax #[cfg(test)] mod tests { - use crate::parentheses::NeedsParentheses; + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; use rome_js_syntax::JsAwaitExpression; diff --git a/crates/rome_js_formatter/src/js/expressions/call_expression.rs b/crates/rome_js_formatter/src/js/expressions/call_expression.rs index 304089a211d..b17b07a8214 100644 --- a/crates/rome_js_formatter/src/js/expressions/call_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/call_expression.rs @@ -1,59 +1,49 @@ use crate::prelude::*; -use crate::utils::format_call_expression; -use crate::parentheses::{resolve_left_most_expression, NeedsParentheses}; +use crate::parentheses::NeedsParentheses; +use crate::utils::get_member_chain; use rome_js_syntax::{JsCallExpression, JsSyntaxKind, JsSyntaxNode}; -use rome_rowan::AstNode; #[derive(Debug, Clone, Default)] pub struct FormatJsCallExpression; impl FormatNodeRule for FormatJsCallExpression { - fn fmt_fields(&self, node: &JsCallExpression, formatter: &mut JsFormatter) -> FormatResult<()> { - format_call_expression(node.syntax(), formatter) + fn fmt_fields(&self, node: &JsCallExpression, f: &mut JsFormatter) -> FormatResult<()> { + let member_chain = get_member_chain(node, f)?; + + member_chain.fmt(f) + } + + fn needs_parentheses(&self, item: &JsCallExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsCallExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match parent.kind() { + JsSyntaxKind::JS_NEW_EXPRESSION => true, + + _ => false, + } } } -// impl NeedsParentheses for JsCallExpression { -// fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { -// match parent.kind() { -// JsSyntaxKind::JS_EXPRESSION_STATEMENT | JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION => { -// self.callee() -// .map_or(false, |callee| starts_with_no_lookahead_token(&callee)) -// } -// _ => {} -// } -// -// // case "CallExpression": -// // case "MemberExpression": -// // case "TaggedTemplateExpression": -// // case "TSNonNullExpression": -// // if ( -// // name === "callee" && -// // (parent.type === "BindExpression" || parent.type === "NewExpression") -// // ) { -// // let object = node; -// // while (object) { -// // switch (object.type) { -// // case "CallExpression": -// // case "OptionalCallExpression": -// // return true; -// // case "MemberExpression": -// // case "OptionalMemberExpression": -// // case "BindExpression": -// // object = object.object; -// // break; -// // // tagged templates are basically member expressions from a grammar perspective -// // // see https://tc39.github.io/ecma262/#prod-MemberExpression -// // case "TaggedTemplateExpression": -// // object = object.tag; -// // break; -// // case "TSNonNullExpression": -// // object = object.expression; -// // break; -// // default: -// // return false; -// // } -// // } -// } -// } +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::JsCallExpression; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("new (call())()", JsCallExpression); + + assert_not_needs_parentheses!("a?.()!.c", JsCallExpression); + assert_not_needs_parentheses!("(a?.())!.c", JsCallExpression); + + assert_not_needs_parentheses!("(call())()", JsCallExpression[1]); + assert_not_needs_parentheses!("getLogger().error(err);", JsCallExpression[0]); + assert_not_needs_parentheses!("getLogger().error(err);", JsCallExpression[1]); + } +} diff --git a/crates/rome_js_formatter/src/js/expressions/class_expression.rs b/crates/rome_js_formatter/src/js/expressions/class_expression.rs index d38e0788ef2..45951b11812 100644 --- a/crates/rome_js_formatter/src/js/expressions/class_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/class_expression.rs @@ -23,7 +23,7 @@ impl NeedsParentheses for JsClassExpression { fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { is_callee(self.syntax(), parent) || is_first_in_statement( - self.syntax(), + self.clone().into(), FirstInStatementMode::ExpressionOrExportDefault, ) } @@ -31,7 +31,7 @@ impl NeedsParentheses for JsClassExpression { #[cfg(test)] mod tests { - use crate::parentheses::NeedsParentheses; + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; use rome_js_syntax::JsClassExpression; diff --git a/crates/rome_js_formatter/src/js/expressions/computed_member_expression.rs b/crates/rome_js_formatter/src/js/expressions/computed_member_expression.rs index e5652c93ace..c8cf5acb2e7 100644 --- a/crates/rome_js_formatter/src/js/expressions/computed_member_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/computed_member_expression.rs @@ -1,8 +1,10 @@ use crate::prelude::*; +use crate::js::expressions::static_member_expression::memberish_needs_parens; +use crate::parentheses::NeedsParentheses; use rome_formatter::{format_args, write}; -use rome_js_syntax::JsComputedMemberExpression; -use rome_js_syntax::JsComputedMemberExpressionFields; +use rome_js_syntax::{JsComputedMemberExpression, JsSyntaxNode}; +use rome_js_syntax::{JsComputedMemberExpressionFields, JsSyntaxKind}; #[derive(Debug, Clone, Default)] pub struct FormatJsComputedMemberExpression; @@ -35,4 +37,38 @@ impl FormatNodeRule for FormatJsComputedMemberExpres ] ] } + + fn needs_parentheses(&self, item: &JsComputedMemberExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsComputedMemberExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + if self.is_optional_chain() && matches!(parent.kind(), JsSyntaxKind::JS_NEW_EXPRESSION) { + return true; + } + + memberish_needs_parens(self.clone().into(), parent) + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::JsComputedMemberExpression; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("new (test()[a])()", JsComputedMemberExpression); + assert_needs_parentheses!("new (test().a[b])()", JsComputedMemberExpression); + assert_needs_parentheses!( + "new (test()`template`[index])()", + JsComputedMemberExpression + ); + assert_needs_parentheses!("new (test()![member])()", JsComputedMemberExpression); + + assert_not_needs_parentheses!("new (test[a])()", JsComputedMemberExpression); + } } diff --git a/crates/rome_js_formatter/src/js/expressions/conditional_expression.rs b/crates/rome_js_formatter/src/js/expressions/conditional_expression.rs index bc19009c949..6ae8fda6a87 100644 --- a/crates/rome_js_formatter/src/js/expressions/conditional_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/conditional_expression.rs @@ -3,7 +3,7 @@ use crate::utils::JsAnyConditional; use crate::parentheses::{ is_binary_like_left_or_right, is_conditional_test, is_in_left_hand_side_position, - resolve_left_most_expression, NeedsParentheses, + NeedsParentheses, }; use rome_js_syntax::{JsConditionalExpression, JsSyntaxKind, JsSyntaxNode}; @@ -45,7 +45,7 @@ impl NeedsParentheses for JsConditionalExpression { #[cfg(test)] mod tests { - use crate::parentheses::NeedsParentheses; + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; use rome_js_syntax::{JsConditionalExpression, SourceType}; diff --git a/crates/rome_js_formatter/src/js/expressions/function_expression.rs b/crates/rome_js_formatter/src/js/expressions/function_expression.rs index 3a9e3ab1d49..34710226385 100644 --- a/crates/rome_js_formatter/src/js/expressions/function_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/function_expression.rs @@ -1,7 +1,6 @@ use crate::prelude::*; use crate::js::declarations::function_declaration::FormatFunction; -use crate::parentheses::FirstInStatementMode::ExpressionStatementOnly; use crate::parentheses::{ is_callee, is_first_in_statement, is_tag, FirstInStatementMode, NeedsParentheses, }; @@ -26,7 +25,7 @@ impl NeedsParentheses for JsFunctionExpression { is_callee(self.syntax(), parent) || is_tag(self.syntax(), parent) || is_first_in_statement( - self.syntax(), + self.clone().into(), FirstInStatementMode::ExpressionOrExportDefault, ) } @@ -34,9 +33,9 @@ impl NeedsParentheses for JsFunctionExpression { #[cfg(test)] mod tests { - use crate::parentheses::NeedsParentheses; + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; - use rome_js_syntax::{JsFunctionExpression, JsPostUpdateExpression}; + use rome_js_syntax::JsFunctionExpression; #[test] fn needs_parentheses() { diff --git a/crates/rome_js_formatter/src/js/expressions/number_literal_expression.rs b/crates/rome_js_formatter/src/js/expressions/number_literal_expression.rs index 85f0b3eb3d8..c970fd08667 100644 --- a/crates/rome_js_formatter/src/js/expressions/number_literal_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/number_literal_expression.rs @@ -33,7 +33,7 @@ impl NeedsParentheses for JsNumberLiteralExpression { #[cfg(test)] mod tests { - use crate::parentheses::NeedsParentheses; + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; use rome_js_syntax::JsNumberLiteralExpression; diff --git a/crates/rome_js_formatter/src/js/expressions/object_expression.rs b/crates/rome_js_formatter/src/js/expressions/object_expression.rs index 6658eb2f2b2..0a58a3d8eb7 100644 --- a/crates/rome_js_formatter/src/js/expressions/object_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/object_expression.rs @@ -1,11 +1,8 @@ use crate::parentheses::{is_first_in_statement, FirstInStatementMode, NeedsParentheses}; use crate::prelude::*; -use crate::utils::{resolve_expression_syntax, JsObjectLike}; +use crate::utils::JsObjectLike; use rome_formatter::write; -use rome_js_syntax::{ - JsAnyExpression, JsAnyFunctionBody, JsArrowFunctionExpression, JsObjectExpression, - JsSyntaxKind, JsSyntaxNode, -}; +use rome_js_syntax::{JsObjectExpression, JsSyntaxKind, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsObjectExpression; @@ -24,7 +21,7 @@ impl NeedsParentheses for JsObjectExpression { fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { matches!(parent.kind(), JsSyntaxKind::JS_EXTENDS_CLAUSE) || is_first_in_statement( - self.syntax(), + self.clone().into(), FirstInStatementMode::ExpressionStatementOrArrow, ) } @@ -33,8 +30,7 @@ impl NeedsParentheses for JsObjectExpression { #[cfg(test)] mod tests { use crate::assert_needs_parentheses; - use crate::parentheses::NeedsParentheses; - use rome_js_syntax::{JsObjectExpression, JsStaticMemberExpression}; + use rome_js_syntax::JsObjectExpression; #[test] fn needs_parentheses() { diff --git a/crates/rome_js_formatter/src/js/expressions/parenthesized_expression.rs b/crates/rome_js_formatter/src/js/expressions/parenthesized_expression.rs index 966d6abcca3..b302280096f 100644 --- a/crates/rome_js_formatter/src/js/expressions/parenthesized_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/parenthesized_expression.rs @@ -121,7 +121,7 @@ fn is_simple_parenthesized_expression(node: &JsParenthesizedExpression) -> Synta } // Allow list of nodes that manually handle inserting parens if needed -fn is_expression_handling_parens(expression: &JsAnyExpression) -> bool { +pub(crate) fn is_expression_handling_parens(expression: &JsAnyExpression) -> bool { use JsAnyExpression::*; if let JsAnyExpression::JsParenthesizedExpression(inner) = expression { @@ -150,6 +150,11 @@ fn is_expression_handling_parens(expression: &JsAnyExpression) -> bool { | JsSuperExpression(_) | JsAssignmentExpression(_) | JsArrowFunctionExpression(_) + | JsCallExpression(_) + | JsStaticMemberExpression(_) + | JsComputedMemberExpression(_) + | TsNonNullAssertionExpression(_) + | JsxTagExpression(_) ) } } diff --git a/crates/rome_js_formatter/src/js/expressions/post_update_expression.rs b/crates/rome_js_formatter/src/js/expressions/post_update_expression.rs index 7b7d5dd38c4..f943a3caf3e 100644 --- a/crates/rome_js_formatter/src/js/expressions/post_update_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/post_update_expression.rs @@ -31,7 +31,7 @@ impl NeedsParentheses for JsPostUpdateExpression { #[cfg(test)] mod tests { - use crate::parentheses::NeedsParentheses; + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; use rome_js_syntax::JsPostUpdateExpression; diff --git a/crates/rome_js_formatter/src/js/expressions/pre_update_expression.rs b/crates/rome_js_formatter/src/js/expressions/pre_update_expression.rs index 2d6abb72637..77742b51eba 100644 --- a/crates/rome_js_formatter/src/js/expressions/pre_update_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/pre_update_expression.rs @@ -45,7 +45,7 @@ impl NeedsParentheses for JsPreUpdateExpression { #[cfg(test)] mod tests { - use crate::parentheses::NeedsParentheses; + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; use rome_js_syntax::JsPreUpdateExpression; diff --git a/crates/rome_js_formatter/src/js/expressions/sequence_expression.rs b/crates/rome_js_formatter/src/js/expressions/sequence_expression.rs index 030ae2f8878..dc3167857a9 100644 --- a/crates/rome_js_formatter/src/js/expressions/sequence_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/sequence_expression.rs @@ -82,24 +82,10 @@ impl NeedsParentheses for JsSequenceExpression { JsSyntaxKind::JS_FOR_STATEMENT => false, JsSyntaxKind::JS_EXPRESSION_STATEMENT => false, JsSyntaxKind::JS_SEQUENCE_EXPRESSION => false, + // Handled as part of the arrow function formatting + JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION => false, // Be on the safer side _ => true, } - // case "SequenceExpression": - // switch (parent.type) { - // - // case "ExpressionStatement": - // return name !== "expression"; - // - // case "ArrowFunctionExpression": - // // We do need parentheses, but SequenceExpressions are handled - // // specially when printing bodies of arrow functions. - // return name !== "body"; - // - // default: - // // Otherwise err on the side of overparenthesization, adding - // // explicit exceptions above if this proves overzealous. - // return true; - // } } } diff --git a/crates/rome_js_formatter/src/js/expressions/static_member_expression.rs b/crates/rome_js_formatter/src/js/expressions/static_member_expression.rs index 8f6805c3be7..862962bc349 100644 --- a/crates/rome_js_formatter/src/js/expressions/static_member_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/static_member_expression.rs @@ -1,9 +1,10 @@ use crate::prelude::*; +use crate::parentheses::NeedsParentheses; use rome_formatter::{format_args, write}; use rome_js_syntax::{ JsAnyExpression, JsAssignmentExpression, JsStaticMemberExpression, - JsStaticMemberExpressionFields, JsVariableDeclarator, + JsStaticMemberExpressionFields, JsSyntaxKind, JsSyntaxNode, JsVariableDeclarator, }; use rome_rowan::AstNode; @@ -38,6 +39,10 @@ impl FormatNodeRule for FormatJsStaticMemberExpression } } } + + fn needs_parentheses(&self, item: &JsStaticMemberExpression) -> bool { + item.needs_parentheses() + } } enum StaticMemberExpressionLayout { @@ -100,3 +105,62 @@ fn compute_member_layout( Ok(StaticMemberExpressionLayout::BreakAfterObject) } + +impl NeedsParentheses for JsStaticMemberExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + if self.is_optional_chain() && matches!(parent.kind(), JsSyntaxKind::JS_NEW_EXPRESSION) { + return true; + } + + memberish_needs_parens(self.clone().into(), parent) + } +} + +pub(crate) fn memberish_needs_parens(node: JsAnyExpression, parent: &JsSyntaxNode) -> bool { + use JsAnyExpression::*; + debug_assert!( + matches!( + node, + JsStaticMemberExpression(_) + | JsComputedMemberExpression(_) + | TsNonNullAssertionExpression(_) + ), + "Expected node to be a member expression" + ); + + match parent.kind() { + // `new (test().a) + JsSyntaxKind::JS_NEW_EXPRESSION => { + let mut object_chain = + std::iter::successors(Some(node), |expression| match expression { + JsStaticMemberExpression(member) => member.object().ok(), + JsComputedMemberExpression(member) => member.object().ok(), + JsTemplate(template) => template.tag(), + TsNonNullAssertionExpression(assertion) => assertion.expression().ok(), + _ => None, + }); + + object_chain.any(|object| matches!(object, JsCallExpression(_))) + } + _ => false, + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::JsStaticMemberExpression; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("new (test().a)()", JsStaticMemberExpression); + assert_needs_parentheses!("new (test()[a].b)()", JsStaticMemberExpression); + assert_needs_parentheses!("new (test()`template`.length)()", JsStaticMemberExpression); + assert_needs_parentheses!("new (test()!.member)()", JsStaticMemberExpression); + + assert_needs_parentheses!("new (foo?.bar)();", JsStaticMemberExpression); + + assert_not_needs_parentheses!("new (test.a)()", JsStaticMemberExpression); + } +} diff --git a/crates/rome_js_formatter/src/js/expressions/string_literal_expression.rs b/crates/rome_js_formatter/src/js/expressions/string_literal_expression.rs index 5a015f05166..183244dc304 100644 --- a/crates/rome_js_formatter/src/js/expressions/string_literal_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/string_literal_expression.rs @@ -51,7 +51,7 @@ impl NeedsParentheses for JsStringLiteralExpression { #[cfg(test)] mod tests { - use crate::parentheses::NeedsParentheses; + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; use rome_js_syntax::{JsStringLiteralExpression, ModuleKind, SourceType}; diff --git a/crates/rome_js_formatter/src/js/expressions/unary_expression.rs b/crates/rome_js_formatter/src/js/expressions/unary_expression.rs index 5adb35e5603..cf9be859200 100644 --- a/crates/rome_js_formatter/src/js/expressions/unary_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/unary_expression.rs @@ -58,7 +58,7 @@ impl NeedsParentheses for JsUnaryExpression { #[cfg(test)] mod tests { - use crate::parentheses::NeedsParentheses; + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; use rome_js_syntax::JsUnaryExpression; diff --git a/crates/rome_js_formatter/src/js/expressions/yield_expression.rs b/crates/rome_js_formatter/src/js/expressions/yield_expression.rs index 8e8c626f2fd..e1a8a010e0c 100644 --- a/crates/rome_js_formatter/src/js/expressions/yield_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/yield_expression.rs @@ -33,7 +33,7 @@ impl NeedsParentheses for JsYieldExpression { #[cfg(test)] mod tests { - use crate::parentheses::NeedsParentheses; + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; use rome_js_syntax::JsYieldExpression; diff --git a/crates/rome_js_formatter/src/jsx/expressions/tag_expression.rs b/crates/rome_js_formatter/src/jsx/expressions/tag_expression.rs index a12efe22b07..0b428eff53e 100644 --- a/crates/rome_js_formatter/src/jsx/expressions/tag_expression.rs +++ b/crates/rome_js_formatter/src/jsx/expressions/tag_expression.rs @@ -1,14 +1,18 @@ +use crate::parentheses::{is_callee, is_tag, NeedsParentheses}; use crate::prelude::*; use crate::utils::jsx::{get_wrap_state, WrapState}; +use crate::utils::resolve_expression_syntax; use rome_formatter::{format_args, write}; -use rome_js_syntax::JsxTagExpression; +use rome_js_syntax::{ + JsBinaryExpression, JsBinaryOperator, JsSyntaxKind, JsSyntaxNode, JsxTagExpression, +}; #[derive(Debug, Clone, Default)] pub struct FormatJsxTagExpression; impl FormatNodeRule for FormatJsxTagExpression { fn fmt_fields(&self, node: &JsxTagExpression, f: &mut JsFormatter) -> FormatResult<()> { - match get_wrap_state(node.syntax()) { + match get_wrap_state(node) { WrapState::WrapOnBreak => write![ f, [group(&format_args![ @@ -17,15 +21,60 @@ impl FormatNodeRule for FormatJsxTagExpression { if_group_breaks(&text(")")) ])] ], - WrapState::AlwaysWrap => write![ - f, - [group(&format_args![ - text("("), - soft_block_indent(&format_args![node.tag().format()]), - text(")") - ])] - ], WrapState::NoWrap => write![f, [node.tag().format()]], } } + + fn needs_parentheses(&self, item: &JsxTagExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsxTagExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match parent.kind() { + JsSyntaxKind::JS_BINARY_EXPRESSION => { + let binary = JsBinaryExpression::unwrap_cast(parent.clone()); + + let is_left = + binary.left().map(resolve_expression_syntax).as_ref() == Ok(self.syntax()); + matches!(binary.operator(), Ok(JsBinaryOperator::LessThan)) && is_left + } + JsSyntaxKind::TS_AS_EXPRESSION + | JsSyntaxKind::JS_AWAIT_EXPRESSION + | JsSyntaxKind::JS_EXTENDS_CLAUSE + | JsSyntaxKind::JS_STATIC_MEMBER_EXPRESSION + | JsSyntaxKind::JS_COMPUTED_MEMBER_EXPRESSION + | JsSyntaxKind::JS_SEQUENCE_EXPRESSION + | JsSyntaxKind::JS_UNARY_EXPRESSION + | JsSyntaxKind::TS_NON_NULL_ASSERTION_EXPRESSION + | JsSyntaxKind::JS_SPREAD + | JsSyntaxKind::JSX_SPREAD_ATTRIBUTE + | JsSyntaxKind::JSX_SPREAD_CHILD => true, + _ => is_callee(self.syntax(), parent) || is_tag(self.syntax(), parent), + } + + // (parent.type !== "ArrayExpression" && + // parent.type !== "ArrowFunctionExpression" && + // parent.type !== "AssignmentExpression" && + // parent.type !== "AssignmentPattern" && + // parent.type !== "BinaryExpression" && + // parent.type !== "NewExpression" && + // parent.type !== "ConditionalExpression" && + // parent.type !== "ExpressionStatement" && + // parent.type !== "JsExpressionRoot" && + // parent.type !== "JSXAttribute" && + // parent.type !== "JSXElement" && + // parent.type !== "JSXExpressionContainer" && + // parent.type !== "JSXFragment" && + // parent.type !== "LogicalExpression" && + // !isCallExpression(parent) && + // !isObjectProperty(parent) && + // parent.type !== "ReturnStatement" && + // parent.type !== "ThrowStatement" && + // parent.type !== "TypeCastExpression" && + // parent.type !== "VariableDeclarator" && + // parent.type !== "YieldExpression") + // ); + } } diff --git a/crates/rome_js_formatter/src/parentheses.rs b/crates/rome_js_formatter/src/parentheses.rs index 62e532c34be..3f5cd3647bd 100644 --- a/crates/rome_js_formatter/src/parentheses.rs +++ b/crates/rome_js_formatter/src/parentheses.rs @@ -137,9 +137,6 @@ pub(crate) fn get_expression_left_side( #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub(crate) enum FirstInStatementMode { - /// Only considers [JsExpressionStatement] as a statement - ExpressionStatementOnly, - /// Considers [JsExpressionStatement] and the body of [JsArrowFunctionExpression] as the first statement. ExpressionStatementOrArrow, @@ -151,12 +148,10 @@ pub(crate) enum FirstInStatementMode { /// /// Traverses upwards the tree for as long as the `node` is the left most expression until the node isn't /// the left most node or reached a statement. -pub(crate) fn is_first_in_statement(node: &JsSyntaxNode, mode: FirstInStatementMode) -> bool { - debug_assert_is_expression(node); +pub(crate) fn is_first_in_statement(node: JsAnyExpression, mode: FirstInStatementMode) -> bool { + let mut current = node; - let mut current = node.clone(); - - while let Some(parent) = current.parent() { + while let Some(parent) = current.syntax().parent() { let parent = match parent.kind() { JsSyntaxKind::JS_EXPRESSION_STATEMENT => { return true; @@ -168,14 +163,16 @@ pub(crate) fn is_first_in_statement(node: &JsSyntaxNode, mode: FirstInStatementM | JsSyntaxKind::JS_CALL_EXPRESSION | JsSyntaxKind::JS_NEW_EXPRESSION | JsSyntaxKind::TS_AS_EXPRESSION - | JsSyntaxKind::TS_NON_NULL_ASSERTION_EXPRESSION => parent, + | JsSyntaxKind::TS_NON_NULL_ASSERTION_EXPRESSION => { + JsAnyExpression::unwrap_cast(parent) + } JsSyntaxKind::JS_SEQUENCE_EXPRESSION => { let sequence = JsSequenceExpression::unwrap_cast(parent); - let is_left = sequence.left().map(AstNode::into_syntax).as_ref() == Ok(¤t); + let is_left = sequence.left().as_ref() == Ok(¤t); if is_left { - sequence.into_syntax() + sequence.into() } else { break; } @@ -184,14 +181,10 @@ pub(crate) fn is_first_in_statement(node: &JsSyntaxNode, mode: FirstInStatementM JsSyntaxKind::JS_COMPUTED_MEMBER_EXPRESSION => { let member_expression = JsComputedMemberExpression::unwrap_cast(parent); - let is_object = member_expression - .object() - .map(AstNode::into_syntax) - .as_ref() - == Ok(¤t); + let is_object = member_expression.object().as_ref() == Ok(¤t); if is_object { - member_expression.into_syntax() + member_expression.into() } else { break; } @@ -200,8 +193,8 @@ pub(crate) fn is_first_in_statement(node: &JsSyntaxNode, mode: FirstInStatementM JsSyntaxKind::JS_CONDITIONAL_EXPRESSION => { let conditional = JsConditionalExpression::unwrap_cast(parent); - if conditional.test().map(AstNode::into_syntax).as_ref() == Ok(¤t) { - conditional.into_syntax() + if conditional.test().as_ref() == Ok(¤t) { + conditional.into() } else { break; } @@ -213,9 +206,7 @@ pub(crate) fn is_first_in_statement(node: &JsSyntaxNode, mode: FirstInStatementM let arrow = JsArrowFunctionExpression::unwrap_cast(parent); let is_body = arrow.body().map_or(false, |body| match body { - JsAnyFunctionBody::JsAnyExpression(expression) => { - expression.syntax() == ¤t - } + JsAnyFunctionBody::JsAnyExpression(expression) => &expression == ¤t, _ => false, }); @@ -237,13 +228,13 @@ pub(crate) fn is_first_in_statement(node: &JsSyntaxNode, mode: FirstInStatementM let is_left = binary_like.left().map_or(false, |left| match left { JsAnyBinaryLikeLeftExpression::JsAnyExpression(expression) => { - expression.syntax() == ¤t + &expression == ¤t } _ => false, }); if is_left { - binary_like.into_syntax() + binary_like.into() } else { break; } @@ -333,11 +324,12 @@ pub(crate) fn is_member_object(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bo JsSyntaxKind::JS_COMPUTED_MEMBER_EXPRESSION => { let member_expression = JsComputedMemberExpression::unwrap_cast(parent.clone()); - member_expression - .object() - .map(resolve_expression_syntax) - .as_ref() - == Ok(node) + member_expression.optional_chain_token().is_none() + && member_expression + .object() + .map(resolve_expression_syntax) + .as_ref() + == Ok(node) } _ => false, } @@ -400,73 +392,150 @@ fn debug_assert_is_expression(node: &JsSyntaxNode) { } #[cfg(test)] -mod tests { - - #[macro_export] - macro_rules! assert_needs_parentheses { - ($input:expr, $Node:ident) => {{ - $crate::assert_needs_parentheses!($input, $Node, rome_js_syntax::SourceType::ts()) - }}; - - ($input:expr, $Node:ident, $source_type: expr) => {{ - use rome_rowan::AstNode; - let parse = rome_js_parser::parse($input, 0, $source_type); - - let diagnostics = parse.diagnostics(); - assert!(diagnostics.is_empty(), "Expected input program to not have syntax errors but had {diagnostics:?}"); +pub(crate) mod tests { + use super::NeedsParentheses; + use rome_js_syntax::{JsLanguage, SourceType}; + use rome_rowan::AstNode; + + pub(crate) fn assert_needs_parentheses_impl< + T: AstNode + std::fmt::Debug + NeedsParentheses, + >( + input: &'static str, + index: Option, + source_type: SourceType, + ) { + let parse = rome_js_parser::parse(input, 0, source_type); + + let diagnostics = parse.diagnostics(); + assert!( + diagnostics.is_empty(), + "Expected input program to not have syntax errors but had {diagnostics:?}" + ); + + let root = parse.syntax(); + let matching_nodes: Vec<_> = root.descendants().filter_map(T::cast).collect(); + + let node = if let Some(index) = index { + matching_nodes.get(index).unwrap_or_else(|| { + panic!("Out of bound index {index}, matching nodes are:\n{matching_nodes:#?}"); + }) + } else { + match matching_nodes.len() { + 0 => { + panic!( + "Expected to find a '{}' node in '{input}' but found none.", + core::any::type_name::(), + ) + } + 1 => matching_nodes.iter().next().unwrap(), + _ => { + panic!("Expected to find a single node matching '{}' in '{input}' but found multiple ones:\n {matching_nodes:#?}", core::any::type_name::()); + } + } + }; - let root = parse.syntax(); - let matching_nodes: Vec<_> = root.descendants().filter_map($Node::cast).collect(); + assert!(node.needs_parentheses()); + } - let node = match matching_nodes.len() { + pub(crate) fn assert_not_needs_parentheses_impl< + T: AstNode + std::fmt::Debug + NeedsParentheses, + >( + input: &'static str, + index: Option, + source_type: SourceType, + ) { + let parse = rome_js_parser::parse(input, 0, source_type); + + let diagnostics = parse.diagnostics(); + assert!( + diagnostics.is_empty(), + "Expected input program to not have syntax errors but had {diagnostics:?}" + ); + + let root = parse.syntax(); + let matching_nodes: Vec<_> = root.descendants().filter_map(T::cast).collect(); + + let node = if let Some(index) = index { + matching_nodes.get(index).unwrap_or_else(|| { + panic!("Out of bound index {index}, matching nodes are:\n{matching_nodes:#?}"); + }) + } else { + match matching_nodes.len() { 0 => { panic!( - "Expected to find a '{}' node in '{}' but found none.", - core::any::type_name::<$Node>(), - $input + "Expected to find a '{}' node in '{input}' but found none.", + core::any::type_name::(), ) } - 1 => matching_nodes.into_iter().next().unwrap(), + 1 => matching_nodes.iter().next().unwrap(), _ => { - panic!("Expected to find a single node matching '{}' in '{}' but found multiple ones\n: {matching_nodes:#?}", core::any::type_name::<$Node>(), $input); + panic!("Expected to find a single node matching '{}' in '{input}' but found multiple ones:\n {matching_nodes:#?}", core::any::type_name::()); } - }; + } + }; - assert!(node.needs_parentheses()) - }}; + assert!(!node.needs_parentheses()); } #[macro_export] - macro_rules! assert_not_needs_parentheses { + macro_rules! assert_needs_parentheses { ($input:expr, $Node:ident) => {{ - $crate::assert_not_needs_parentheses!($input, $Node, rome_js_syntax::SourceType::ts()) + $crate::assert_needs_parentheses!($input, $Node, rome_js_syntax::SourceType::ts()) + }}; + + ($input:expr, $Node:ident[$index:expr]) => {{ + $crate::assert_needs_parentheses!( + $input, + $Node[$index], + rome_js_syntax::SourceType::ts() + ) }}; ($input:expr, $Node:ident, $source_type: expr) => {{ - use rome_rowan::AstNode; - let parse = rome_js_parser::parse($input, 0, $source_type); + $crate::parentheses::tests::assert_needs_parentheses_impl::<$Node>( + $input, + None, + $source_type, + ) + }}; - let diagnostics = parse.diagnostics(); - assert!(diagnostics.is_empty(), "Expected input program to not have syntax errors but had {diagnostics:?}"); + ($input:expr, $Node:ident[$index:expr], $source_type: expr) => {{ + $crate::parentheses::tests::assert_needs_parentheses_impl::<$Node>( + $input, + Some($index), + $source_type, + ) + }}; + } + + #[macro_export] + macro_rules! assert_not_needs_parentheses { + ($input:expr, $Node:ident) => {{ + $crate::assert_not_needs_parentheses!($input, $Node, rome_js_syntax::SourceType::ts()) + }}; - let root = parse.syntax(); - let matching_nodes: Vec<_> = root.descendants().filter_map($Node::cast).collect(); + ($input:expr, $Node:ident[$index:expr]) => {{ + $crate::assert_not_needs_parentheses!( + $input, + $Node[$index], + rome_js_syntax::SourceType::ts() + ) + }}; - let node = match matching_nodes.len() { - 0 => { - panic!( - "Expected to find a '{}' node in '{}' but found none.", - core::any::type_name::<$Node>(), - $input - ) - } - 1 => matching_nodes.into_iter().next().unwrap(), - _ => { - panic!("Expected to find a single node matching '{}' in '{}' but found multiple ones\n: {matching_nodes:#?}", core::any::type_name::<$Node>(), $input); - } - }; + ($input:expr, $Node:ident[$index:expr], $source_type: expr) => {{ + $crate::parentheses::tests::assert_not_needs_parentheses_impl::<$Node>( + $input, + Some($index), + $source_type, + ) + }}; - assert!(!node.needs_parentheses()) + ($input:expr, $Node:ident, $source_type: expr) => {{ + $crate::parentheses::tests::assert_not_needs_parentheses_impl::<$Node>( + $input, + None, + $source_type, + ) }}; } } diff --git a/crates/rome_js_formatter/src/ts/expressions/non_null_assertion_expression.rs b/crates/rome_js_formatter/src/ts/expressions/non_null_assertion_expression.rs index 956d5c208ab..bd02f661ac6 100644 --- a/crates/rome_js_formatter/src/ts/expressions/non_null_assertion_expression.rs +++ b/crates/rome_js_formatter/src/ts/expressions/non_null_assertion_expression.rs @@ -1,8 +1,10 @@ use crate::prelude::*; +use crate::js::expressions::static_member_expression::memberish_needs_parens; +use crate::parentheses::NeedsParentheses; use rome_formatter::write; -use rome_js_syntax::TsNonNullAssertionExpression; -use rome_js_syntax::TsNonNullAssertionExpressionFields; +use rome_js_syntax::{JsSyntaxKind, TsNonNullAssertionExpressionFields}; +use rome_js_syntax::{JsSyntaxNode, TsNonNullAssertionExpression}; #[derive(Debug, Clone, Default)] pub struct FormatTsNonNullAssertionExpression; @@ -20,4 +22,15 @@ impl FormatNodeRule for FormatTsNonNullAssertionEx write![f, [expression.format(), excl_token.format()]] } + + fn needs_parentheses(&self, item: &TsNonNullAssertionExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for TsNonNullAssertionExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + matches!(parent.kind(), JsSyntaxKind::JS_EXTENDS_CLAUSE) + || memberish_needs_parens(self.clone().into(), parent) + } } diff --git a/crates/rome_js_formatter/src/utils/jsx.rs b/crates/rome_js_formatter/src/utils/jsx.rs index 071ffa058d3..435d73378c5 100644 --- a/crates/rome_js_formatter/src/utils/jsx.rs +++ b/crates/rome_js_formatter/src/utils/jsx.rs @@ -1,7 +1,8 @@ use crate::context::QuoteStyle; +use crate::parentheses::resolve_expression_parent; use crate::prelude::*; use rome_formatter::{format_args, write}; -use rome_js_syntax::{JsSyntaxKind, JsSyntaxNode, JsxAnyChild, JsxChildList}; +use rome_js_syntax::{JsSyntaxKind, JsSyntaxNode, JsxAnyChild, JsxChildList, JsxTagExpression}; /// Checks if the children of an element contain meaningful text. See [is_meaningful_jsx_text] for /// definition of meaningful JSX text. @@ -69,44 +70,27 @@ pub enum WrapState { /// ); /// ``` WrapOnBreak, - /// For a JSX element that must always be wrapped in parentheses. - /// For instance, a JSX element inside a static member expression - /// should always be wrapped: - /// ```jsx - /// (
Badlands
).property - /// ``` - AlwaysWrap, } /// Checks if a JSX Element should be wrapped in parentheses. Returns a [WrapState] which /// indicates when the element should be wrapped in parentheses. -pub fn get_wrap_state(node: &JsSyntaxNode) -> WrapState { +pub fn get_wrap_state(node: &JsxTagExpression) -> WrapState { // We skip the first item because the first item in ancestors is the node itself, i.e. // the JSX Element in this case. - let mut ancestors = node.ancestors().skip(1); - - ancestors - .next() - .map(|parent| { - let parent_kind = parent.kind(); - if parent_kind == JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION { - return get_wrap_state(&parent); - } - - match parent_kind { - JsSyntaxKind::JS_ARRAY_EXPRESSION - | JsSyntaxKind::JSX_ATTRIBUTE - | JsSyntaxKind::JSX_ELEMENT - | JsSyntaxKind::JSX_EXPRESSION_CHILD - | JsSyntaxKind::JSX_FRAGMENT - | JsSyntaxKind::JS_EXPRESSION_STATEMENT - | JsSyntaxKind::JS_CALL_ARGUMENT_LIST => WrapState::NoWrap, - JsSyntaxKind::JS_STATIC_MEMBER_EXPRESSION - | JsSyntaxKind::JS_COMPUTED_MEMBER_EXPRESSION => WrapState::AlwaysWrap, - _ => WrapState::WrapOnBreak, - } - }) - .unwrap_or(WrapState::NoWrap) + let parent = resolve_expression_parent(node.syntax()); + + parent.map_or(WrapState::NoWrap, |parent| match parent.kind() { + JsSyntaxKind::JS_ARRAY_EXPRESSION + | JsSyntaxKind::JSX_ATTRIBUTE + | JsSyntaxKind::JSX_ELEMENT + | JsSyntaxKind::JSX_EXPRESSION_CHILD + | JsSyntaxKind::JSX_FRAGMENT + | JsSyntaxKind::JS_EXPRESSION_STATEMENT + | JsSyntaxKind::JS_STATIC_MEMBER_EXPRESSION + | JsSyntaxKind::JS_COMPUTED_MEMBER_EXPRESSION + | JsSyntaxKind::JS_CALL_ARGUMENT_LIST => WrapState::NoWrap, + _ => WrapState::WrapOnBreak, + }) } /// This is a very special situation where we're returning a JsxElement diff --git a/crates/rome_js_formatter/src/utils/member_chain/flatten_item.rs b/crates/rome_js_formatter/src/utils/member_chain/chain_member.rs similarity index 54% rename from crates/rome_js_formatter/src/utils/member_chain/flatten_item.rs rename to crates/rome_js_formatter/src/utils/member_chain/chain_member.rs index a3f6a9274c7..5020c9fc735 100644 --- a/crates/rome_js_formatter/src/utils/member_chain/flatten_item.rs +++ b/crates/rome_js_formatter/src/utils/member_chain/chain_member.rs @@ -1,18 +1,142 @@ use crate::context::TabWidth; +use crate::js::expressions::parenthesized_expression::is_expression_handling_parens; use crate::prelude::*; use rome_formatter::write; use rome_js_syntax::{ JsAnyExpression, JsCallExpression, JsCallExpressionFields, JsComputedMemberExpression, JsComputedMemberExpressionFields, JsIdentifierExpression, JsImportCallExpression, - JsNewExpression, JsStaticMemberExpression, JsStaticMemberExpressionFields, JsSyntaxNode, - JsThisExpression, + JsNewExpression, JsParenthesizedExpression, JsStaticMemberExpression, + JsStaticMemberExpressionFields, JsSyntaxNode, JsThisExpression, }; use rome_rowan::{AstNode, SyntaxResult}; use std::fmt::Debug; +#[derive(Clone, Debug)] +pub(crate) enum ChainEntry { + Parenthesized { + member: ChainMember, + top_most_parentheses: JsParenthesizedExpression, + }, + Member(ChainMember), +} + +impl ChainEntry { + pub fn member(&self) -> &ChainMember { + match self { + ChainEntry::Parenthesized { member, .. } => member, + ChainEntry::Member(member) => member, + } + } + + pub fn top_most_parentheses(&self) -> Option<&JsParenthesizedExpression> { + match self { + ChainEntry::Parenthesized { + top_most_parentheses, + .. + } => Some(top_most_parentheses), + ChainEntry::Member(_) => None, + } + } + + pub fn into_member(self) -> ChainMember { + match self { + ChainEntry::Parenthesized { member, .. } => member, + ChainEntry::Member(member) => member, + } + } + + pub(crate) fn has_trailing_comments(&self) -> bool { + self.nodes().any(|node| node.has_trailing_comments()) + } + + pub(crate) fn has_leading_comments(&self) -> SyntaxResult { + let has_operator_comment = match self.member() { + ChainMember::StaticMember(node) => node.operator_token()?.has_leading_comments(), + _ => false, + }; + + Ok(self.nodes().any(|node| node.has_leading_comments()) || has_operator_comment) + } + + fn nodes(&self) -> impl Iterator { + let first = match self { + ChainEntry::Parenthesized { + top_most_parentheses, + .. + } => top_most_parentheses.syntax().clone(), + ChainEntry::Member(member) => member.syntax().clone(), + }; + + let is_parenthesized = matches!(self, ChainEntry::Parenthesized { .. }); + + std::iter::successors(Some(first), move |previous| { + if is_parenthesized { + JsParenthesizedExpression::cast(previous.clone()).and_then(|parenthesized| { + parenthesized.expression().map(AstNode::into_syntax).ok() + }) + } else { + None + } + }) + } +} + +impl Format for ChainEntry { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let parentheses = self.top_most_parentheses(); + + let handles_parens = JsAnyExpression::cast(self.member().syntax().clone()) + .map_or(false, |expression| { + is_expression_handling_parens(&expression) + }); + + if let Some(parentheses) = parentheses { + let mut current = parentheses.clone(); + + loop { + if handles_parens { + write!(f, [format_removed(¤t.l_paren_token()?)])?; + } else { + write!(f, [current.l_paren_token().format()])?; + } + + match current.expression()? { + JsAnyExpression::JsParenthesizedExpression(inner) => { + current = inner; + } + _ => break, + } + } + } + + write!(f, [self.member()])?; + + if let Some(parentheses) = parentheses { + let mut current = parentheses.clone(); + + loop { + if handles_parens { + write!(f, [format_removed(¤t.r_paren_token()?)])?; + } else { + write!(f, [current.r_paren_token().format()])?; + } + + match current.expression()? { + JsAnyExpression::JsParenthesizedExpression(inner) => { + current = inner; + } + _ => break, + } + } + } + + Ok(()) + } +} + #[derive(Clone, Debug)] /// Data structure that holds the node with its formatted version -pub(crate) enum FlattenItem { +pub(crate) enum ChainMember { /// Holds onto a [rome_js_syntax::JsStaticMemberExpression] StaticMember(JsStaticMemberExpression), /// Holds onto a [rome_js_syntax::JsCallExpression] @@ -24,12 +148,12 @@ pub(crate) enum FlattenItem { Node(JsSyntaxNode), } -impl FlattenItem { +impl ChainMember { /// checks if the current node is a [rome_js_syntax::JsCallExpression], [rome_js_syntax::JsImportExpression] or a [rome_js_syntax::JsNewExpression] pub fn is_loose_call_expression(&self) -> bool { match self { - FlattenItem::CallExpression(_) => true, - FlattenItem::Node(node) => { + ChainMember::CallExpression(_) => true, + ChainMember::Node(node) => { JsImportCallExpression::can_cast(node.kind()) | JsNewExpression::can_cast(node.kind()) } @@ -37,53 +161,33 @@ impl FlattenItem { } } - pub(crate) fn as_syntax(&self) -> &JsSyntaxNode { + pub(crate) fn syntax(&self) -> &JsSyntaxNode { match self { - FlattenItem::StaticMember(node) => node.syntax(), - FlattenItem::CallExpression(node) => node.syntax(), - FlattenItem::ComputedMember(node) => node.syntax(), - FlattenItem::Node(node) => node, + ChainMember::StaticMember(node) => node.syntax(), + ChainMember::CallExpression(node) => node.syntax(), + ChainMember::ComputedMember(node) => node.syntax(), + ChainMember::Node(node) => node, } } - pub(crate) fn has_trailing_comments(&self) -> bool { - match self { - FlattenItem::StaticMember(node) => node.syntax().has_trailing_comments(), - FlattenItem::CallExpression(node) => node.syntax().has_trailing_comments(), - FlattenItem::ComputedMember(node) => node.syntax().has_trailing_comments(), - FlattenItem::Node(node) => node.has_trailing_comments(), - } - } - - pub fn is_computed_expression(&self) -> bool { - matches!(self, FlattenItem::ComputedMember(..)) + pub const fn is_computed_expression(&self) -> bool { + matches!(self, ChainMember::ComputedMember(..)) } pub(crate) fn is_this_expression(&self) -> bool { match self { - FlattenItem::Node(node) => JsThisExpression::can_cast(node.kind()), + ChainMember::Node(node) => JsThisExpression::can_cast(node.kind()), _ => false, } } pub(crate) fn is_identifier_expression(&self) -> bool { match self { - FlattenItem::Node(node) => JsIdentifierExpression::can_cast(node.kind()), + ChainMember::Node(node) => JsIdentifierExpression::can_cast(node.kind()), _ => false, } } - pub(crate) fn has_leading_comments(&self) -> SyntaxResult { - Ok(match self { - FlattenItem::StaticMember(node) => { - node.syntax().has_comments_direct() || node.operator_token()?.has_leading_comments() - } - FlattenItem::CallExpression(node) => node.syntax().has_leading_comments(), - FlattenItem::ComputedMember(node) => node.syntax().has_leading_comments(), - FlattenItem::Node(node) => node.has_leading_comments(), - }) - } - /// There are cases like Object.keys(), Observable.of(), _.values() where /// they are the subject of all the chained calls and therefore should /// be kept on the same line: @@ -108,7 +212,7 @@ impl FlattenItem { || text.starts_with('$') } - if let FlattenItem::StaticMember(static_member, ..) = self { + if let ChainMember::StaticMember(static_member, ..) = self { if check_left_hand_side { if let JsAnyExpression::JsIdentifierExpression(identifier_expression) = static_member.object()? @@ -122,7 +226,7 @@ impl FlattenItem { } else { Ok(check_str(static_member.member()?.text().as_str())) } - } else if let FlattenItem::Node(node, ..) = self { + } else if let ChainMember::Node(node, ..) = self { if let Some(identifier_expression) = JsIdentifierExpression::cast(node.clone()) { let value_token = identifier_expression.name()?.value_token()?; let text = value_token.text_trimmed(); @@ -136,7 +240,7 @@ impl FlattenItem { } pub(crate) fn has_short_name(&self, tab_width: TabWidth) -> SyntaxResult { - if let FlattenItem::StaticMember(static_member, ..) = self { + if let ChainMember::StaticMember(static_member, ..) = self { if let JsAnyExpression::JsIdentifierExpression(identifier_expression) = static_member.object()? { @@ -152,19 +256,19 @@ impl FlattenItem { } } -impl Format for FlattenItem { +impl Format for ChainMember { fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> { match self { - FlattenItem::StaticMember(static_member) => { + ChainMember::StaticMember(static_member) => { let JsStaticMemberExpressionFields { // Formatted as part of the previous item object: _, operator_token, member, } = static_member.as_fields(); - write![f, [operator_token.format(), member.format(),]] + write![f, [operator_token.format(), member.format()]] } - FlattenItem::CallExpression(call_expression) => { + ChainMember::CallExpression(call_expression) => { let JsCallExpressionFields { // Formatted as part of the previous item callee: _, @@ -182,7 +286,7 @@ impl Format for FlattenItem { ] ) } - FlattenItem::ComputedMember(computed_member) => { + ChainMember::ComputedMember(computed_member) => { let JsComputedMemberExpressionFields { // Formatted as part of the previous item object: _, @@ -201,7 +305,7 @@ impl Format for FlattenItem { ] ) } - FlattenItem::Node(node) => { + ChainMember::Node(node) => { write!(f, [node.format()]) } } diff --git a/crates/rome_js_formatter/src/utils/member_chain/groups.rs b/crates/rome_js_formatter/src/utils/member_chain/groups.rs index 452672ceda0..33a32d59a6b 100644 --- a/crates/rome_js_formatter/src/utils/member_chain/groups.rs +++ b/crates/rome_js_formatter/src/utils/member_chain/groups.rs @@ -1,60 +1,47 @@ use crate::context::TabWidth; +use crate::parentheses::NeedsParentheses; use crate::prelude::*; -use crate::utils::member_chain::flatten_item::FlattenItem; +use crate::utils::member_chain::chain_member::{ChainEntry, ChainMember}; use crate::utils::member_chain::simple_argument::SimpleArgument; +use crate::utils::member_chain::MemberChain; +use rome_formatter::write; use rome_js_syntax::JsCallExpression; use rome_rowan::{AstSeparatedList, SyntaxResult}; use std::mem; -#[derive(Clone)] -/// Handles creation of groups while scanning the flatten items -pub(crate) struct Groups { - /// If the current group is inside an expression statement. - /// - /// This information is important when evaluating the break of the groups. - in_expression_statement: bool, +pub(super) struct MemberChainGroupsBuilder { /// keeps track of the groups created - groups: Vec>, + groups: Vec, /// keeps track of the current group that is being created/updated - current_group: Vec, + current_group: MemberChainGroup, - /// This is a threshold of when we should start breaking the groups + /// If the current group is inside an expression statement. /// - /// By default, it's 1, meaning that we start breaking after the first group. - cutoff: u8, + /// This information is important when evaluating the break of the groups. + in_expression_statement: bool, tab_width: TabWidth, } -impl Groups { +impl MemberChainGroupsBuilder { pub fn new(in_expression_statement: bool, tab_width: TabWidth) -> Self { Self { in_expression_statement, groups: Vec::new(), - current_group: Vec::new(), - cutoff: 1, + current_group: MemberChainGroup::default(), tab_width, } } - /// This function checks if the current grouping should be merged with the first group. - pub fn should_merge(&self, head_group: &HeadGroup) -> SyntaxResult { - Ok(!self.groups.len() >= 1 - && self.should_not_wrap(head_group)? - && !self.groups[0] - .first() - .map_or(false, |item| item.has_trailing_comments())) - } - /// starts a new group - pub fn start_group>(&mut self, flatten_item: I) { - debug_assert!(self.current_group.is_empty()); - self.current_group.push(flatten_item.into()); + pub fn start_group(&mut self, flatten_item: ChainEntry) { + debug_assert!(self.current_group.entries.is_empty()); + self.current_group.entries.push(flatten_item); } /// continues of starts a new group - pub fn start_or_continue_group>(&mut self, flatten_item: I) { - if self.current_group.is_empty() { + pub fn start_or_continue_group(&mut self, flatten_item: ChainEntry) { + if self.current_group.entries.is_empty() { self.start_group(flatten_item); } else { self.continue_group(flatten_item); @@ -62,51 +49,67 @@ impl Groups { } /// adds the passed element to the current group - pub fn continue_group>(&mut self, flatten_item: I) { - debug_assert!(!self.current_group.is_empty()); - self.current_group.push(flatten_item.into()); + pub fn continue_group(&mut self, flatten_item: ChainEntry) { + debug_assert!(!self.current_group.entries.is_empty()); + self.current_group.entries.push(flatten_item.into()); } /// clears the current group, and adds a new group to the groups pub fn close_group(&mut self) { - if !self.current_group.is_empty() { - let mut elements = vec![]; + if !self.current_group.entries.is_empty() { + let mut elements = MemberChainGroup::default(); std::mem::swap(&mut elements, &mut self.current_group); self.groups.push(elements); } } - /// It tells if the groups should be break on multiple lines - pub fn groups_should_break( - &self, - calls_count: usize, - head_group: &HeadGroup, - ) -> FormatResult { - // Do not allow the group to break if it only contains a single call expression - if calls_count <= 1 { - return Ok(false); + pub(super) fn finish(self) -> MemberChainGroups { + debug_assert!(self.current_group.entries().is_empty()); + + MemberChainGroups { + groups: self.groups, + tab_width: self.tab_width, + in_expression_statement: self.in_expression_statement, + cutoff: 1, } + } +} - // we want to check the simplicity of the call expressions only if we have at least - // two of them - // Check prettier: https://github.com/prettier/prettier/blob/main/src/language-js/print/member-chain.js#L389 - let call_expressions_are_not_simple = - calls_count > 2 && self.call_expressions_are_not_simple()?; +#[derive(Clone, Debug)] +/// Handles creation of groups while scanning the flatten items +pub(super) struct MemberChainGroups { + /// keeps track of the groups created + groups: Vec, - // TODO: add here will_break logic + /// If the current group is inside an expression statement. + /// + /// This information is important when evaluating the break of the groups. + in_expression_statement: bool, - let node_has_comments = self.has_comments()? || head_group.has_comments(); + tab_width: TabWidth, - let should_break = node_has_comments || call_expressions_are_not_simple; + /// This is a threshold of when we should start breaking the groups + /// + /// By default, it's 1, meaning that we start breaking after the first group. + cutoff: u8, +} - Ok(should_break) +impl MemberChainGroups { + /// This function checks if the current grouping should be merged with the first group. + pub fn should_merge(&self, head_group: &MemberChainGroup) -> SyntaxResult { + Ok(!self.groups.len() >= 1 + && self.should_not_wrap(head_group)? + && !self.groups[0] + .entries + .first() + .map_or(false, |item| item.has_trailing_comments())) } /// Checks if the groups contain comments. pub fn has_comments(&self) -> SyntaxResult { let mut has_leading_comments = false; - let flat_groups = self.groups.iter().flat_map(|item| item.iter()); + let flat_groups = self.groups.iter().flat_map(|item| item.entries.iter()); for item in flat_groups { if item.has_leading_comments()? { has_leading_comments = true; @@ -117,13 +120,13 @@ impl Groups { let has_trailing_comments = self .groups .iter() - .flat_map(|item| item.iter()) + .flat_map(|item| item.entries.iter()) .any(|item| item.has_trailing_comments()); let cutoff_has_leading_comments = if self.groups.len() >= self.cutoff as usize { let group = self.groups.get(self.cutoff as usize); if let Some(group) = group { - let first_item = group.first(); + let first_item = group.entries.first(); if let Some(first_item) = first_item { first_item.has_leading_comments()? } else { @@ -139,39 +142,14 @@ impl Groups { Ok(has_leading_comments || has_trailing_comments || cutoff_has_leading_comments) } - /// Format groups on multiple lines - pub fn write_joined_with_hard_line_breaks(&self, f: &mut JsFormatter) -> FormatResult<()> { - f.join_with(hard_line_break()) - .entries( - self.groups - .iter() - .map(|group| format_with(|f| f.join().entries(group.iter()).finish())), - ) - .finish() - } - - /// Creates two different versions of the formatted groups, one that goes in one line - /// and the other one that goes on multiple lines. - /// - /// It's up to the printer to decide which one to use. - pub fn write(&self, f: &mut JsFormatter) -> FormatResult<()> { - if self.groups.is_empty() { - return Ok(()); - } - - f.join() - .entries(self.groups.iter().flat_map(|group| group.iter())) - .finish() - } - /// Filters the stack of [FlattenItem] and return only the ones that /// contain [JsCallExpression]. The function returns the actual nodes. pub fn get_call_expressions(&self) -> impl Iterator { self.groups .iter() - .flat_map(|group| group.iter()) + .flat_map(|group| group.entries.iter()) .filter_map(|item| { - if let FlattenItem::CallExpression(call_expression, ..) = item { + if let ChainMember::CallExpression(call_expression, ..) = item.member() { Some(call_expression) } else { None @@ -179,37 +157,24 @@ impl Groups { }) } - /// We retrieve all the call expressions inside the group and we check if - /// their arguments are not simple. - pub fn call_expressions_are_not_simple(&self) -> SyntaxResult { - Ok(self.get_call_expressions().any(|call_expression| { - call_expression.arguments().map_or(false, |arguments| { - !arguments - .args() - .iter() - .filter_map(|argument| argument.ok()) - .all(|argument| SimpleArgument::new(argument).is_simple(0)) - }) - })) - } - /// This is an heuristic needed to check when the first element of the group /// Should be part of the "head" or the "tail". - fn should_not_wrap(&self, first_group: &HeadGroup) -> SyntaxResult { + fn should_not_wrap(&self, first_group: &MemberChainGroup) -> SyntaxResult { let tab_with = self.tab_width; let has_computed_property = if self.groups.len() > 1 { // SAFETY: guarded by the previous check let group = &self.groups[0]; group + .entries .first() - .map_or(false, |item| item.is_computed_expression()) + .map_or(false, |item| item.member().is_computed_expression()) } else { false }; - if first_group.items.len() == 1 { + if first_group.entries().len() == 1 { // SAFETY: access is guarded by the previous check - let first_node = first_group.items().first().unwrap(); + let first_node = first_group.entries().first().unwrap().member(); return Ok(first_node.is_this_expression() || (first_node.is_identifier_expression() @@ -223,9 +188,11 @@ impl Groups { let last_node_is_factory = self .groups .iter() - .flat_map(|group| group.iter()) + .flat_map(|group| group.entries.iter()) .last() - .map_or(false, |item| item.is_factory(false).unwrap_or(false)); + .map_or(false, |item| { + item.member().is_factory(false).unwrap_or(false) + }); Ok(last_node_is_factory || has_computed_property) } @@ -234,8 +201,8 @@ impl Groups { /// we move out the first group out of the groups pub(crate) fn should_merge_with_first_group( &mut self, - head_group: &HeadGroup, - ) -> Option>> { + head_group: &MemberChainGroup, + ) -> Option> { if self.should_merge(head_group).unwrap_or(false) { let mut new_groups = self.groups.split_off(1); // self.groups is now the head (one element), while `new_groups` is a new vector without the @@ -254,33 +221,63 @@ impl Groups { pub(crate) fn is_member_call_chain(&self) -> SyntaxResult { Ok(self.groups.len() > self.cutoff as usize || self.has_comments()?) } + + pub(super) fn iter(&self) -> impl Iterator { + self.groups.iter() + } +} + +impl Format for MemberChainGroups { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.join().entries(self.groups.iter()).finish() + } } -#[derive(Debug)] -pub(crate) struct HeadGroup { - items: Vec, +#[derive(Debug, Clone, Default)] +pub(super) struct MemberChainGroup { + entries: Vec, } -impl HeadGroup { - pub(crate) fn new(items: Vec) -> Self { - Self { items } +impl MemberChainGroup { + pub(super) fn into_entries(self) -> Vec { + self.entries } - fn items(&self) -> &[FlattenItem] { - &self.items + fn entries(&self) -> &[ChainEntry] { + &self.entries } - pub fn expand_group(&mut self, group: Vec) { - self.items.extend(group) + pub(super) fn expand_group(&mut self, group: impl IntoIterator) { + self.entries.extend(group) } - fn has_comments(&self) -> bool { - self.items.iter().any(|item| item.has_trailing_comments()) + pub(super) fn has_comments(&self) -> bool { + self.entries.iter().any(|item| item.has_trailing_comments()) } } -impl Format for HeadGroup { - fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> { - f.join().entries(self.items.iter()).finish() +impl From> for MemberChainGroup { + fn from(entries: Vec) -> Self { + Self { entries } + } +} + +impl Format for MemberChainGroup { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let last = self.entries.last(); + + let needs_parens = last.map_or(false, |last| match last.member() { + ChainMember::StaticMember(member) => member.needs_parentheses(), + ChainMember::ComputedMember(member) => member.needs_parentheses(), + _ => false, + }); + + let format_entries = format_with(|f| f.join().entries(self.entries.iter()).finish()); + + if needs_parens { + write!(f, [text("("), format_entries, text(")")]) + } else { + write!(f, [format_entries]) + } } } diff --git a/crates/rome_js_formatter/src/utils/member_chain/mod.rs b/crates/rome_js_formatter/src/utils/member_chain/mod.rs index 1efbde751d4..474dd3c6b27 100644 --- a/crates/rome_js_formatter/src/utils/member_chain/mod.rs +++ b/crates/rome_js_formatter/src/utils/member_chain/mod.rs @@ -1,155 +1,229 @@ -mod flatten_item; +///! Utility function that applies some heuristic to format chain member expressions and call expressions +///! +///! We want to transform code that looks like this: +///! +///! ```js +///! something.execute().then().then().catch() +///! ``` +///! +///! To something like this: +///! +///! ```js +///! something +///! .execute() +///! .then() +///! .then() +///! .catch() +///! ``` +///! +///! In order to achieve that we use the same heuristic that [Prettier applies]. +///! +///! The process is the following: +///! +///! ### Flattening the AST +///! We flatten the AST. See, the code above is actually nested, where the first member expression (`something`) +///! that we see is actually the last one. This is a oversimplified version of the AST: +///! +///! ```block +///! [ +///! .catch() [ +///! .then() [ +///! .then() [ +///! .execute() [ +///! something +///! ] +///! ] +///! ] +///! ] +///! ] +///! ``` +///! So we need to navigate the AST and make sure that `something` is actually +///! the first one. In a sense, we have to revert the chain of children. We will do that using a recursion. +///! +///! While we navigate the AST and we found particular nodes that we want to track, we also +///! format them. The format of these nodes is different from the standard version. +///! +///! Our formatter makes sure that we don't format twice the same nodes. Let's say for example that +///! we find a `something().execute()`, its AST is like this: +///! +///! ```block +///! JsCallExpression { +///! callee: JsStaticMember { +///! object: JsCallExpression { +///! callee: Reference { +///! execute +///! } +///! } +///! } +///! } +///! ``` +///! +///! When we track the first [rome_js_syntax::JsCallExpression], we hold basically all the children, +///! that applies for the rest of the nodes. If we decided to format all the children of each node, +///! we will risk to format the last node, the `Reference`, four times. +///! +///! To avoid this, when we encounter particular nodes, we don't format all of its children, but defer +///! the formatting to the child itself. +///! +///! The end result of the flattening, will create an array of something like this: +///! +///! ```block +///! [ Identifier, JsCallExpression, JsStaticMember, JsCallExpression ] +///! ``` +///! +///! ### Grouping +///! +///! After the flattening, we start the grouping. We want to group nodes in a way that will help us +///! to apply a deterministic formatting. +///! - first group will be the identifier +///! - the rest of the groups will be will start StaticMemberExpression followed by the rest of the nodes, +///! right before the end of the next StaticMemberExpression +///! +///! The first group is special, because it holds the reference; it has its own heuristic. +///! Inside the first group we store the first element of the flattened array, then: +///! +///! 1. as many as [rome_js_syntax::JsCallExpression] we can find, this cover cases like +///! `something()()().then()`; +///! 2. as many as [rome_js_syntax::JsComputedMemberExpression] we can find, this cover cases like +///! `something()()[1][3].then()`; +///! 3. as many as consecutive [rome_js_syntax::JsStaticMemberExpression] or [rome_js_syntax::JsComputedExpression], this cover cases like +///! `this.items[0].then()` +///! +///! The rest of the groups are essentially a sequence of `[StaticMemberExpression , CallExpression]`. +///! In order to achieve that, we simply start looping through the rest of the flatten items that we haven't seen. +///! +///! Eventually, we should have something like this: +///! +///! ```block +///! [ +///! [ReferenceIdentifier, CallExpression], // with possible computed expressions in the middle +///! [StaticMemberExpression, StaticMemberExpression, CallExpression], +///! [StaticMemberExpression, CallExpression], +///! [StaticMemberExpression], +///! ] +///! ``` +///! +///! [Prettier applies]: https://github.com/prettier/prettier/blob/main/src/language-js/print/member-chain.js +mod chain_member; mod groups; mod simple_argument; +use crate::parentheses::resolve_expression_parent; use crate::prelude::*; -use crate::utils::member_chain::flatten_item::FlattenItem; -use crate::utils::member_chain::groups::{Groups, HeadGroup}; -use rome_formatter::{format_args, write, Buffer, Comments, CstFormatContext, PreambleBuffer}; -use rome_js_syntax::{ - JsCallExpression, JsComputedMemberExpression, JsExpressionStatement, JsLanguage, - JsStaticMemberExpression, +use crate::utils::member_chain::chain_member::{ChainEntry, ChainMember}; +use crate::utils::member_chain::groups::{ + MemberChainGroup, MemberChainGroups, MemberChainGroupsBuilder, }; -use rome_js_syntax::{JsSyntaxKind, JsSyntaxNode}; +use crate::utils::member_chain::simple_argument::SimpleArgument; +use rome_formatter::{format_args, write, Buffer, Comments, CstFormatContext, PreambleBuffer}; +use rome_js_syntax::{JsAnyExpression, JsCallExpression, JsExpressionStatement, JsLanguage}; use rome_rowan::{AstNode, SyntaxResult}; -/// Utility function that applies some heuristic to format chain member expressions and call expressions -/// -/// We want to transform code that looks like this: -/// -/// ```js -/// something.execute().then().then().catch() -/// ``` -/// -/// To something like this: -/// -/// ```js -/// something -/// .execute() -/// .then() -/// .then() -/// .catch() -/// ``` -/// -/// In order to achieve that we use the same heuristic that [Prettier applies]. -/// -/// The process is the following: -/// -/// ### Flattening the AST -/// We flatten the AST. See, the code above is actually nested, where the first member expression (`something`) -/// that we see is actually the last one. This is a oversimplified version of the AST: -/// -/// ```block -/// [ -/// .catch() [ -/// .then() [ -/// .then() [ -/// .execute() [ -/// something -/// ] -/// ] -/// ] -/// ] -/// ] -/// ``` -/// So we need to navigate the AST and make sure that `something` is actually -/// the first one. In a sense, we have to revert the chain of children. We will do that using a recursion. -/// -/// While we navigate the AST and we found particular nodes that we want to track, we also -/// format them. The format of these nodes is different from the standard version. -/// -/// Our formatter makes sure that we don't format twice the same nodes. Let's say for example that -/// we find a `something().execute()`, its AST is like this: -/// -/// ```block -/// JsCallExpression { -/// callee: JsStaticMember { -/// object: JsCallExpression { -/// callee: Reference { -/// execute -/// } -/// } -/// } -/// } -/// ``` -/// -/// When we track the first [rome_js_syntax::JsCallExpression], we hold basically all the children, -/// that applies for the rest of the nodes. If we decided to format all the children of each node, -/// we will risk to format the last node, the `Reference`, four times. -/// -/// To avoid this, when we encounter particular nodes, we don't format all of its children, but defer -/// the formatting to the child itself. -/// -/// The end result of the flattening, will create an array of something like this: -/// -/// ```block -/// [ Identifier, JsCallExpression, JsStaticMember, JsCallExpression ] -/// ``` -/// -/// ### Grouping -/// -/// After the flattening, we start the grouping. We want to group nodes in a way that will help us -/// to apply a deterministic formatting. -/// - first group will be the identifier -/// - the rest of the groups will be will start StaticMemberExpression followed by the rest of the nodes, -/// right before the end of the next StaticMemberExpression -/// -/// The first group is special, because it holds the reference; it has its own heuristic. -/// Inside the first group we store the first element of the flattened array, then: -/// -/// 1. as many as [rome_js_syntax::JsCallExpression] we can find, this cover cases like -/// `something()()().then()`; -/// 2. as many as [rome_js_syntax::JsComputedMemberExpression] we can find, this cover cases like -/// `something()()[1][3].then()`; -/// 3. as many as consecutive [rome_js_syntax::JsStaticMemberExpression] or [rome_js_syntax::JsComputedExpression], this cover cases like -/// `this.items[0].then()` -/// -/// The rest of the groups are essentially a sequence of `[StaticMemberExpression , CallExpression]`. -/// In order to achieve that, we simply start looping through the rest of the flatten items that we haven't seen. -/// -/// Eventually, we should have something like this: -/// -/// ```block -/// [ -/// [ReferenceIdentifier, CallExpression], // with possible computed expressions in the middle -/// [StaticMemberExpression, StaticMemberExpression, CallExpression], -/// [StaticMemberExpression, CallExpression], -/// [StaticMemberExpression], -/// ] -/// ``` -/// -/// [Prettier applies]: https://github.com/prettier/prettier/blob/main/src/language-js/print/member-chain.js -pub fn format_call_expression(syntax_node: &JsSyntaxNode, f: &mut JsFormatter) -> FormatResult<()> { - let (calls_count, head_group, rest_of_groups) = get_call_expression_groups(syntax_node, f)?; - write_groups(calls_count, head_group, rest_of_groups, f) +#[derive(Debug, Clone)] +pub(crate) struct MemberChain { + calls_count: usize, + head: MemberChainGroup, + tail: MemberChainGroups, +} + +impl MemberChain { + /// It tells if the groups should be break on multiple lines + pub(crate) fn groups_should_break(&self) -> FormatResult { + // Do not allow the group to break if it only contains a single call expression + if self.calls_count <= 1 { + return Ok(false); + } + + // we want to check the simplicity of the call expressions only if we have at least + // two of them + // Check prettier: https://github.com/prettier/prettier/blob/main/src/language-js/print/member-chain.js#L389 + let call_expressions_are_not_simple = + self.calls_count > 2 && self.call_expressions_are_not_simple()?; + + // TODO: add here will_break logic + + let node_has_comments = self.tail.has_comments()? || self.head.has_comments(); + + let should_break = node_has_comments || call_expressions_are_not_simple; + + Ok(should_break) + } + + /// We retrieve all the call expressions inside the group and we check if + /// their arguments are not simple. + fn call_expressions_are_not_simple(&self) -> SyntaxResult { + Ok(self.tail.get_call_expressions().any(|call_expression| { + call_expression.arguments().map_or(false, |arguments| { + !arguments + .args() + .iter() + .filter_map(|argument| argument.ok()) + .all(|argument| SimpleArgument::new(argument).is_simple(0)) + }) + })) + } +} + +impl Format for MemberChain { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + // TODO use Alternatives once available + write!(f, [&self.head])?; + + if self.groups_should_break()? { + write!( + f, + [indent(&format_args!( + hard_line_break(), + format_with(|f| { + f.join_with(hard_line_break()) + .entries(self.tail.iter()) + .finish() + }) + ))] + ) + } else { + // TODO This line suffix boundary shouldn't be needed but currently is because comments + // can move over node boundaries. Follow up when re-working member chain formatting + let mut buffer = PreambleBuffer::new(f, line_suffix_boundary()); + + write!(buffer, [&self.tail]) + } + } } -fn get_call_expression_groups( - syntax_node: &JsSyntaxNode, +pub(crate) fn get_member_chain( + call_expression: &JsCallExpression, f: &mut JsFormatter, -) -> SyntaxResult<(usize, HeadGroup, Groups)> { - let mut flattened_items = vec![]; - let parent_is_expression_statement = syntax_node.parent().map_or(false, |parent| { - JsExpressionStatement::can_cast(parent.kind()) - }); +) -> SyntaxResult { + let mut chain_members = vec![]; + let parent_is_expression_statement = resolve_expression_parent(call_expression.syntax()) + .map_or(false, |parent| { + JsExpressionStatement::can_cast(parent.kind()) + }); - flatten_call_expression(&mut flattened_items, syntax_node, &f.context().comments())?; + let root = flatten_member_chain( + &mut chain_members, + call_expression.clone().into(), + &f.context().comments(), + )?; + + chain_members.push(root); // Count the number of CallExpression in the chain, // will be used later to decide on how to format it - let calls_count = flattened_items + let calls_count = chain_members .iter() - .filter(|item| item.is_loose_call_expression()) + .filter(|item| item.member().is_loose_call_expression()) .count(); // as explained before, the first group is particular, so we calculate it - let index_to_split_at = compute_first_group_index(&flattened_items); + let index_to_split_at = compute_first_group_index(&chain_members); // we have the index where we want to take the first group - let remaining_groups = flattened_items.split_off(index_to_split_at); - let first_group = flattened_items; + let remaining_groups = chain_members.split_off(index_to_split_at); + let first_group = chain_members; - let mut head_group = HeadGroup::new(first_group); + let mut head_group = MemberChainGroup::from(first_group); // `flattened_items` now contains only the nodes that should have a sequence of // `[ StaticMemberExpression -> AnyNode + JsCallExpression ]` @@ -162,17 +236,23 @@ fn get_call_expression_groups( // Here we check if the first element of Groups::groups can be moved inside the head. // If so, then we extract it and concatenate it together with the head. if let Some(group_to_merge) = rest_of_groups.should_merge_with_first_group(&head_group) { - let group_to_merge = group_to_merge.into_iter().flatten().collect(); + let group_to_merge = group_to_merge + .into_iter() + .flat_map(|group| group.into_entries()); head_group.expand_group(group_to_merge); } - Ok((calls_count, head_group, rest_of_groups)) + Ok(MemberChain { + calls_count, + head: head_group, + tail: rest_of_groups, + }) } /// Retrieves the index where we want to calculate the first group. /// The first group gathers inside it all those nodes that are not a sequence of something like: /// `[ StaticMemberExpression -> AnyNode + JsCallExpression ]` -fn compute_first_group_index(flatten_items: &[FlattenItem]) -> usize { +fn compute_first_group_index(flatten_items: &[ChainEntry]) -> usize { flatten_items .iter() .enumerate() @@ -180,14 +260,14 @@ fn compute_first_group_index(flatten_items: &[FlattenItem]) -> usize { .skip(1) // we now find the index, all items before this index will belong to the first group .find_map(|(index, item)| { - let should_skip = match item { + let should_skip = match item.member() { // This where we apply the first two points explained in the description of the main public function. // We want to keep iterating over the items until we have call expressions or computed expressions: // - `something()()()()` // - `something[1][2][4]` // - `something[1]()[3]()` // - `something()[2].something.else[0]` - FlattenItem::CallExpression(_) | FlattenItem::ComputedMember(_) => true, + ChainMember::CallExpression(_) | ChainMember::ComputedMember(_) => true, // SAFETY: The check `flatten_items[index + 1]` will never panic at runtime because // 1. The array will always have at least two items @@ -218,11 +298,11 @@ fn compute_first_group_index(flatten_items: &[FlattenItem]) -> usize { // and the next one is a call expression... the `matches!` fails and the loop is stopped. // // The last element of the array is always a `CallExpression`, which allows us to avoid the overflow of the array. - FlattenItem::StaticMember(_) => { + ChainMember::StaticMember(_) => { let next_flatten_item = &flatten_items[index + 1]; matches!( - next_flatten_item, - FlattenItem::StaticMember(_) | FlattenItem::ComputedMember(_) + next_flatten_item.member(), + ChainMember::StaticMember(_) | ChainMember::ComputedMember(_) ) } _ => false, @@ -242,125 +322,111 @@ fn compute_first_group_index(flatten_items: &[FlattenItem]) -> usize { /// computes groups coming after the first group fn compute_groups( - flatten_items: impl Iterator, + flatten_items: impl Iterator, in_expression_statement: bool, f: &JsFormatter, -) -> Groups { +) -> MemberChainGroups { let mut has_seen_call_expression = false; - let mut groups = Groups::new(in_expression_statement, f.context().tab_width()); + let mut groups_builder = + MemberChainGroupsBuilder::new(in_expression_statement, f.context().tab_width()); for item in flatten_items { - let has_trailing_comments = item.as_syntax().has_trailing_comments(); + let has_trailing_comments = item.member().syntax().has_trailing_comments(); - match item { - FlattenItem::StaticMember(_) => { + match item.member() { + ChainMember::StaticMember(_) => { // if we have seen a JsCallExpression, we want to close the group. // The resultant group will be something like: [ . , then, () ]; // `.` and `then` belong to the previous StaticMemberExpression, // and `()` belong to the call expression we just encountered if has_seen_call_expression { - groups.close_group(); - groups.start_or_continue_group(item); + groups_builder.close_group(); + groups_builder.start_or_continue_group(item); has_seen_call_expression = false; } else { - groups.start_or_continue_group(item); + groups_builder.start_or_continue_group(item); } } - FlattenItem::CallExpression(_) => { - let is_loose_call_expression = item.is_loose_call_expression(); - groups.start_or_continue_group(item); + ChainMember::CallExpression(_) => { + let is_loose_call_expression = item.member().is_loose_call_expression(); + groups_builder.start_or_continue_group(item); if is_loose_call_expression { has_seen_call_expression = true; } } - FlattenItem::ComputedMember(_) => { - groups.start_or_continue_group(item); + ChainMember::ComputedMember(_) => { + groups_builder.start_or_continue_group(item); } - FlattenItem::Node(_) => groups.continue_group(item), + ChainMember::Node(_) => groups_builder.continue_group(item), } // Close the group immediately if the node had any trailing comments to // ensure those are printed in a trailing position for the token they // were originally commenting if has_trailing_comments { - groups.close_group(); + groups_builder.close_group(); } } // closing possible loose groups - groups.close_group(); - - groups -} + groups_builder.close_group(); -/// Formats together the first group and the rest of groups -fn write_groups( - calls_count: usize, - head_group: HeadGroup, - groups: Groups, - f: &mut JsFormatter, -) -> FormatResult<()> { - // TODO use Alternatives once available - write!(f, [head_group])?; - - if groups.groups_should_break(calls_count, &head_group)? { - write!( - f, - [indent(&format_args!( - hard_line_break(), - format_with(|f| { groups.write_joined_with_hard_line_breaks(f) }) - ))] - ) - } else { - // TODO This line suffix boundary shouldn't be needed but currently is because comments - // can move over node boundaries. Follow up when re-working member chain formatting - let mut buffer = PreambleBuffer::new(f, line_suffix_boundary()); - - write!(buffer, [format_with(|f| { groups.write(f) })]) - } + groups_builder.finish() } /// This function tries to flatten the AST. It stores nodes and its formatted version /// inside an vector of [FlattenItem]. The first element of the vector is the last one. -fn flatten_call_expression( - queue: &mut Vec, - node: &JsSyntaxNode, +fn flatten_member_chain( + queue: &mut Vec, + node: JsAnyExpression, comments: &Comments, -) -> SyntaxResult<()> { - if comments.is_suppressed(node) { - queue.push(FlattenItem::Node(node.clone())) +) -> SyntaxResult { + use JsAnyExpression::*; + + if comments.is_suppressed(node.syntax()) { + return Ok(ChainEntry::Member(ChainMember::Node(node.into_syntax()))); } - match node.kind() { - JsSyntaxKind::JS_CALL_EXPRESSION => { - let call_expression = JsCallExpression::cast(node.clone()).unwrap(); + match node { + JsCallExpression(call_expression) => { let callee = call_expression.callee()?; - flatten_call_expression(queue, callee.syntax(), comments)?; + let left = flatten_member_chain(queue, callee, comments)?; + queue.push(left); - queue.push(FlattenItem::CallExpression(call_expression)); + Ok(ChainEntry::Member(ChainMember::CallExpression( + call_expression, + ))) } - JsSyntaxKind::JS_STATIC_MEMBER_EXPRESSION => { - let static_member = JsStaticMemberExpression::cast(node.clone()).unwrap(); + JsStaticMemberExpression(static_member) => { let object = static_member.object()?; - flatten_call_expression(queue, object.syntax(), comments)?; + let left = flatten_member_chain(queue, object, comments)?; + queue.push(left); - queue.push(FlattenItem::StaticMember(static_member)); + Ok(ChainEntry::Member(ChainMember::StaticMember(static_member))) } - JsSyntaxKind::JS_COMPUTED_MEMBER_EXPRESSION => { - let computed_expression = JsComputedMemberExpression::cast(node.clone()).unwrap(); + JsComputedMemberExpression(computed_expression) => { let object = computed_expression.object()?; - flatten_call_expression(queue, object.syntax(), comments)?; - queue.push(FlattenItem::ComputedMember(computed_expression)); + let left = flatten_member_chain(queue, object, comments)?; + queue.push(left); + + Ok(ChainEntry::Member(ChainMember::ComputedMember( + computed_expression, + ))) } + JsParenthesizedExpression(parenthesized) => { + let inner = flatten_member_chain(queue, parenthesized.expression()?, comments)?; - _ => { - queue.push(FlattenItem::Node(node.clone())); + Ok(ChainEntry::Parenthesized { + member: inner.into_member(), + top_most_parentheses: parenthesized, + }) } + expression => Ok(ChainEntry::Member(ChainMember::Node( + expression.into_syntax(), + ))), } - - Ok(()) } /// Here we check if the length of the groups exceeds the cutoff or there are comments @@ -370,6 +436,7 @@ pub fn is_member_call_chain( expression: &JsCallExpression, f: &mut JsFormatter, ) -> SyntaxResult { - let (_, _, groups) = get_call_expression_groups(expression.syntax(), f)?; - groups.is_member_call_chain() + let chain = get_member_chain(expression, f)?; + + chain.tail.is_member_call_chain() } diff --git a/crates/rome_js_formatter/src/utils/mod.rs b/crates/rome_js_formatter/src/utils/mod.rs index 89bad17c65d..26701e118ac 100644 --- a/crates/rome_js_formatter/src/utils/mod.rs +++ b/crates/rome_js_formatter/src/utils/mod.rs @@ -23,7 +23,7 @@ pub(crate) use binary_like_expression::{ JsAnyBinaryLikeLeftExpression, }; pub(crate) use conditional::{resolve_expression, resolve_expression_syntax, JsAnyConditional}; -pub(crate) use member_chain::format_call_expression; +pub(crate) use member_chain::get_member_chain; pub(crate) use object_like::JsObjectLike; pub(crate) use object_pattern_like::JsObjectPatternLike; use rome_formatter::{format_args, normalize_newlines, write, Buffer, VecBuffer}; diff --git a/crates/rome_js_formatter/tests/specs/js/module/expression/sequence_expression.js.snap b/crates/rome_js_formatter/tests/specs/js/module/expression/sequence_expression.js.snap index 0b1810b4268..143b81b0367 100644 --- a/crates/rome_js_formatter/tests/specs/js/module/expression/sequence_expression.js.snap +++ b/crates/rome_js_formatter/tests/specs/js/module/expression/sequence_expression.js.snap @@ -47,13 +47,15 @@ Quote style: Double Quotes ----- a, b; -const f = () => (____________first, -____________second, -____________third, -____________third, -____________third, -____________third, -____________third); +const f = () => ( + ____________first, + ____________second, + ____________third, + ____________third, + ____________third, + ____________third, + ____________third +); ____________first, ____________second, diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/assignment/issue-2184.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/assignment/issue-2184.js.snap index 10cdd377973..f328c14db4f 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/assignment/issue-2184.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/assignment/issue-2184.js.snap @@ -17,25 +17,29 @@ const areaPercentageDiff = ( ```diff --- Prettier +++ Rome -@@ -1,4 +1,3 @@ - const areaPercentageDiff = ( +@@ -1,4 +1,4 @@ +-const areaPercentageDiff = ( - topRankedZoneFit.areaPercentageRemaining - - previousZoneFitNow.areaPercentageRemaining -+ topRankedZoneFit.areaPercentageRemaining - previousZoneFitNow.areaPercentageRemaining - ).toFixed(2); +-).toFixed(2); ++const areaPercentageDiff = ++ (topRankedZoneFit.areaPercentageRemaining - previousZoneFitNow.areaPercentageRemaining).toFixed( ++ 2, ++ ); ``` # Output ```js -const areaPercentageDiff = ( - topRankedZoneFit.areaPercentageRemaining - previousZoneFitNow.areaPercentageRemaining -).toFixed(2); +const areaPercentageDiff = + (topRankedZoneFit.areaPercentageRemaining - previousZoneFitNow.areaPercentageRemaining).toFixed( + 2, + ); ``` # Lines exceeding max width of 80 characters ``` - 2: topRankedZoneFit.areaPercentageRemaining - previousZoneFitNow.areaPercentageRemaining + 2: (topRankedZoneFit.areaPercentageRemaining - previousZoneFitNow.areaPercentageRemaining).toFixed( ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/async/inline-await.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/async/inline-await.js.snap index d28719aa77b..60e4a08cbf1 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/async/inline-await.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/async/inline-await.js.snap @@ -25,11 +25,11 @@ async function f() { - .leftJoin("bla") - .where("id", "in", [1, 2, 3, 4]) - ).map(({ id, name }) => ({ id, name })); -+ const admins = (await (db ++ const admins = (await db + .select("*") + .from("admins") + .leftJoin("bla") -+ .where("id", "in", [1, 2, 3, 4]))).map(({ id, name }) => ({ id, name })); ++ .where("id", "in", [1, 2, 3, 4])).map(({ id, name }) => ({ id, name })); } ``` @@ -37,11 +37,11 @@ async function f() { ```js async function f() { - const admins = (await (db + const admins = (await db .select("*") .from("admins") .leftJoin("bla") - .where("id", "in", [1, 2, 3, 4]))).map(({ id, name }) => ({ id, name })); + .where("id", "in", [1, 2, 3, 4])).map(({ id, name }) => ({ id, name })); } ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/binary-expressions/call.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/binary-expressions/call.js.snap index 657390f79bb..91c6aeafdae 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/binary-expressions/call.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/binary-expressions/call.js.snap @@ -78,100 +78,85 @@ source: crates/rome_js_formatter/tests/prettier_tests.rs ```diff --- Prettier +++ Rome -@@ -1,37 +1,37 @@ - ( - aaaaaaaaaaaaaaaaaaaaaaaaa && -- bbbbbbbbbbbbbbbbbbbbbbbbb && -- ccccccccccccccccccccccccc && -- ddddddddddddddddddddddddd && +@@ -1,38 +1,30 @@ +-( +- aaaaaaaaaaaaaaaaaaaaaaaaa && ++(aaaaaaaaaaaaaaaaaaaaaaaaa && + bbbbbbbbbbbbbbbbbbbbbbbbb && + ccccccccccccccccccccccccc && + ddddddddddddddddddddddddd && - eeeeeeeeeeeeeeeeeeeeeeeee -+ bbbbbbbbbbbbbbbbbbbbbbbbb && -+ ccccccccccccccccccccccccc && -+ ddddddddddddddddddddddddd && -+ eeeeeeeeeeeeeeeeeeeeeeeee - )(); +-)(); ++ eeeeeeeeeeeeeeeeeeeeeeeee)(); (aa && bb && cc && dd && ee)(); - ( - aaaaaaaaaaaaaaaaaaaaaaaaa + -- bbbbbbbbbbbbbbbbbbbbbbbbb + -- ccccccccccccccccccccccccc + -- ddddddddddddddddddddddddd + +-( +- aaaaaaaaaaaaaaaaaaaaaaaaa + ++(aaaaaaaaaaaaaaaaaaaaaaaaa + + bbbbbbbbbbbbbbbbbbbbbbbbb + + ccccccccccccccccccccccccc + + ddddddddddddddddddddddddd + - eeeeeeeeeeeeeeeeeeeeeeeee -+ bbbbbbbbbbbbbbbbbbbbbbbbb + -+ ccccccccccccccccccccccccc + -+ ddddddddddddddddddddddddd + -+ eeeeeeeeeeeeeeeeeeeeeeeee - )(); +-)(); ++ eeeeeeeeeeeeeeeeeeeeeeeee)(); (aa + bb + cc + dd + ee)(); - ( - aaaaaaaaaaaaaaaaaaaaaaaaa && -- bbbbbbbbbbbbbbbbbbbbbbbbb && -- ccccccccccccccccccccccccc && -- ddddddddddddddddddddddddd && +-( +- aaaaaaaaaaaaaaaaaaaaaaaaa && ++(aaaaaaaaaaaaaaaaaaaaaaaaa && + bbbbbbbbbbbbbbbbbbbbbbbbb && + ccccccccccccccccccccccccc && + ddddddddddddddddddddddddd && - eeeeeeeeeeeeeeeeeeeeeeeee -+ bbbbbbbbbbbbbbbbbbbbbbbbb && -+ ccccccccccccccccccccccccc && -+ ddddddddddddddddddddddddd && -+ eeeeeeeeeeeeeeeeeeeeeeeee - )()()(); +-)()()(); ++ eeeeeeeeeeeeeeeeeeeeeeeee)()()(); - ( - aaaaaaaaaaaaaaaaaaaaaaaaa && -- bbbbbbbbbbbbbbbbbbbbbbbbb && -- ccccccccccccccccccccccccc && -- ddddddddddddddddddddddddd && +-( +- aaaaaaaaaaaaaaaaaaaaaaaaa && ++(aaaaaaaaaaaaaaaaaaaaaaaaa && + bbbbbbbbbbbbbbbbbbbbbbbbb && + ccccccccccccccccccccccccc && + ddddddddddddddddddddddddd && - eeeeeeeeeeeeeeeeeeeeeeeee -+ bbbbbbbbbbbbbbbbbbbbbbbbb && -+ ccccccccccccccccccccccccc && -+ ddddddddddddddddddddddddd && -+ eeeeeeeeeeeeeeeeeeeeeeeee - )( +-)( ++ eeeeeeeeeeeeeeeeeeeeeeeee)( aaaaaaaaaaaaaaaaaaaaaaaaa && bbbbbbbbbbbbbbbbbbbbbbbbb && + ccccccccccccccccccccccccc && ``` # Output ```js -( - aaaaaaaaaaaaaaaaaaaaaaaaa && - bbbbbbbbbbbbbbbbbbbbbbbbb && - ccccccccccccccccccccccccc && - ddddddddddddddddddddddddd && - eeeeeeeeeeeeeeeeeeeeeeeee -)(); +(aaaaaaaaaaaaaaaaaaaaaaaaa && + bbbbbbbbbbbbbbbbbbbbbbbbb && + ccccccccccccccccccccccccc && + ddddddddddddddddddddddddd && + eeeeeeeeeeeeeeeeeeeeeeeee)(); (aa && bb && cc && dd && ee)(); -( - aaaaaaaaaaaaaaaaaaaaaaaaa + - bbbbbbbbbbbbbbbbbbbbbbbbb + - ccccccccccccccccccccccccc + - ddddddddddddddddddddddddd + - eeeeeeeeeeeeeeeeeeeeeeeee -)(); +(aaaaaaaaaaaaaaaaaaaaaaaaa + + bbbbbbbbbbbbbbbbbbbbbbbbb + + ccccccccccccccccccccccccc + + ddddddddddddddddddddddddd + + eeeeeeeeeeeeeeeeeeeeeeeee)(); (aa + bb + cc + dd + ee)(); -( - aaaaaaaaaaaaaaaaaaaaaaaaa && - bbbbbbbbbbbbbbbbbbbbbbbbb && - ccccccccccccccccccccccccc && - ddddddddddddddddddddddddd && - eeeeeeeeeeeeeeeeeeeeeeeee -)()()(); +(aaaaaaaaaaaaaaaaaaaaaaaaa && + bbbbbbbbbbbbbbbbbbbbbbbbb && + ccccccccccccccccccccccccc && + ddddddddddddddddddddddddd && + eeeeeeeeeeeeeeeeeeeeeeeee)()()(); -( - aaaaaaaaaaaaaaaaaaaaaaaaa && - bbbbbbbbbbbbbbbbbbbbbbbbb && - ccccccccccccccccccccccccc && - ddddddddddddddddddddddddd && - eeeeeeeeeeeeeeeeeeeeeeeee -)( +(aaaaaaaaaaaaaaaaaaaaaaaaa && + bbbbbbbbbbbbbbbbbbbbbbbbb && + ccccccccccccccccccccccccc && + ddddddddddddddddddddddddd && + eeeeeeeeeeeeeeeeeeeeeeeee)( aaaaaaaaaaaaaaaaaaaaaaaaa && bbbbbbbbbbbbbbbbbbbbbbbbb && ccccccccccccccccccccccccc && diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/classes/assignment.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/classes/assignment.js.snap index caf89fbb470..5e10f3df8a8 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/classes/assignment.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/classes/assignment.js.snap @@ -44,14 +44,17 @@ module.exports = class A extends B { ```diff --- Prettier +++ Rome -@@ -1,4 +1,5 @@ +@@ -1,6 +1,6 @@ -aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg2 = class extends ( +- aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg1 +-) { +aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee -+ .ffffffff.gggggggg2 = class extends ( - aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg1 - ) { ++ .ffffffff.gggggggg2 = class extends aaaaaaaa.bbbbbbbb.cccccccc.dddddddd ++ .eeeeeeee.ffffffff.gggggggg1 { method() { -@@ -12,17 +13,15 @@ + console.log("foo"); + } +@@ -12,17 +12,15 @@ } }; @@ -79,9 +82,8 @@ module.exports = class A extends B { ```js aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee - .ffffffff.gggggggg2 = class extends ( - aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg1 -) { + .ffffffff.gggggggg2 = class extends aaaaaaaa.bbbbbbbb.cccccccc.dddddddd + .eeeeeeee.ffffffff.gggggggg1 { method() { console.log("foo"); } diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/closure-compiler-type-cast.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/closure-compiler-type-cast.js.snap index bec94e3b271..2067797fcd1 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/closure-compiler-type-cast.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/closure-compiler-type-cast.js.snap @@ -73,23 +73,14 @@ const style2 =/** ```diff --- Prettier +++ Rome -@@ -1,28 +1,32 @@ - // test to make sure comments are attached correctly --let inlineComment = /* some comment */ someReallyLongFunctionCall( -+let inlineComment = /* some comment */ (someReallyLongFunctionCall( - withLots, - ofArguments, --); -+)); - - let object = { -- key: /* some comment */ someReallyLongFunctionCall(withLots, ofArguments), -+ key: /* some comment */ (someReallyLongFunctionCall(withLots, ofArguments)), +@@ -9,35 +9,39 @@ }; // preserve parens only for type casts - let assignment = /** @type {string} */ (getValue()); - let value = /** @type {string} */ (this.members[0]).functionCall(); +-let assignment = /** @type {string} */ (getValue()); +-let value = /** @type {string} */ (this.members[0]).functionCall(); ++let assignment = /** @type {string} */ getValue(); ++let value = /** @type {string} */ this.members[0].functionCall(); -functionCall(1 + /** @type {string} */ (value), /** @type {!Foo} */ ({})); +functionCall( @@ -114,10 +105,14 @@ const style2 =/** +var newArray = test(/** @type {array} */ numberOrString.map((x) => x)); // The numberOrString.map CallExpression is typecast - var newArray = /** @type {array} */ (numberOrString.map((x) => x)); -@@ -30,14 +34,14 @@ - var newArray = test(/** @type {array} */ (numberOrString.map((x) => x))); - var newArray = test(/** @type {array} */ (numberOrString.map((x) => x))); +-var newArray = /** @type {array} */ (numberOrString.map((x) => x)); +-var newArray = /** @type {array} */ (numberOrString.map((x) => x)); +-var newArray = test(/** @type {array} */ (numberOrString.map((x) => x))); +-var newArray = test(/** @type {array} */ (numberOrString.map((x) => x))); ++var newArray = /** @type {array} */ numberOrString.map((x) => x); ++var newArray = /** @type {array} */ numberOrString.map((x) => x); ++var newArray = test(/** @type {array} */ numberOrString.map((x) => x)); ++var newArray = test(/** @type {array} */ numberOrString.map((x) => x)); -test(/** @type {number} */ (num) + 1); -test(/** @type {!Array} */ (arrOrString).length + 1); @@ -161,18 +156,18 @@ const style2 =/** ```js // test to make sure comments are attached correctly -let inlineComment = /* some comment */ (someReallyLongFunctionCall( +let inlineComment = /* some comment */ someReallyLongFunctionCall( withLots, ofArguments, -)); +); let object = { - key: /* some comment */ (someReallyLongFunctionCall(withLots, ofArguments)), + key: /* some comment */ someReallyLongFunctionCall(withLots, ofArguments), }; // preserve parens only for type casts -let assignment = /** @type {string} */ (getValue()); -let value = /** @type {string} */ (this.members[0]).functionCall(); +let assignment = /** @type {string} */ getValue(); +let value = /** @type {string} */ this.members[0].functionCall(); functionCall( 1 + /** @type {string} */ @@ -191,10 +186,10 @@ var newArray = test(/** @type {array} */ numberOrString.map((x) => x)); var newArray = test(/** @type {array} */ numberOrString.map((x) => x)); // The numberOrString.map CallExpression is typecast -var newArray = /** @type {array} */ (numberOrString.map((x) => x)); -var newArray = /** @type {array} */ (numberOrString.map((x) => x)); -var newArray = test(/** @type {array} */ (numberOrString.map((x) => x))); -var newArray = test(/** @type {array} */ (numberOrString.map((x) => x))); +var newArray = /** @type {array} */ numberOrString.map((x) => x); +var newArray = /** @type {array} */ numberOrString.map((x) => x); +var newArray = test(/** @type {array} */ numberOrString.map((x) => x)); +var newArray = test(/** @type {array} */ numberOrString.map((x) => x)); test(/** @type {number} */ num + 1); test(/** @type {!Array} */ arrOrString.length + 1); diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/comment-in-the-middle.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/comment-in-the-middle.js.snap index 9d5e8c2aae1..d61bb653ac9 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/comment-in-the-middle.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/comment-in-the-middle.js.snap @@ -24,8 +24,9 @@ console.log(a.foo()); ```diff --- Prettier +++ Rome -@@ -1,11 +1,11 @@ +@@ -1,11 +1,12 @@ var a = ++ window /** - * bla bla bla - * @type {string | @@ -40,7 +41,8 @@ console.log(a.foo()); +* bla bla bla + */ //2 - (window["s"]).toString(); +- (window["s"]).toString(); ++ ["s"].toString(); console.log(a.foo()); ``` @@ -48,6 +50,7 @@ console.log(a.foo()); ```js var a = + window /** * bla bla bla * @type {string | @@ -56,7 +59,7 @@ var a = * bla bla bla */ //2 - (window["s"]).toString(); + ["s"].toString(); console.log(a.foo()); ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/iife.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/iife.js.snap index 72114ef8fac..728349da835 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/iife.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/iife.js.snap @@ -26,42 +26,41 @@ const helpers = /** @type {Helpers} */ (( ```diff --- Prettier +++ Rome -@@ -1,13 +1,11 @@ - const helpers1 = /** @type {Helpers} */ (((helpers = {}) => helpers)()); +@@ -1,13 +1,10 @@ +-const helpers1 = /** @type {Helpers} */ (((helpers = {}) => helpers)()); ++const helpers1 = /** @type {Helpers} */ ((helpers = {}) => helpers)(); -const helpers2 = /** @type {Helpers} */ ( - (function () { - return something; - })() -); -+const helpers2 = /** @type {Helpers} */ ((function () { ++const helpers2 = /** @type {Helpers} */ (function () { + return something; -+})()); ++})(); // TODO: @param is misplaced https://github.com/prettier/prettier/issues/5850 -const helpers = /** @type {Helpers} */ ( +const helpers = /** @type {Helpers} */ -+ ( /** @param {Partial} helpers */ - ((helpers = {}) => helpers)() -); -+ ((helpers = {}) => helpers)()); ++ ((helpers = {}) => helpers)(); ``` # Output ```js -const helpers1 = /** @type {Helpers} */ (((helpers = {}) => helpers)()); +const helpers1 = /** @type {Helpers} */ ((helpers = {}) => helpers)(); -const helpers2 = /** @type {Helpers} */ ((function () { +const helpers2 = /** @type {Helpers} */ (function () { return something; -})()); +})(); // TODO: @param is misplaced https://github.com/prettier/prettier/issues/5850 const helpers = /** @type {Helpers} */ - ( /** @param {Partial} helpers */ - ((helpers = {}) => helpers)()); + ((helpers = {}) => helpers)(); ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-4124.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-4124.js.snap index 05644283018..c561aea9791 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-4124.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-4124.js.snap @@ -30,29 +30,30 @@ const test = /** @type (function (*): ?|undefined) */ (foo); --- Prettier +++ Rome @@ -1,18 +1,22 @@ - /** @type {Object} */ (myObject.property).someProp = true; -/** @type {Object} */ (myObject.property).someProp = true; -+(/** @type {Object} */ myObject.property).someProp = true; +-/** @type {Object} */ (myObject.property).someProp = true; ++/** @type {Object} */ myObject.property.someProp = true; ++/** @type {Object} */ myObject.property.someProp = true; - const prop = /** @type {Object} */ (myObject.property).someProp; +-const prop = /** @type {Object} */ (myObject.property).someProp; ++const prop = /** @type {Object} */ myObject.property.someProp; -const test = - /** @type (function (*): ?|undefined) */ - (goog.partial(NewThing.onTemplateChange, rationaleField, typeField)); -+const test = /** @type (function (*): ?|undefined) */ (goog.partial( ++const test = /** @type (function (*): ?|undefined) */ goog.partial( + NewThing.onTemplateChange, + rationaleField, + typeField, -+)); ++); -const test = /** @type (function (*): ?|undefined) */ ( - goog.partial(NewThing.onTemplateChange, rationaleField, typeField) --); -+const test = /** @type (function (*): ?|undefined) */ (goog.partial( ++const test = /** @type (function (*): ?|undefined) */ goog.partial( + NewThing.onTemplateChange, + rationaleField, + typeField, -+)); + ); -const model = /** @type {?{getIndex: Function}} */ (model); +const model = /** @type {?{getIndex: Function}} */ model; @@ -67,22 +68,22 @@ const test = /** @type (function (*): ?|undefined) */ (foo); # Output ```js -/** @type {Object} */ (myObject.property).someProp = true; -(/** @type {Object} */ myObject.property).someProp = true; +/** @type {Object} */ myObject.property.someProp = true; +/** @type {Object} */ myObject.property.someProp = true; -const prop = /** @type {Object} */ (myObject.property).someProp; +const prop = /** @type {Object} */ myObject.property.someProp; -const test = /** @type (function (*): ?|undefined) */ (goog.partial( +const test = /** @type (function (*): ?|undefined) */ goog.partial( NewThing.onTemplateChange, rationaleField, typeField, -)); +); -const test = /** @type (function (*): ?|undefined) */ (goog.partial( +const test = /** @type (function (*): ?|undefined) */ goog.partial( NewThing.onTemplateChange, rationaleField, typeField, -)); +); const model = /** @type {?{getIndex: Function}} */ model; diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/member.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/member.js.snap index 99f51a553bf..685fe607292 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/member.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/member.js.snap @@ -16,13 +16,13 @@ foo = (/** @type {!Baz} */ (baz).bar); +++ Rome @@ -1 +1 @@ -foo = /** @type {!Baz} */ (baz).bar; -+foo = (/** @type {!Baz} */ baz.bar); ++foo = /** @type {!Baz} */ baz.bar; ``` # Output ```js -foo = (/** @type {!Baz} */ baz.bar); +foo = /** @type {!Baz} */ baz.bar; ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/nested.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/nested.js.snap index a3625154874..4ec2cee6b97 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/nested.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/nested.js.snap @@ -23,7 +23,7 @@ const BarImpl = /** @type {BarConstructor} */ ( +++ Rome @@ -1,10 +1,7 @@ -foo = /** @type {!Foo} */ (/** @type {!Baz} */ (baz).bar); -+foo = /** @type {!Foo} */ (/** @type {!Baz} */ baz.bar); ++foo = /** @type {!Foo} */ /** @type {!Baz} */ baz.bar; -const BarImpl = /** @type {BarConstructor} */ ( +const BarImpl = /** @type {BarConstructor} */ @@ -42,7 +42,7 @@ const BarImpl = /** @type {BarConstructor} */ ( # Output ```js -foo = /** @type {!Foo} */ (/** @type {!Baz} */ baz.bar); +foo = /** @type {!Foo} */ /** @type {!Baz} */ baz.bar; const BarImpl = /** @type {BarConstructor} */ /** @type {unknown} */ diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/styled-components.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/styled-components.js.snap index 95bab75730e..93a6b1a6a44 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/styled-components.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/styled-components.js.snap @@ -26,8 +26,9 @@ top: ${p => p.overlap === 'next' && 0}; @@ -1,10 +1,10 @@ const OverlapWrapper = /** @type {import('styled-components').ThemedStyledFunction<'div',null,{overlap: boolean}>} */ - (styled.div)` +- (styled.div)` - position: relative; ++ styled.div` +position:relative; > { - position: absolute; @@ -47,7 +48,7 @@ top: ${p => p.overlap === 'next' && 0}; ```js const OverlapWrapper = /** @type {import('styled-components').ThemedStyledFunction<'div',null,{overlap: boolean}>} */ - (styled.div)` + styled.div` position:relative; > { position: absolute; diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/issues.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/issues.js.snap index 45941bc312f..17e9edc759d 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/issues.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments/issues.js.snap @@ -84,27 +84,7 @@ foo({} ```diff --- Prettier +++ Rome -@@ -9,13 +9,12 @@ - }); - - // Missing one level of indentation because of the comment --const rootEpic = (actions, store) => -- combineEpics(...epics)(actions, store) -- // Log errors and continue. -- .catch((err, stream) => { -- getLogger().error(err); -- return stream; -- }); -+const rootEpic = (actions, store) => (combineEpics(...epics)(actions, store) -+ // Log errors and continue. -+ .catch((err, stream) => { -+ getLogger().error(err); -+ return stream; -+ })); - - // optional trailing comma gets moved all the way to the beginning - const regex = new RegExp( -@@ -64,5 +63,5 @@ +@@ -64,5 +64,5 @@ // The closing paren is printed on the same line as the comment foo( {}, @@ -127,12 +107,13 @@ throw new ProcessSystemError({ }); // Missing one level of indentation because of the comment -const rootEpic = (actions, store) => (combineEpics(...epics)(actions, store) - // Log errors and continue. - .catch((err, stream) => { - getLogger().error(err); - return stream; - })); +const rootEpic = (actions, store) => + combineEpics(...epics)(actions, store) + // Log errors and continue. + .catch((err, stream) => { + getLogger().error(err); + return stream; + }); // optional trailing comma gets moved all the way to the beginning const regex = new RegExp( @@ -188,6 +169,6 @@ foo( # Lines exceeding max width of 80 characters ``` - 30: import path from "path"; // eslint-disable-line nuclide-internal/prefer-nuclide-uri + 31: import path from "path"; // eslint-disable-line nuclide-internal/prefer-nuclide-uri ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/return-statement.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/return-statement.js.snap index dfd0054cb02..107525ce77e 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/return-statement.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments/return-statement.js.snap @@ -134,20 +134,7 @@ function inlineComment() { ```diff --- Prettier +++ Rome -@@ -67,8 +67,10 @@ - - function memberInside() { - return ( -- // Reason for a.b -- a.b.c -+ ( -+ // Reason for a.b -+ a.b -+ ).c - ); - } - -@@ -80,10 +82,11 @@ +@@ -80,10 +80,12 @@ } function memberInAndOutWithCalls() { @@ -156,14 +143,15 @@ function inlineComment() { - () - .c.d(); + return ( -+ ( + // Reason for a -+ aFunction.b()).c.d() ++ aFunction ++ .b() ++ .c.d() + ); } function excessiveEverything() { -@@ -103,8 +106,8 @@ +@@ -103,8 +105,8 @@ function sequenceExpressionInside() { return ( @@ -174,7 +162,7 @@ function inlineComment() { ); } -@@ -116,5 +119,7 @@ +@@ -116,5 +118,7 @@ } function inlineComment() { @@ -257,10 +245,8 @@ function call() { function memberInside() { return ( - ( - // Reason for a.b - a.b - ).c + // Reason for a.b + a.b.c ); } @@ -273,9 +259,10 @@ function memberOutside() { function memberInAndOutWithCalls() { return ( - ( // Reason for a - aFunction.b()).c.d() + aFunction + .b() + .c.d() ); } diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/export-default/class_instance.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/export-default/class_instance.js.snap index 140a2087d5a..41e86369725 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/export-default/class_instance.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/export-default/class_instance.js.snap @@ -16,13 +16,13 @@ export default (class {}.getInstance()); +++ Rome @@ -1 +1 @@ -export default (class {}.getInstance()); -+export default ((class {}).getInstance()); ++export default (class {}).getInstance(); ``` # Output ```js -export default ((class {}).getInstance()); +export default (class {}).getInstance(); ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/function/issue-10277.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/function/issue-10277.js.snap deleted file mode 100644 index b349d78a952..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/function/issue-10277.js.snap +++ /dev/null @@ -1,40 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -(fold => fold)(fmap => algebra => function doFold(v) {return algebra(fmap(doFold)(v))}) -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,6 +1,5 @@ - ((fold) => fold)( -- (fmap) => (algebra) => -- function doFold(v) { -- return algebra(fmap(doFold)(v)); -- }, -+ (fmap) => (algebra) => function doFold(v) { -+ return algebra(fmap(doFold)(v)); -+ }, - ); -``` - -# Output - -```js -((fold) => fold)( - (fmap) => (algebra) => function doFold(v) { - return algebra(fmap(doFold)(v)); - }, -); -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/method-chain/logical.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/method-chain/logical.js.snap index 9634bd8b9a6..42557a0f90d 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/method-chain/logical.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/method-chain/logical.js.snap @@ -30,9 +30,14 @@ const someLongVariableName = (idx( ```diff --- Prettier +++ Rome -@@ -2,24 +2,24 @@ - idx(this.props, (props) => props.someLongPropertyName) || [] - ).map((edge) => edge.node); +@@ -1,25 +1,24 @@ +-const someLongVariableName = ( +- idx(this.props, (props) => props.someLongPropertyName) || [] +-).map((edge) => edge.node); ++const someLongVariableName = (idx( ++ this.props, ++ (props) => props.someLongPropertyName, ++) || []).map((edge) => edge.node); -(veryLongVeryLongVeryLong || e).map((tickets) => - TicketRecord.createFromSomeLongString(), @@ -47,34 +52,36 @@ const someLongVariableName = (idx( + (tickets) => TicketRecord.createFromSomeLongString(), +).filter((obj) => !!obj); - ( - veryLongVeryLongVeryLong || -- anotherVeryLongVeryLongVeryLong || +-( +- veryLongVeryLongVeryLong || ++(veryLongVeryLongVeryLong || + anotherVeryLongVeryLongVeryLong || - veryVeryVeryLongError -+ anotherVeryLongVeryLongVeryLong || -+ veryVeryVeryLongError - ).map((tickets) => TicketRecord.createFromSomeLongString()); +-).map((tickets) => TicketRecord.createFromSomeLongString()); ++ veryVeryVeryLongError).map( ++ (tickets) => TicketRecord.createFromSomeLongString(), ++); - ( - veryLongVeryLongVeryLong || -- anotherVeryLongVeryLongVeryLong || +-( +- veryLongVeryLongVeryLong || ++(veryLongVeryLongVeryLong || + anotherVeryLongVeryLongVeryLong || - veryVeryVeryLongError -) - .map((tickets) => TicketRecord.createFromSomeLongString()) - .filter((obj) => !!obj); -+ anotherVeryLongVeryLongVeryLong || -+ veryVeryVeryLongError -+).map((tickets) => TicketRecord.createFromSomeLongString()).filter( -+ (obj) => !!obj, -+); ++ veryVeryVeryLongError).map( ++ (tickets) => TicketRecord.createFromSomeLongString(), ++).filter((obj) => !!obj); ``` # Output ```js -const someLongVariableName = ( - idx(this.props, (props) => props.someLongPropertyName) || [] -).map((edge) => edge.node); +const someLongVariableName = (idx( + this.props, + (props) => props.someLongPropertyName, +) || []).map((edge) => edge.node); (veryLongVeryLongVeryLong || e).map( (tickets) => TicketRecord.createFromSomeLongString(), @@ -84,19 +91,17 @@ const someLongVariableName = ( (tickets) => TicketRecord.createFromSomeLongString(), ).filter((obj) => !!obj); -( - veryLongVeryLongVeryLong || - anotherVeryLongVeryLongVeryLong || - veryVeryVeryLongError -).map((tickets) => TicketRecord.createFromSomeLongString()); - -( - veryLongVeryLongVeryLong || - anotherVeryLongVeryLongVeryLong || - veryVeryVeryLongError -).map((tickets) => TicketRecord.createFromSomeLongString()).filter( - (obj) => !!obj, +(veryLongVeryLongVeryLong || + anotherVeryLongVeryLongVeryLong || + veryVeryVeryLongError).map( + (tickets) => TicketRecord.createFromSomeLongString(), ); + +(veryLongVeryLongVeryLong || + anotherVeryLongVeryLongVeryLong || + veryVeryVeryLongError).map( + (tickets) => TicketRecord.createFromSomeLongString(), +).filter((obj) => !!obj); ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/method-chain/test.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/method-chain/test.js.snap index 65945e8b3e8..f62128a3728 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/method-chain/test.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/method-chain/test.js.snap @@ -28,7 +28,7 @@ method().then(x => x) -({}.a().b()); -({}.a().b()); -+(({}).a().b()); ++({}).a().b(); +({}).a().b(); ``` @@ -38,7 +38,7 @@ method().then(x => x) method() .then((x) => x)["abc"]((x) => x)[abc]((x) => x); -(({}).a().b()); +({}).a().b(); ({}).a().b(); ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/new-expression/new_expression.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/new-expression/new_expression.js.snap index 3239dad828f..181be34c49b 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/new-expression/new_expression.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/new-expression/new_expression.js.snap @@ -26,7 +26,7 @@ new (a``()); new (memoize.Cache || MapCache)(); new (typeof this == "function" ? this : Dict())(); -new (createObj().prop)(a()); -+new (createObj()).prop(a()); ++new createObj().prop(a()); new (x()``.y)(); new e[f().x].y(); new e[f()].y(); @@ -37,7 +37,7 @@ new (a``()); ```js new (memoize.Cache || MapCache)(); new (typeof this == "function" ? this : Dict())(); -new (createObj()).prop(a()); +new createObj().prop(a()); new (x()``.y)(); new e[f().x].y(); new e[f()].y(); diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/no-semi/no-semi.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/no-semi/no-semi.js.snap index bd412381989..1f795b58ff0 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/no-semi/no-semi.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/no-semi/no-semi.js.snap @@ -105,12 +105,9 @@ aReallyLongLine012345678901234567890123456789012345678901234567890123456789 * x; ; x; -@@ -85,7 +85,8 @@ - x; - ++(a || b).c; +@@ -87,5 +87,6 @@ --while (false) (function () {})(); -+while (false) ((function () {})()); + while (false) (function () {})(); -aReallyLongLine012345678901234567890123456789012345678901234567890123456789 * - (b + c); @@ -209,7 +206,7 @@ x; x; ++(a || b).c; -while (false) ((function () {})()); +while (false) (function () {})(); aReallyLongLine012345678901234567890123456789012345678901234567890123456789 * ( b + c diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/optional-chaining/chaining.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/optional-chaining/chaining.js.snap index 3857715d81d..16230433fa0 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/optional-chaining/chaining.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/optional-chaining/chaining.js.snap @@ -96,12 +96,23 @@ new (foo?.())(); ```diff --- Prettier +++ Rome -@@ -19,11 +19,11 @@ - (a?.b).c(); - (a?.b[c]).c(); +@@ -10,20 +10,20 @@ + a?.b.c(++x).d; + a?.b[3].c?.(x).d; + a?.b.c; +-(a?.b).c; ++a?.b.c; + a?.b?.c; + delete a?.b; --a?.b?.c.d?.e; -+(a?.b)?.c.d?.e; + a?.b[3].c?.(x).d.e?.f[3].g?.(y).h; + +-(a?.b).c(); +-(a?.b[c]).c(); ++a?.b.c(); ++a?.b[c].c(); + + a?.b?.c.d?.e; (a ? b : c)?.d; (list || list2)?.length; @@ -110,38 +121,44 @@ new (foo?.())(); async function HelloWorld() { var x = (await foo.bar.blah)?.hi; -@@ -36,17 +36,17 @@ - a[b?.c]?.d(); +@@ -37,18 +37,18 @@ a?.[b?.c]?.d(); --one?.fn(); -+(one?.fn()); - (one?.two).fn(); - (one?.two)(); - (one?.two())(); --one.two?.fn(); -+(one.two?.fn()); - (one.two?.three).fn(); --one.two?.three?.fn(); -+(one.two?.three?.fn()); + one?.fn(); +-(one?.two).fn(); +-(one?.two)(); +-(one?.two())(); ++one?.two.fn(); ++one?.two(); ++one?.two()(); + one.two?.fn(); +-(one.two?.three).fn(); ++one.two?.three.fn(); + one.two?.three?.fn(); + + one?.(); +-(one?.())(); ++one?.()(); + one?.()?.(); --one?.(); -+(one?.()); - (one?.())(); --one?.()?.(); -+(one?.())?.(); +-(one?.()).two; ++one?.().two; - (one?.()).two; + a?.[b ? c : d]; -@@ -62,25 +62,25 @@ - (a?.(x)).x; - ( +@@ -59,28 +59,26 @@ + (function () {})?.(); + (() => f)?.(); + (() => f)?.x; +-(a?.(x)).x; +-( ++a?.(x).x; ++(aaaaaaaaaaaaaaaaaaaaaaaa && aaaaaaaaaaaaaaaaaaaaaaaa && - aaaaaaaaaaaaaaaaaaaaaaaa && - aaaaaaaaaaaaaaaaaaaaaaaa -+ aaaaaaaaaaaaaaaaaaaaaaaa && -+ aaaaaaaaaaaaaaaaaaaaaaaa - )?.(); +-)?.(); ++ aaaaaaaaaaaaaaaaaaaaaaaa)?.(); -let f = () => ({}?.()); -let g = () => ({}?.b); @@ -158,8 +175,8 @@ new (foo?.())(); -(x) => ({}?.b.b); -({}?.a().b()); -({ a: 1 }?.entries()); -+let f = () => (({})?.()); -+let g = () => (({})?.b); ++let f = () => ({})?.(); ++let g = () => ({})?.b; +a = () => (({})?.() && a); +a = () => (({})?.()() && a); +a = () => (({})?.().b && a); @@ -167,12 +184,12 @@ new (foo?.())(); +a = () => (({})?.b() && a); +(a) => (({})?.()?.b && 0); +(a) => (({})?.b?.b && 0); -+(x) => (({})?.()()); -+(x) => (({})?.().b); -+(x) => (({})?.b()); -+(x) => (({})?.b.b); -+(({})?.a().b()); -+(({ a: 1 })?.entries()); ++(x) => ({})?.()(); ++(x) => ({})?.().b; ++(x) => ({})?.b(); ++(x) => ({})?.b.b; ++({})?.a().b(); ++({ a: 1 })?.entries(); new (foo?.bar)(); new (foo?.bar())(); @@ -193,16 +210,16 @@ a?.[++x]; a?.b.c(++x).d; a?.b[3].c?.(x).d; a?.b.c; -(a?.b).c; +a?.b.c; a?.b?.c; delete a?.b; a?.b[3].c?.(x).d.e?.f[3].g?.(y).h; -(a?.b).c(); -(a?.b[c]).c(); +a?.b.c(); +a?.b[c].c(); -(a?.b)?.c.d?.e; +a?.b?.c.d?.e; (a ? b : c)?.d; (list || list2)?.length; @@ -219,19 +236,19 @@ a?.[b?.c].d(); a[b?.c]?.d(); a?.[b?.c]?.d(); -(one?.fn()); -(one?.two).fn(); -(one?.two)(); -(one?.two())(); -(one.two?.fn()); -(one.two?.three).fn(); -(one.two?.three?.fn()); +one?.fn(); +one?.two.fn(); +one?.two(); +one?.two()(); +one.two?.fn(); +one.two?.three.fn(); +one.two?.three?.fn(); -(one?.()); -(one?.())(); -(one?.())?.(); +one?.(); +one?.()(); +one?.()?.(); -(one?.()).two; +one?.().two; a?.[b ? c : d]; @@ -242,15 +259,13 @@ a?.[b ? c : d]; (function () {})?.(); (() => f)?.(); (() => f)?.x; -(a?.(x)).x; -( +a?.(x).x; +(aaaaaaaaaaaaaaaaaaaaaaaa && aaaaaaaaaaaaaaaaaaaaaaaa && - aaaaaaaaaaaaaaaaaaaaaaaa && - aaaaaaaaaaaaaaaaaaaaaaaa -)?.(); + aaaaaaaaaaaaaaaaaaaaaaaa)?.(); -let f = () => (({})?.()); -let g = () => (({})?.b); +let f = () => ({})?.(); +let g = () => ({})?.b; a = () => (({})?.() && a); a = () => (({})?.()() && a); a = () => (({})?.().b && a); @@ -258,12 +273,12 @@ a = () => (({})?.b && a); a = () => (({})?.b() && a); (a) => (({})?.()?.b && 0); (a) => (({})?.b?.b && 0); -(x) => (({})?.()()); -(x) => (({})?.().b); -(x) => (({})?.b()); -(x) => (({})?.b.b); -(({})?.a().b()); -(({ a: 1 })?.entries()); +(x) => ({})?.()(); +(x) => ({})?.().b; +(x) => ({})?.b(); +(x) => ({})?.b.b; +({})?.a().b(); +({ a: 1 })?.entries(); new (foo?.bar)(); new (foo?.bar())(); diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/preserve-line/member-chain.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/preserve-line/member-chain.js.snap index cc0eee76ca1..da19c4bf882 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/preserve-line/member-chain.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/preserve-line/member-chain.js.snap @@ -72,7 +72,7 @@ const sel = this.connections ```diff --- Prettier +++ Rome -@@ -1,59 +1,41 @@ +@@ -1,59 +1,39 @@ fooBar .doSomething("Hello World") .doAnotherThing("Foo", { foo: bar }) @@ -115,20 +115,19 @@ const sel = this.connections -helloWorld - - .text() -- -- .then((t) => t); +helloWorld.text().then((t) => t); - ( - veryLongVeryLongVeryLong || -- anotherVeryLongVeryLongVeryLong || +- .then((t) => t); +- +-( +- veryLongVeryLongVeryLong || ++(veryLongVeryLongVeryLong || + anotherVeryLongVeryLongVeryLong || - veryVeryVeryLongError -) -+ anotherVeryLongVeryLongVeryLong || -+ veryVeryVeryLongError -+).map((tickets) => TicketRecord.createFromSomeLongString()).filter( -+ (obj) => !!obj, -+); ++ veryVeryVeryLongError).map( ++ (tickets) => TicketRecord.createFromSomeLongString(), ++).filter((obj) => !!obj); - .map((tickets) => TicketRecord.createFromSomeLongString()) - @@ -176,13 +175,11 @@ foo.bar.baz helloWorld.text().then((t) => t); -( - veryLongVeryLongVeryLong || - anotherVeryLongVeryLongVeryLong || - veryVeryVeryLongError -).map((tickets) => TicketRecord.createFromSomeLongString()).filter( - (obj) => !!obj, -); +(veryLongVeryLongVeryLong || + anotherVeryLongVeryLongVeryLong || + veryVeryVeryLongError).map( + (tickets) => TicketRecord.createFromSomeLongString(), +).filter((obj) => !!obj); const sel = this.connections.concat( this.activities.concat(this.operators), diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/sequence-break/break.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/sequence-break/break.js.snap index 4dc5e0747da..6fce3da0ee0 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/sequence-break/break.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/sequence-break/break.js.snap @@ -26,34 +26,8 @@ for (aLongIdentifierName = 0, aLongIdentifierName = 0, aLongIdentifierName = 0, ```diff --- Prettier +++ Rome -@@ -1,8 +1,8 @@ --const f = (argument1, argument2, argument3) => ( -- doSomethingWithArgument(argument1), -- doSomethingWithArgument(argument2), -- argument1 --); -+const f = (argument1, argument2, argument3) => (doSomethingWithArgument( -+ argument1, -+), -+doSomethingWithArgument(argument2), -+argument1); - (function () { - return ( - aLongIdentifierName, -@@ -16,17 +16,15 @@ - aLongIdentifierName, - aLongIdentifierName; - a.then( -- () => ( -- aLongIdentifierName, -- aLongIdentifierName, -- aLongIdentifierName, -- aLongIdentifierName -- ), -+ () => (aLongIdentifierName, -+ aLongIdentifierName, -+ aLongIdentifierName, -+ aLongIdentifierName), +@@ -24,9 +24,9 @@ + ), ); for ( - aLongIdentifierName = 0, @@ -70,11 +44,11 @@ for (aLongIdentifierName = 0, aLongIdentifierName = 0, aLongIdentifierName = 0, # Output ```js -const f = (argument1, argument2, argument3) => (doSomethingWithArgument( - argument1, -), -doSomethingWithArgument(argument2), -argument1); +const f = (argument1, argument2, argument3) => ( + doSomethingWithArgument(argument1), + doSomethingWithArgument(argument2), + argument1 +); (function () { return ( aLongIdentifierName, @@ -88,10 +62,12 @@ aLongIdentifierName, aLongIdentifierName, aLongIdentifierName; a.then( - () => (aLongIdentifierName, - aLongIdentifierName, - aLongIdentifierName, - aLongIdentifierName), + () => ( + aLongIdentifierName, + aLongIdentifierName, + aLongIdentifierName, + aLongIdentifierName + ), ); for ( (aLongIdentifierName = 0), diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/binary.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/binary.js.snap index 7f5ea77daef..0ed2b5c73fd 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/binary.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/binary.js.snap @@ -25,7 +25,7 @@ room = room.map((row, rowIndex) => ( ```diff --- Prettier +++ Rome -@@ -1,17 +1,20 @@ +@@ -1,17 +1,21 @@ const funnelSnapshotCard = - (report === MY_OVERVIEW && !ReportGK.xar_metrics_active_capitol_v2) || - (report === COMPANY_OVERVIEW && @@ -48,17 +48,18 @@ room = room.map((row, rowIndex) => ( - : 0, - ), +room = room.map( -+ (row, rowIndex) => (row.map( -+ (col, colIndex) => -+ ( -+ rowIndex === 0 || -+ colIndex === 0 || -+ rowIndex === height || -+ colIndex === width -+ ) -+ ? 1 -+ : 0, -+ )), ++ (row, rowIndex) => ++ row.map( ++ (col, colIndex) => ++ ( ++ rowIndex === 0 || ++ colIndex === 0 || ++ rowIndex === height || ++ colIndex === width ++ ) ++ ? 1 ++ : 0, ++ ), ); ``` @@ -73,17 +74,18 @@ const funnelSnapshotCard = : null; room = room.map( - (row, rowIndex) => (row.map( - (col, colIndex) => - ( - rowIndex === 0 || - colIndex === 0 || - rowIndex === height || - colIndex === width - ) - ? 1 - : 0, - )), + (row, rowIndex) => + row.map( + (col, colIndex) => + ( + rowIndex === 0 || + colIndex === 0 || + rowIndex === height || + colIndex === width + ) + ? 1 + : 0, + ), ); ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/indent-after-paren.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/indent-after-paren.js.snap index cd9864c8f41..872c94889df 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/indent-after-paren.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/indent-after-paren.js.snap @@ -305,75 +305,55 @@ fn?.[ ```diff --- Prettier +++ Rome -@@ -210,54 +210,52 @@ +@@ -210,54 +210,44 @@ )(Fooooooooooo.Fooooooooooo); bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + - (glimseGlyphsHazardNoopsTieTie === 0 -- ? averredBathersBoxroomBuggyNurl -- : anodyneCondosMalateOverateRetinol)[AnnularCooeedSplicesWalksWayWay]; -+ askTrovenaBeenaDependsRowans + ( -+ (glimseGlyphsHazardNoopsTieTie === 0 -+ ? averredBathersBoxroomBuggyNurl -+ : anodyneCondosMalateOverateRetinol)[AnnularCooeedSplicesWalksWayWay] -+ ); ++ askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 + ? averredBathersBoxroomBuggyNurl + : anodyneCondosMalateOverateRetinol)[AnnularCooeedSplicesWalksWayWay]; bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + - (glimseGlyphsHazardNoopsTieTie === 0 && - kochabCooieGameOnOboleUnweave === Math.PI -- ? averredBathersBoxroomBuggyNurl -- : anodyneCondosMalateOverateRetinol)[AnnularCooeedSplicesWalksWayWay]; -+ askTrovenaBeenaDependsRowans + ( -+ (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI -+ ? averredBathersBoxroomBuggyNurl -+ : anodyneCondosMalateOverateRetinol)[AnnularCooeedSplicesWalksWayWay] -+ ); ++ askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI + ? averredBathersBoxroomBuggyNurl + : anodyneCondosMalateOverateRetinol)[AnnularCooeedSplicesWalksWayWay]; bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + - (glimseGlyphsHazardNoopsTieTie === 0 -- ? averredBathersBoxroomBuggyNurl -- : anodyneCondosMalateOverateRetinol -- ).Fooooooooooo.Fooooooooooo; -+ askTrovenaBeenaDependsRowans + ( -+ (glimseGlyphsHazardNoopsTieTie === 0 -+ ? averredBathersBoxroomBuggyNurl -+ : anodyneCondosMalateOverateRetinol -+ ).Fooooooooooo.Fooooooooooo -+ ); ++ askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 + ? averredBathersBoxroomBuggyNurl + : anodyneCondosMalateOverateRetinol + ).Fooooooooooo.Fooooooooooo; bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + - (glimseGlyphsHazardNoopsTieTie === 0 && - kochabCooieGameOnOboleUnweave === Math.PI -- ? averredBathersBoxroomBuggyNurl -- : anodyneCondosMalateOverateRetinol -- ).Fooooooooooo.Fooooooooooo; -+ askTrovenaBeenaDependsRowans + ( -+ (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI -+ ? averredBathersBoxroomBuggyNurl -+ : anodyneCondosMalateOverateRetinol -+ ).Fooooooooooo.Fooooooooooo -+ ); ++ askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI + ? averredBathersBoxroomBuggyNurl + : anodyneCondosMalateOverateRetinol + ).Fooooooooooo.Fooooooooooo; bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + - (glimseGlyphsHazardNoopsTieTie === 0 -+ askTrovenaBeenaDependsRowans + ((glimseGlyphsHazardNoopsTieTie === 0 ++ askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 ? averredBathersBoxroomBuggyNurl -- : anodyneCondosMalateOverateRetinol)(Fooooooooooo.Fooooooooooo); -+ : anodyneCondosMalateOverateRetinol)(Fooooooooooo.Fooooooooooo)); + : anodyneCondosMalateOverateRetinol)(Fooooooooooo.Fooooooooooo); bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + - (glimseGlyphsHazardNoopsTieTie === 0 && - kochabCooieGameOnOboleUnweave === Math.PI -+ askTrovenaBeenaDependsRowans + ((glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI ++ askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI ? averredBathersBoxroomBuggyNurl -- : anodyneCondosMalateOverateRetinol)(Fooooooooooo.Fooooooooooo); -+ : anodyneCondosMalateOverateRetinol)(Fooooooooooo.Fooooooooooo)); + : anodyneCondosMalateOverateRetinol)(Fooooooooooo.Fooooooooooo); bifornCringerMoshedPerplexSawder = ( - glimseGlyphsHazardNoopsTieTie === 0 && @@ -390,7 +370,7 @@ fn?.[ foo = ( coooooooooooooooooooooooooooooooooooooooooooooooooooond -@@ -280,8 +278,9 @@ +@@ -280,8 +270,9 @@ const decorated = (arg, ignoreRequestError) => { return ( @@ -619,44 +599,36 @@ foo36 = new ( )(Fooooooooooo.Fooooooooooo); bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + ( - (glimseGlyphsHazardNoopsTieTie === 0 - ? averredBathersBoxroomBuggyNurl - : anodyneCondosMalateOverateRetinol)[AnnularCooeedSplicesWalksWayWay] - ); + askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 + ? averredBathersBoxroomBuggyNurl + : anodyneCondosMalateOverateRetinol)[AnnularCooeedSplicesWalksWayWay]; bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + ( - (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI - ? averredBathersBoxroomBuggyNurl - : anodyneCondosMalateOverateRetinol)[AnnularCooeedSplicesWalksWayWay] - ); + askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI + ? averredBathersBoxroomBuggyNurl + : anodyneCondosMalateOverateRetinol)[AnnularCooeedSplicesWalksWayWay]; bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + ( - (glimseGlyphsHazardNoopsTieTie === 0 - ? averredBathersBoxroomBuggyNurl - : anodyneCondosMalateOverateRetinol - ).Fooooooooooo.Fooooooooooo - ); + askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 + ? averredBathersBoxroomBuggyNurl + : anodyneCondosMalateOverateRetinol + ).Fooooooooooo.Fooooooooooo; bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + ( - (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI - ? averredBathersBoxroomBuggyNurl - : anodyneCondosMalateOverateRetinol - ).Fooooooooooo.Fooooooooooo - ); + askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI + ? averredBathersBoxroomBuggyNurl + : anodyneCondosMalateOverateRetinol + ).Fooooooooooo.Fooooooooooo; bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + ((glimseGlyphsHazardNoopsTieTie === 0 + askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 ? averredBathersBoxroomBuggyNurl - : anodyneCondosMalateOverateRetinol)(Fooooooooooo.Fooooooooooo)); + : anodyneCondosMalateOverateRetinol)(Fooooooooooo.Fooooooooooo); bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + ((glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI + askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI ? averredBathersBoxroomBuggyNurl - : anodyneCondosMalateOverateRetinol)(Fooooooooooo.Fooooooooooo)); + : anodyneCondosMalateOverateRetinol)(Fooooooooooo.Fooooooooooo); bifornCringerMoshedPerplexSawder = ( glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI @@ -757,9 +729,9 @@ fn?.[ # Lines exceeding max width of 80 characters ``` - 221: (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI - 236: (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI - 248: askTrovenaBeenaDependsRowans + ((glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI - 253: glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI + 218: askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI + 229: askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI + 240: askTrovenaBeenaDependsRowans + (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI + 245: glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/nested.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/nested.js.snap index 3c2c3ea2e17..b8e0f390d03 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/nested.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/nested.js.snap @@ -166,29 +166,6 @@ a ? "-record" : medals[0].unique ? "-unique" -@@ -74,16 +72,16 @@ - } - > - {medals[0].record -- ? i18n("Record") -+ ? (i18n("Record")) - : medals[0].unique -- ? i18n("Unique") -+ ? (i18n("Unique")) - : medals[0].type === 0 -- ? i18n("Silver") -+ ? (i18n("Silver")) - : medals[0].type === 1 -- ? i18n("Gold") -+ ? (i18n("Gold")) - : medals[0].type === 2 -- ? i18n("Platinum") -- : i18n("Theme")} -+ ? (i18n("Platinum")) -+ : (i18n("Theme"))} - - ); - ``` # Output @@ -268,16 +245,16 @@ const foo = ( } > {medals[0].record - ? (i18n("Record")) + ? i18n("Record") : medals[0].unique - ? (i18n("Unique")) + ? i18n("Unique") : medals[0].type === 0 - ? (i18n("Silver")) + ? i18n("Silver") : medals[0].type === 1 - ? (i18n("Gold")) + ? i18n("Gold") : medals[0].type === 2 - ? (i18n("Platinum")) - : (i18n("Theme"))} + ? i18n("Platinum") + : i18n("Theme")} ); diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/unary-expression/comments.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/unary-expression/comments.js.snap index de97e68df8e..6873b92b399 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/unary-expression/comments.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/unary-expression/comments.js.snap @@ -298,7 +298,7 @@ async function bar2() { ```diff --- Prettier +++ Rome -@@ -1,162 +1,129 @@ +@@ -1,207 +1,162 @@ !x; -!(x /* foo */); -!(/* foo */ x); @@ -552,9 +552,28 @@ async function bar2() { +!(x = y); // foo !x.y; - !(x.y /* foo */); -@@ -174,34 +141,26 @@ - ); +-!(x.y /* foo */); +-!(/* foo */ x.y); +-!( +- /* foo */ +- x.y +-); +-!( +- x.y +- /* foo */ +-); +-!( +- x.y // foo +-); ++!x.y /* foo */; ++! /* foo */ x.y; ++! ++/* foo */ ++x.y; ++!x.y ++/* foo */ ++; ++!x.y; // foo !(x ? y : z); -!((x ? y : z) /* foo */); @@ -581,28 +600,32 @@ async function bar2() { +!(x ? y : z); // foo !x(); - !(x() /* foo */); - !(/* foo */ x()); +-!(x() /* foo */); +-!(/* foo */ x()); -!( - /* foo */ - x() -); - !( +-!( - x() - /* foo */ -+/* foo */ -+x()); -+!(x() -+/* foo */ - ); +-); -!( - x() // foo -); -+!(x()); // foo ++!x() /* foo */; ++! /* foo */ x(); ++! ++/* foo */ ++x(); ++!x() ++/* foo */ ++; ++!x(); // foo !new x(); !(new x() /* foo */); -@@ -219,65 +178,51 @@ +@@ -219,65 +174,49 @@ ); !(x, y); @@ -616,6 +639,9 @@ async function bar2() { - (x, y) - /* foo */ -); +-!( +- x.y // foo +-); +!(x, y) /* foo */; +! /* foo */ (x, y); +! @@ -624,9 +650,7 @@ async function bar2() { +!(x, y) +/* foo */ +; - !( - x.y // foo - ); ++!x.y; // foo !(() => 3); -!((() => 3) /* foo */); @@ -836,19 +860,15 @@ function () { !(x = y); // foo !x.y; -!(x.y /* foo */); -!(/* foo */ x.y); -!( - /* foo */ - x.y -); -!( - x.y - /* foo */ -); -!( - x.y // foo -); +!x.y /* foo */; +! /* foo */ x.y; +! +/* foo */ +x.y; +!x.y +/* foo */ +; +!x.y; // foo !(x ? y : z); !(x ? y : z) /* foo */; @@ -862,15 +882,15 @@ function () { !(x ? y : z); // foo !x(); -!(x() /* foo */); -!(/* foo */ x()); -!( +!x() /* foo */; +! /* foo */ x(); +! /* foo */ -x()); -!(x() +x(); +!x() /* foo */ -); -!(x()); // foo +; +!x(); // foo !new x(); !(new x() /* foo */); @@ -896,9 +916,7 @@ x()); !(x, y) /* foo */ ; -!( - x.y // foo -); +!x.y; // foo !(() => 3); !(() => 3) /* foo */; diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/arrow/arrow_regression.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/arrow/arrow_regression.ts.snap index da1491833c8..936667d8918 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/arrow/arrow_regression.ts.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/arrow/arrow_regression.ts.snap @@ -38,7 +38,7 @@ app.get("/", (req, res): void => { - () => {}, - () => {}, - ); -+const foo = (x: string): void => (bar(x, () => {}, () => {})); ++const foo = (x: string): void => bar(x, () => {}, () => {}); app.get("/", (req, res): void => { res.send("Hello world"); @@ -51,7 +51,7 @@ const bar = (...varargs: any[]) => { console.log(varargs); }; -const foo = (x: string): void => (bar(x, () => {}, () => {})); +const foo = (x: string): void => bar(x, () => {}, () => {}); app.get("/", (req, res): void => { res.send("Hello world"); diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/as/nested-await-and-as.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/as/nested-await-and-as.ts.snap index 93bcb67819a..0663061b3e9 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/as/nested-await-and-as.ts.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/as/nested-await-and-as.ts.snap @@ -1,8 +1,5 @@ --- source: crates/rome_js_formatter/tests/prettier_tests.rs -assertion_line: 271 -info: - test_file: typescript/as/nested-await-and-as.ts --- # Input @@ -23,7 +20,7 @@ const getAccountCount = async () => ```diff --- Prettier +++ Rome -@@ -1,8 +1,6 @@ +@@ -1,8 +1,4 @@ const getAccountCount = async () => - ( - await ( @@ -32,22 +29,18 @@ const getAccountCount = async () => - ).findItem("My bookmarks")) as TreeItem - ).getChildren() - ).length; -+ (await ( -+ (await (await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)).findItem( -+ "My bookmarks", -+ )) as TreeItem -+ ).getChildren()).length; ++ (await ((await (await focusOnSection( ++ BOOKMARKED_PROJECTS_SECTION_NAME, ++ )).findItem("My bookmarks")) as TreeItem).getChildren()).length; ``` # Output ```js const getAccountCount = async () => - (await ( - (await (await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)).findItem( - "My bookmarks", - )) as TreeItem - ).getChildren()).length; + (await ((await (await focusOnSection( + BOOKMARKED_PROJECTS_SECTION_NAME, + )).findItem("My bookmarks")) as TreeItem).getChildren()).length; ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/expressions/functionCalls/callWithSpreadES6.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/expressions/functionCalls/callWithSpreadES6.ts.snap deleted file mode 100644 index 9613d187963..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/expressions/functionCalls/callWithSpreadES6.ts.snap +++ /dev/null @@ -1,137 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -// @target: ES6 - -interface X { - foo(x: number, y: number, ...z: string[]); -} - -function foo(x: number, y: number, ...z: string[]) { -} - -var a: string[]; -var z: number[]; -var obj: X; -var xa: X[]; - -foo(1, 2, "abc"); -foo(1, 2, ...a); -foo(1, 2, ...a, "abc"); - -obj.foo(1, 2, "abc"); -obj.foo(1, 2, ...a); -obj.foo(1, 2, ...a, "abc"); - -(obj.foo)(1, 2, "abc"); -(obj.foo)(1, 2, ...a); -(obj.foo)(1, 2, ...a, "abc"); - -xa[1].foo(1, 2, "abc"); -xa[1].foo(1, 2, ...a); -xa[1].foo(1, 2, ...a, "abc"); - -(xa[1].foo)(...[1, 2, "abc"]); - -class C { - constructor(x: number, y: number, ...z: string[]) { - this.foo(x, y); - this.foo(x, y, ...z); - } - foo(x: number, y: number, ...z: string[]) { - } -} - -class D extends C { - constructor() { - super(1, 2); - super(1, 2, ...a); - } - foo() { - super.foo(1, 2); - super.foo(1, 2, ...a); - } -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -19,9 +19,9 @@ - obj.foo(1, 2, ...a); - obj.foo(1, 2, ...a, "abc"); - --obj.foo(1, 2, "abc"); --obj.foo(1, 2, ...a); --obj.foo(1, 2, ...a, "abc"); -+(obj.foo)(1, 2, "abc"); -+(obj.foo)(1, 2, ...a); -+(obj.foo)(1, 2, ...a, "abc"); - - xa[1].foo(1, 2, "abc"); - xa[1].foo(1, 2, ...a); -``` - -# Output - -```js -// @target: ES6 - -interface X { - foo(x: number, y: number, ...z: string[]); -} - -function foo(x: number, y: number, ...z: string[]) {} - -var a: string[]; -var z: number[]; -var obj: X; -var xa: X[]; - -foo(1, 2, "abc"); -foo(1, 2, ...a); -foo(1, 2, ...a, "abc"); - -obj.foo(1, 2, "abc"); -obj.foo(1, 2, ...a); -obj.foo(1, 2, ...a, "abc"); - -(obj.foo)(1, 2, "abc"); -(obj.foo)(1, 2, ...a); -(obj.foo)(1, 2, ...a, "abc"); - -xa[1].foo(1, 2, "abc"); -xa[1].foo(1, 2, ...a); -xa[1].foo(1, 2, ...a, "abc"); - -(xa[1].foo)(...[1, 2, "abc"]); - -class C { - constructor(x: number, y: number, ...z: string[]) { - this.foo(x, y); - this.foo(x, y, ...z); - } - foo(x: number, y: number, ...z: string[]) {} -} - -class D extends C { - constructor() { - super(1, 2); - super(1, 2, ...a); - } - foo() { - super.foo(1, 2); - super.foo(1, 2, ...a); - } -} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/non-null/braces.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/non-null/braces.ts.snap index f68823cfc0b..d5549a38952 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/non-null/braces.ts.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/non-null/braces.ts.snap @@ -28,25 +28,22 @@ class a extends ({}!) {} ```diff --- Prettier +++ Rome -@@ -1,17 +1,18 @@ --const myFunction2 = (key: string): number => -+const myFunction2 = (key: string): number => ( +@@ -2,16 +2,16 @@ ({ a: 42, b: 42, - }[key]!); -+ })[key]! -+); ++ })[key]!; -const myFunction3 = (key) => ({}!.a); -+const myFunction3 = (key) => (({})!.a); ++const myFunction3 = (key) => ({})!.a; const f = ((a) => { log(a); })!; -if (a) ({ a, ...b }.a()!.c()); -+if (a) (({ a, ...b }).a())!.c(); ++if (a) ({ a, ...b }).a()!.c(); -(function () {}!()); +(function () {})!(); @@ -57,20 +54,19 @@ class a extends ({}!) {} # Output ```js -const myFunction2 = (key: string): number => ( +const myFunction2 = (key: string): number => ({ a: 42, b: 42, - })[key]! -); + })[key]!; -const myFunction3 = (key) => (({})!.a); +const myFunction3 = (key) => ({})!.a; const f = ((a) => { log(a); })!; -if (a) (({ a, ...b }).a())!.c(); +if (a) ({ a, ...b }).a()!.c(); (function () {})!(); diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/non-null/optional-chain.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/non-null/optional-chain.ts.snap index 87874dc06db..7bde7ecddb7 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/non-null/optional-chain.ts.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/non-null/optional-chain.ts.snap @@ -31,24 +31,27 @@ a?.().b!.c?.c; ```diff --- Prettier +++ Rome -@@ -4,7 +4,7 @@ +@@ -4,15 +4,15 @@ a!.b?.c; a?.b!?.c; a?.b!.c?.c; -(a?.b)!.c; -+(a?.b!).c; - (a?.b)!.c; +-(a?.b)!.c; ++a?.b!.c; ++a?.b!.c; a?.().b!.c; -@@ -12,7 +12,7 @@ + a?.().b!.c.d; a?.().b.c!.d; a?.().b!?.c; a?.().b!.c?.c; -(a?.().b)!.c; -+(a?.().b!).c; - (a?.().b)!.c; +-(a?.().b)!.c; ++a?.().b!.c; ++a?.().b!.c; - (a?.b)![c?.d!]; +-(a?.b)![c?.d!]; ++a?.b![c?.d!]; ``` # Output @@ -60,18 +63,18 @@ a?.b.c!.d; a!.b?.c; a?.b!?.c; a?.b!.c?.c; -(a?.b!).c; -(a?.b)!.c; +a?.b!.c; +a?.b!.c; a?.().b!.c; a?.().b!.c.d; a?.().b.c!.d; a?.().b!?.c; a?.().b!.c?.c; -(a?.().b!).c; -(a?.().b)!.c; +a?.().b!.c; +a?.().b!.c; -(a?.b)![c?.d!]; +a?.b![c?.d!]; ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/non-null/parens.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/non-null/parens.ts.snap deleted file mode 100644 index 1c5d60e3e7f..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/non-null/parens.ts.snap +++ /dev/null @@ -1,83 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -(a ? b : c) ![tokenKey]; -(a || b) ![tokenKey]; -(void 0)!; - -async function f() { - return (await foo())!; -} - -function* g() { - return (yield * foo())!; -} - -const a = (b()!)(); // parens aren't necessary -const b = c!(); - -// parens are necessary if the expression result is called as a constructor -const c1 = new (d()!)(); -const c2 = new (d()!); -const c3 = new (d()!.e)(); -new (x()``.y!)(); -new (x()``!.y)(); -new (x()!``.y)(); -new (x!()``.y)(); - -xyz.a(b!).a(b!).a(b!) -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -10,7 +10,7 @@ - return (yield* foo())!; - } - --const a = b()!(); // parens aren't necessary -+const a = (b()!)(); // parens aren't necessary - const b = c!(); - - // parens are necessary if the expression result is called as a constructor -``` - -# Output - -```js -(a ? b : c)![tokenKey]; -(a || b)![tokenKey]; -(void 0)!; - -async function f() { - return (await foo())!; -} - -function* g() { - return (yield* foo())!; -} - -const a = (b()!)(); // parens aren't necessary -const b = c!(); - -// parens are necessary if the expression result is called as a constructor -const c1 = new (d()!)(); -const c2 = new (d()!)(); -const c3 = new (d()!.e)(); -new (x()``.y!)(); -new (x()``!.y)(); -new (x()!``.y)(); -new (x!()``.y)(); - -xyz.a(b!).a(b!).a(b!); -``` - - - diff --git a/crates/rome_js_syntax/src/expr_ext.rs b/crates/rome_js_syntax/src/expr_ext.rs index db9af1ad6c3..4fd889f51bc 100644 --- a/crates/rome_js_syntax/src/expr_ext.rs +++ b/crates/rome_js_syntax/src/expr_ext.rs @@ -2,9 +2,10 @@ use crate::numbers::parse_js_number; use crate::{ JsAnyExpression, JsAnyLiteralExpression, JsArrayExpression, JsArrayHole, - JsAssignmentExpression, JsBinaryExpression, JsLiteralMemberName, JsLogicalExpression, - JsNumberLiteralExpression, JsObjectExpression, JsRegexLiteralExpression, - JsStringLiteralExpression, JsSyntaxToken, JsTemplate, JsUnaryExpression, OperatorPrecedence, T, + JsAssignmentExpression, JsBinaryExpression, JsCallExpression, JsComputedMemberExpression, + JsLiteralMemberName, JsLogicalExpression, JsNumberLiteralExpression, JsObjectExpression, + JsRegexLiteralExpression, JsStaticMemberExpression, JsStringLiteralExpression, JsSyntaxKind, + JsSyntaxToken, JsTemplate, JsUnaryExpression, OperatorPrecedence, T, }; use crate::{JsPreUpdateExpression, JsSyntaxKind::*}; use rome_rowan::{ @@ -444,3 +445,70 @@ impl JsRegexLiteralExpression { Ok(String::from(&text_trimmed[1..end_slash_pos])) } } + +impl JsStaticMemberExpression { + pub fn is_optional(&self) -> bool { + self.operator_token() + .map_or(false, |token| token.kind() == JsSyntaxKind::QUESTIONDOT) + } + + pub fn is_optional_chain(&self) -> bool { + is_optional_chain(self.clone().into()) + } +} + +impl JsComputedMemberExpression { + pub fn is_optional(&self) -> bool { + self.optional_chain_token().is_some() + } + + pub fn is_optional_chain(&self) -> bool { + is_optional_chain(self.clone().into()) + } +} + +impl JsCallExpression { + pub fn is_optional(&self) -> bool { + self.optional_chain_token().is_some() + } + + pub fn is_optional_chain(&self) -> bool { + is_optional_chain(self.clone().into()) + } +} + +fn is_optional_chain(start: JsAnyExpression) -> bool { + let mut current = Some(start); + + while let Some(node) = current { + current = match node { + JsAnyExpression::JsParenthesizedExpression(parenthesized) => { + parenthesized.expression().ok() + } + + JsAnyExpression::JsCallExpression(call) => { + if call.is_optional() { + return true; + } + call.callee().ok() + } + + JsAnyExpression::JsStaticMemberExpression(member) => { + if member.is_optional() { + return true; + } + member.object().ok() + } + + JsAnyExpression::JsComputedMemberExpression(member) => { + if member.is_optional() { + return true; + } + member.object().ok() + } + _ => return false, + } + } + + false +}