From 14eab140c2d69aa3ab7132bbd004ae33ce4b2884 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Thu, 18 Aug 2022 08:30:57 +0100 Subject: [PATCH] feat(rome_js_formatter): Parenthesizing expressions (#3057) --- .../src/js/auxiliary/new_target.rs | 30 +- .../src/js/classes/extends_clause.rs | 2 + .../src/js/expressions/array_expression.rs | 32 +- .../expressions/arrow_function_expression.rs | 215 ++-- .../js/expressions/assignment_expression.rs | 130 ++- .../src/js/expressions/await_expression.rs | 119 ++- .../expressions/big_int_literal_expression.rs | 33 +- .../src/js/expressions/binary_expression.rs | 81 +- .../expressions/boolean_literal_expression.rs | 32 +- .../src/js/expressions/call_arguments.rs | 417 ++++---- .../src/js/expressions/call_expression.rs | 53 +- .../src/js/expressions/class_expression.rs | 49 +- .../expressions/computed_member_expression.rs | 53 +- .../js/expressions/conditional_expression.rs | 108 +- .../src/js/expressions/function_expression.rs | 56 +- .../js/expressions/identifier_expression.rs | 32 +- .../js/expressions/import_call_expression.rs | 27 +- .../src/js/expressions/in_expression.rs | 129 ++- .../js/expressions/instanceof_expression.rs | 73 +- .../src/js/expressions/logical_expression.rs | 76 +- .../src/js/expressions/new_expression.rs | 27 +- .../js/expressions/null_literal_expression.rs | 32 +- .../expressions/number_literal_expression.rs | 54 +- .../src/js/expressions/object_expression.rs | 57 +- .../expressions/parenthesized_expression.rs | 211 ++-- .../js/expressions/post_update_expression.rs | 57 +- .../js/expressions/pre_update_expression.rs | 74 +- .../expressions/regex_literal_expression.rs | 32 +- .../src/js/expressions/sequence_expression.rs | 57 +- .../expressions/static_member_expression.rs | 81 +- .../expressions/string_literal_expression.rs | 104 +- .../src/js/expressions/super_expression.rs | 32 +- .../src/js/expressions/template.rs | 34 +- .../src/js/expressions/this_expression.rs | 31 +- .../src/js/expressions/unary_expression.rs | 103 +- .../src/js/expressions/yield_expression.rs | 79 +- .../src/js/module/import_meta.rs | 31 +- .../src/js/statements/return_statement.rs | 21 +- .../src/js/unknown/unknown_expression.rs | 34 +- .../src/jsx/expressions/tag_expression.rs | 61 +- crates/rome_js_formatter/src/lib.rs | 28 +- crates/rome_js_formatter/src/parentheses.rs | 998 ++++++++++++++++++ .../src/ts/expressions/as_expression.rs | 125 ++- .../non_null_assertion_expression.rs | 29 +- .../expressions/type_assertion_expression.rs | 85 +- .../src/utils/assignment_like.rs | 157 +-- .../src/utils/binary_like_expression.rs | 232 ++-- .../src/utils/conditional.rs | 192 +--- crates/rome_js_formatter/src/utils/jsx.rs | 50 +- .../{flatten_item.rs => chain_member.rs} | 209 +++- .../src/utils/member_chain/groups.rs | 239 ++--- .../src/utils/member_chain/mod.rs | 478 +++++---- crates/rome_js_formatter/src/utils/mod.rs | 12 +- crates/rome_js_formatter/src/utils/parens.rs | 66 -- .../specs/js/module/arrow/params.js.snap | 10 +- .../js/module/assignment/assignment.js.snap | 51 +- .../tests/specs/js/module/class/class.js.snap | 9 +- .../expression/sequence_expression.js.snap | 23 +- .../object/property_object_member.js.snap | 30 +- .../js/module/parentheses/parentheses.js.snap | 10 +- .../js/arrows-bind/arrows-bind.js.snap | 4 +- .../prettier/js/assignment/chain.js.snap | 30 +- .../prettier/js/assignment/issue-2184.js.snap | 20 +- .../prettier/js/assignment/sequence.js.snap | 44 - .../prettier/js/async/await-parse.js.snap | 65 -- .../js/async/conditional-expression.js.snap | 57 - .../prettier/js/async/inline-await.js.snap | 55 - .../specs/prettier/js/async/nested.js.snap | 51 - .../js/binary-expressions/call.js.snap | 123 +-- .../js/bind-expressions/bind_parens.js.snap | 8 +- .../js/class-comment/superclass.js.snap | 51 +- .../prettier/js/class-extends/extends.js.snap | 147 --- .../prettier/js/classes/assignment.js.snap | 113 -- .../binary-expr.js.snap | 4 +- .../closure-compiler-type-cast.js.snap | 125 ++- .../comment-in-the-middle.js.snap | 9 +- .../comment-placement.js.snap | 28 +- .../extra-spaces-and-asterisks.js.snap | 57 + .../js/comments-closure-typecast/iife.js.snap | 44 +- .../issue-4124.js.snap | 58 +- .../issue-8045.js.snap | 14 +- .../issue-9358.js.snap | 50 + .../comments-closure-typecast/member.js.snap | 4 +- .../comments-closure-typecast/nested.js.snap | 18 +- .../non-casts.js.snap | 81 -- .../object-with-comment.js.snap | 38 +- .../styled-components.js.snap | 5 +- .../superclass.js.snap | 29 + .../ways-to-specify-type.js.snap | 75 ++ .../specs/prettier/js/comments/issues.js.snap | 26 +- .../js/comments/return-statement.js.snap | 176 +-- .../js/comments/trailing-jsdocs.js.snap | 13 +- .../prettier/js/conditional/comments.js.snap | 36 +- .../binary_and_template.js.snap | 29 + .../body.js.snap} | 13 +- .../js/export-default/class_instance.js.snap | 29 + .../js/first-argument-expansion/test.js.snap | 12 +- .../js/function/function_expression.js.snap | 22 +- .../prettier/js/function/issue-10277.js.snap | 40 - .../prettier/js/method-chain/logical.js.snap | 69 +- .../prettier/js/method-chain/test.js.snap | 5 +- .../js/new-expression/new_expression.js.snap | 50 - .../specs/prettier/js/no-semi/no-semi.js.snap | 18 +- .../prettier/js/objects/expression.js.snap | 60 +- .../js/optional-chaining/chaining.js.snap | 179 ++-- .../js/optional-chaining/eval.js.snap | 81 -- .../js/preserve-line/member-chain.js.snap | 33 +- .../js/reserved-word/interfaces.js.snap | 81 -- .../prettier/js/sequence-break/break.js.snap | 169 --- .../sequence-expressions.js.snap | 15 +- .../prettier/js/template/parenthesis.js.snap | 19 +- .../prettier/js/ternaries/binary.js.snap | 13 +- .../js/ternaries/indent-after-paren.js.snap | 130 +-- .../js/ternaries/nested-in-condition.js.snap | 14 +- .../prettier/js/ternaries/nested.js.snap | 35 +- .../js/unary-expression/comments.js.snap | 767 +++++++++----- .../urnary_expression.js.snap | 51 - .../specs/prettier/js/yield/arrow.js.snap | 43 - .../prettier/js/yield/conditional.js.snap | 71 -- .../argument_expansion.ts.snap | 137 --- .../typescript/arrow/arrow_regression.ts.snap | 4 +- .../specs/prettier/typescript/as/as.ts.snap | 52 +- .../prettier/typescript/as/assignment.ts.snap | 115 -- .../typescript/as/export_default_as.ts.snap | 29 + .../typescript/as/nested-await-and-as.ts.snap | 55 - .../prettier/typescript/as/ternary.ts.snap | 60 +- .../prettier/typescript/cast/as-const.ts.snap | 42 - .../typescript/cast/generic-cast.ts.snap | 38 +- .../typescript/compiler/castOfAwait.ts.snap | 52 - .../compiler/castParentheses.ts.snap | 18 +- .../typescript/compiler/castTest.ts.snap | 99 -- .../functionCalls/callWithSpreadES6.ts.snap | 137 --- .../functions/functionImplementations.ts.snap | 114 +- .../types/tuple/wideningTuples3.ts.snap | 38 - .../types/tuple/wideningTuples4.ts.snap | 38 - .../types/tuple/wideningTuples7.ts.snap | 41 - .../export-default/function_as.ts.snap | 29 + .../typescript/non-null/braces.ts.snap | 28 +- .../non-null/optional-chain.ts.snap | 27 +- .../typescript/non-null/parens.ts.snap | 83 -- .../1.ts.snap | 4 +- .../type_assertion_expression.ts.snap | 2 +- .../tests/specs/ts/parenthesis.ts.snap | 2 +- crates/rome_js_syntax/src/expr_ext.rs | 116 +- crates/rome_rowan/src/syntax/token.rs | 23 +- crates/rome_rowan/src/syntax/trivia.rs | 18 + editors/vscode/src/commands/syntaxTree.ts | 4 +- website/playground/src/utils.ts | 18 +- 148 files changed, 6328 insertions(+), 4793 deletions(-) create mode 100644 crates/rome_js_formatter/src/parentheses.rs rename crates/rome_js_formatter/src/utils/member_chain/{flatten_item.rs => chain_member.rs} (51%) delete mode 100644 crates/rome_js_formatter/src/utils/parens.rs delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/assignment/sequence.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/async/await-parse.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/async/conditional-expression.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/async/inline-await.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/async/nested.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/class-extends/extends.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/classes/assignment.js.snap create mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/extra-spaces-and-asterisks.js.snap create mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-9358.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/non-casts.js.snap create mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/superclass.js.snap create mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/ways-to-specify-type.js.snap create mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/export-default/binary_and_template.js.snap rename crates/rome_js_formatter/tests/specs/prettier/js/{classes/new.js.snap => export-default/body.js.snap} (52%) create mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/export-default/class_instance.js.snap 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/js/new-expression/new_expression.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/optional-chaining/eval.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/reserved-word/interfaces.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/sequence-break/break.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/unary-expression/urnary_expression.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/yield/arrow.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/js/yield/conditional.js.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/argument-expansion/argument_expansion.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/as/assignment.ts.snap create mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/as/export_default_as.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/as/nested-await-and-as.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/cast/as-const.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/castOfAwait.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/castTest.ts.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/conformance/types/tuple/wideningTuples3.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/tuple/wideningTuples4.ts.snap delete mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/tuple/wideningTuples7.ts.snap create mode 100644 crates/rome_js_formatter/tests/specs/prettier/typescript/export-default/function_as.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/auxiliary/new_target.rs b/crates/rome_js_formatter/src/js/auxiliary/new_target.rs index 71c91ebf688..4305a61bf61 100644 --- a/crates/rome_js_formatter/src/js/auxiliary/new_target.rs +++ b/crates/rome_js_formatter/src/js/auxiliary/new_target.rs @@ -1,8 +1,9 @@ use crate::prelude::*; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; use rome_formatter::write; -use rome_js_syntax::NewTarget; -use rome_js_syntax::NewTargetFields; +use rome_js_syntax::{JsAnyExpression, NewTargetFields}; +use rome_js_syntax::{JsSyntaxNode, NewTarget}; #[derive(Debug, Clone, Default)] pub struct FormatNewTarget; @@ -24,4 +25,29 @@ impl FormatNodeRule for FormatNewTarget { ] ] } + + fn needs_parentheses(&self, item: &NewTarget) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for NewTarget { + fn needs_parentheses(&self) -> bool { + false + } + + fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool { + false + } +} + +impl ExpressionNode for NewTarget { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + fn into_resolved(self) -> JsAnyExpression { + self.into() + } } diff --git a/crates/rome_js_formatter/src/js/classes/extends_clause.rs b/crates/rome_js_formatter/src/js/classes/extends_clause.rs index df00e9a1ed4..8d37c889ae6 100644 --- a/crates/rome_js_formatter/src/js/classes/extends_clause.rs +++ b/crates/rome_js_formatter/src/js/classes/extends_clause.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::parentheses::resolve_parent; use rome_formatter::{format_args, write}; use rome_js_syntax::JsExtendsClauseFields; use rome_js_syntax::JsSyntaxKind::JS_ASSIGNMENT_EXPRESSION; @@ -31,6 +32,7 @@ impl FormatNodeRule for FormatJsExtendsClause { if node .syntax() .parent() + .and_then(|node| resolve_parent(&node)) .map_or(false, |p| p.kind() == JS_ASSIGNMENT_EXPRESSION) { if super_class.syntax().has_leading_comments() || has_trailing_comments { diff --git a/crates/rome_js_formatter/src/js/expressions/array_expression.rs b/crates/rome_js_formatter/src/js/expressions/array_expression.rs index c0cea1b125a..880c310bcc3 100644 --- a/crates/rome_js_formatter/src/js/expressions/array_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/array_expression.rs @@ -1,8 +1,9 @@ use crate::prelude::*; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; use rome_formatter::write; -use rome_js_syntax::JsArrayExpression; -use rome_js_syntax::JsArrayExpressionFields; +use rome_js_syntax::{JsAnyExpression, JsArrayExpressionFields}; +use rome_js_syntax::{JsArrayExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsArrayExpression; @@ -27,4 +28,31 @@ impl FormatNodeRule for FormatJsArrayExpression { ] ) } + + fn needs_parentheses(&self, item: &JsArrayExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsArrayExpression { + #[inline(always)] + fn needs_parentheses(&self) -> bool { + false + } + #[inline(always)] + fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool { + false + } +} + +impl ExpressionNode for JsArrayExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } } 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 8bc4c14ecb7..d4a85e26b72 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 @@ -1,10 +1,15 @@ use crate::prelude::*; use rome_formatter::{format_args, write}; -use crate::utils::{is_simple_expression, resolve_expression, starts_with_no_lookahead_token}; +use crate::parentheses::{ + is_binary_like_left_or_right, is_conditional_test, + update_or_lower_expression_needs_parentheses, ExpressionNode, NeedsParentheses, +}; +use crate::utils::{resolve_left_most_expression, JsAnyBinaryLikeLeftExpression}; use rome_js_syntax::{ JsAnyArrowFunctionParameters, JsAnyExpression, JsAnyFunctionBody, JsAnyTemplateElement, - JsArrowFunctionExpression, JsArrowFunctionExpressionFields, JsTemplate, + JsArrowFunctionExpression, JsArrowFunctionExpressionFields, JsSyntaxKind, JsSyntaxNode, + JsTemplate, }; #[derive(Debug, Clone, Default)] @@ -28,36 +33,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?; @@ -78,55 +84,144 @@ impl FormatNodeRule for FormatJsArrowFunctionExpressi // Therefore if our body is an arrow self, array, or object, we // do not have a soft line break after the arrow because the body is // going to get broken anyways. - let (body_has_soft_line_break, should_add_parens) = match &body { - JsFunctionBody(_) => (true, false), - JsAnyExpression(expr) => match expr { + let body_has_soft_line_break = match &body { + JsFunctionBody(_) => true, + JsAnyExpression(expr) => match expr.resolve() { JsArrowFunctionExpression(_) | JsArrayExpression(_) | JsObjectExpression(_) - | JsxTagExpression(_) => (true, false), - JsParenthesizedExpression(expression) => { - let resolved = resolve_expression(expression.expression()?); - - match resolved { - JsConditionalExpression(conditional) => { - (false, !starts_with_no_lookahead_token(conditional.into())?) - } - _ => (true, false), - } + | JsParenthesizedExpression(_) + | JsxTagExpression(_) => true, + JsTemplate(template) => is_multiline_template_starting_on_same_line(&template), + JsSequenceExpression(_) => { + return write!( + f, + [group(&format_args![ + format_signature, + group(&format_args![ + space(), + text("("), + soft_block_indent(&body.format()), + text(")") + ]) + ])] + ); } - JsConditionalExpression(conditional) => ( - false, - !starts_with_no_lookahead_token(conditional.clone().into())?, - ), - JsTemplate(template) => { - (is_multiline_template_starting_on_same_line(template), false) - } - expr => (is_simple_expression(expr)?, false), + _ => false, }, }; - if body_has_soft_line_break { - write![f, [body.format()]] + // Add parentheses to avoid confusion between `a => b ? c : d` and `a <= b ? c : d` + // but only if the body isn't an object/function or class expression because parentheses are always required in that + // case and added by the object expression itself + let should_add_parens = match &body { + JsAnyExpression(expression) => { + let resolved = expression.resolve(); + + let is_conditional = matches!(resolved, JsConditionalExpression(_)); + let are_parentheses_mandatory = matches!( + resolve_left_most_expression(expression), + JsAnyBinaryLikeLeftExpression::JsAnyExpression( + JsObjectExpression(_) | JsFunctionExpression(_) | JsClassExpression(_) + ) + ); + + is_conditional && !are_parentheses_mandatory + } + _ => false, + }; + + if body_has_soft_line_break && !should_add_parens { + 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("("))])?; - } + [ + 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()])?; + write!(f, [body.format()])?; - if should_add_parens { - write!(f, [if_group_fits_on_line(&text(")"))])?; - } + if should_add_parens { + write!(f, [if_group_fits_on_line(&text(")"))])?; + } - Ok(()) - })))] + Ok(()) + }))) + ] ) } } + + fn needs_parentheses(&self, item: &JsArrowFunctionExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsArrowFunctionExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match parent.kind() { + JsSyntaxKind::TS_AS_EXPRESSION + | JsSyntaxKind::JS_UNARY_EXPRESSION + | JsSyntaxKind::JS_AWAIT_EXPRESSION + | JsSyntaxKind::TS_TYPE_ASSERTION_EXPRESSION => true, + + _ => { + is_conditional_test(self.syntax(), parent) + || update_or_lower_expression_needs_parentheses(self.syntax(), parent) + || is_binary_like_left_or_right(self.syntax(), parent) + } + } + } +} + +impl ExpressionNode for JsArrowFunctionExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::{JsArrowFunctionExpression, SourceType}; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("new (a => test)()`", JsArrowFunctionExpression); + assert_needs_parentheses!("(a => test)()", JsArrowFunctionExpression); + assert_needs_parentheses!("(a => test).member", JsArrowFunctionExpression); + assert_needs_parentheses!("(a => test)[member]", JsArrowFunctionExpression); + assert_not_needs_parentheses!("object[a => a]", JsArrowFunctionExpression); + assert_needs_parentheses!("(a => a) as Function", JsArrowFunctionExpression); + assert_needs_parentheses!("(a => a)!", JsArrowFunctionExpression); + assert_needs_parentheses!("(a => a)`template`", JsArrowFunctionExpression); + assert_needs_parentheses!("+(a => a)", JsArrowFunctionExpression); + assert_needs_parentheses!("(a => a) && b", JsArrowFunctionExpression); + assert_needs_parentheses!("(a => a) instanceof b", JsArrowFunctionExpression); + assert_needs_parentheses!("(a => a) in b", JsArrowFunctionExpression); + assert_needs_parentheses!("(a => a) + b", JsArrowFunctionExpression); + assert_needs_parentheses!("await (a => a)", JsArrowFunctionExpression); + assert_needs_parentheses!( + "(a => a)", + JsArrowFunctionExpression, + SourceType::ts() + ); + assert_needs_parentheses!("(a => a) ? b : c", JsArrowFunctionExpression); + assert_not_needs_parentheses!("a ? b => b : c", JsArrowFunctionExpression); + assert_not_needs_parentheses!("a ? b : c => c", JsArrowFunctionExpression); + assert_needs_parentheses!("class Test extends (a => a) {}", JsArrowFunctionExpression); + } } /// Returns `true` if the template contains any new lines inside of its text chunks. 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 4cbc014bd68..0a1c3ab9690 100644 --- a/crates/rome_js_formatter/src/js/expressions/assignment_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/assignment_expression.rs @@ -1,8 +1,17 @@ use crate::prelude::*; use crate::utils::JsAnyAssignmentLike; +use crate::parentheses::{ + is_arrow_function_body, is_first_in_statement, ExpressionNode, FirstInStatementMode, + NeedsParentheses, +}; use rome_formatter::write; -use rome_js_syntax::JsAssignmentExpression; + +use rome_js_syntax::{ + JsAnyAssignmentPattern, JsAnyExpression, JsAnyForInitializer, JsAssignmentExpression, + JsForStatement, JsSyntaxKind, JsSyntaxNode, +}; +use rome_rowan::AstNode; #[derive(Debug, Clone, Default)] pub struct FormatJsAssignmentExpression; @@ -11,4 +20,123 @@ impl FormatNodeRule for FormatJsAssignmentExpression { fn fmt_fields(&self, node: &JsAssignmentExpression, f: &mut JsFormatter) -> FormatResult<()> { write![f, [JsAnyAssignmentLike::from(node.clone())]] } + + fn needs_parentheses(&self, item: &JsAssignmentExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsAssignmentExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match parent.kind() { + JsSyntaxKind::JS_ASSIGNMENT_EXPRESSION => false, + // `[a = b]` + JsSyntaxKind::JS_COMPUTED_MEMBER_NAME => false, + + JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION => { + is_arrow_function_body(self.syntax(), parent) + } + JsSyntaxKind::JS_FOR_STATEMENT => { + let for_statement = JsForStatement::unwrap_cast(parent.clone()); + let is_initializer = match for_statement.initializer() { + Some(JsAnyForInitializer::JsAnyExpression(expression)) => { + &expression.resolve_syntax() == self.syntax() + } + None | Some(_) => false, + }; + + let is_update = for_statement + .update() + .map(ExpressionNode::into_resolved_syntax) + .as_ref() + == Some(self.syntax()); + + !(is_initializer || is_update) + } + JsSyntaxKind::JS_EXPRESSION_STATEMENT => { + // Parenthesize `{ a } = { a: 5 }` + is_first_in_statement( + self.clone().into(), + FirstInStatementMode::ExpressionStatementOrArrow, + ) && matches!( + self.left(), + Ok(JsAnyAssignmentPattern::JsObjectAssignmentPattern(_)) + ) + } + JsSyntaxKind::JS_SEQUENCE_EXPRESSION => { + let mut child = parent.clone(); + + for ancestor in parent.ancestors().skip(1) { + match ancestor.kind() { + JsSyntaxKind::JS_SEQUENCE_EXPRESSION + | JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION => child = ancestor, + JsSyntaxKind::JS_FOR_STATEMENT => { + let for_statement = JsForStatement::unwrap_cast(ancestor); + + let is_initializer = match for_statement.initializer() { + Some(JsAnyForInitializer::JsAnyExpression(expression)) => { + expression.syntax() == &child + } + None | Some(_) => false, + }; + + let is_update = + for_statement.update().map(AstNode::into_syntax).as_ref() + == Some(&child); + + return !(is_initializer || is_update); + } + _ => break, + } + } + + true + } + + _ => true, + } + } +} + +impl ExpressionNode for JsAssignmentExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::JsAssignmentExpression; + + #[test] + fn needs_parentheses() { + assert_not_needs_parentheses!("({ [a = 3]: value })", JsAssignmentExpression); + assert_not_needs_parentheses!("class Test { [a = 3]: value }", JsAssignmentExpression); + assert_not_needs_parentheses!("type Test = { [a = 3]: value }", JsAssignmentExpression); + assert_not_needs_parentheses!("interface Test { [a = 3]: value }", JsAssignmentExpression); + + assert_needs_parentheses!("a => (a = 3)", JsAssignmentExpression); + assert_not_needs_parentheses!("a => { a = 3 }", JsAssignmentExpression); + + assert_not_needs_parentheses!("for(a = 3;;) {}", JsAssignmentExpression); + assert_not_needs_parentheses!("for(a = 3, b = 2;;) {}", JsAssignmentExpression[1]); + assert_not_needs_parentheses!("for(a = 3, b = 2, c= 3;;) {}", JsAssignmentExpression[2]); + assert_needs_parentheses!("for(; a = 3; ) {}", JsAssignmentExpression); + assert_not_needs_parentheses!("for(;;a = 3) {}", JsAssignmentExpression); + + assert_not_needs_parentheses!("for ((a, a = 3);;) {}", JsAssignmentExpression); + assert_needs_parentheses!("for (; (a, a = 3);) {}", JsAssignmentExpression); + assert_not_needs_parentheses!("for (;;(a, a = 3)) {}", JsAssignmentExpression); + + assert_not_needs_parentheses!("a = 3", JsAssignmentExpression); + assert_needs_parentheses!("({ a } = { a: 3 })", 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 b93374c6136..1a4d24ae050 100644 --- a/crates/rome_js_formatter/src/js/expressions/await_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/await_expression.rs @@ -1,8 +1,13 @@ use crate::prelude::*; use rome_formatter::write; -use rome_js_syntax::JsAwaitExpression; -use rome_js_syntax::JsAwaitExpressionFields; +use crate::parentheses::{ + is_binary_like_left_or_right, is_callee, is_conditional_test, is_member_object, is_spread, + update_or_lower_expression_needs_parentheses, ExpressionNode, NeedsParentheses, +}; + +use rome_js_syntax::{JsAnyExpression, JsAwaitExpression, JsSyntaxNode}; +use rome_js_syntax::{JsAwaitExpressionFields, JsSyntaxKind}; #[derive(Debug, Clone, Default)] pub struct FormatJsAwaitExpression; @@ -14,6 +19,114 @@ impl FormatNodeRule for FormatJsAwaitExpression { argument, } = node.as_fields(); - write![f, [await_token.format(), space(), argument.format(),]] + let format_inner = + format_with(|f| write![f, [await_token.format(), space(), argument.format()]]); + + let parent = node.resolve_parent(); + + if let Some(parent) = parent { + if is_callee(node.syntax(), &parent) || is_member_object(node.syntax(), &parent) { + let ancestor_await_or_block = parent.ancestors().skip(1).find(|ancestor| { + matches!( + ancestor.kind(), + JsSyntaxKind::JS_AWAIT_EXPRESSION + // Stop at statement boundaries. + | JsSyntaxKind::JS_STATEMENT_LIST + | JsSyntaxKind::JS_MODULE_ITEM_LIST + ) + }); + + let indented = format_with(|f| write!(f, [soft_block_indent(&format_inner)])); + + return if ancestor_await_or_block.map_or(false, |ancestor| { + JsAwaitExpression::can_cast(ancestor.kind()) + }) { + write!(f, [indented]) + } else { + write!(f, [group(&indented)]) + }; + } + } + + write!(f, [format_inner]) + } + + fn needs_parentheses(&self, item: &JsAwaitExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsAwaitExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + await_or_yield_needs_parens(parent, self.syntax()) + } +} + +pub(super) fn await_or_yield_needs_parens(parent: &JsSyntaxNode, node: &JsSyntaxNode) -> bool { + debug_assert!(matches!( + node.kind(), + JsSyntaxKind::JS_AWAIT_EXPRESSION | JsSyntaxKind::JS_YIELD_EXPRESSION + )); + + match parent.kind() { + JsSyntaxKind::JS_UNARY_EXPRESSION | JsSyntaxKind::TS_AS_EXPRESSION => true, + + _ => { + let expression = node; + is_conditional_test(node, parent) + || update_or_lower_expression_needs_parentheses(expression, parent) + || is_spread(expression, parent) + || is_binary_like_left_or_right(node, parent) + } + } +} + +impl ExpressionNode for JsAwaitExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::JsAwaitExpression; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("(await a)`template`", JsAwaitExpression); + assert_needs_parentheses!("+(await a)", JsAwaitExpression); + + assert_needs_parentheses!("(await a).b", JsAwaitExpression); + assert_needs_parentheses!("(await a)[b]", JsAwaitExpression); + assert_not_needs_parentheses!("a[await b]", JsAwaitExpression); + + assert_needs_parentheses!("(await a)()", JsAwaitExpression); + assert_needs_parentheses!("new (await a)()", JsAwaitExpression); + + assert_needs_parentheses!("(await a) && b", JsAwaitExpression); + assert_needs_parentheses!("(await a) + b", JsAwaitExpression); + assert_needs_parentheses!("(await a) instanceof b", JsAwaitExpression); + assert_needs_parentheses!("(await a) in b", JsAwaitExpression); + + assert_needs_parentheses!("[...(await a)]", JsAwaitExpression); + assert_needs_parentheses!("({...(await b)})", JsAwaitExpression); + assert_needs_parentheses!("call(...(await b))", JsAwaitExpression); + + assert_needs_parentheses!("class A extends (await b) {}", JsAwaitExpression); + + assert_needs_parentheses!("(await b) as number", JsAwaitExpression); + assert_needs_parentheses!("(await b)!", JsAwaitExpression); + + assert_needs_parentheses!("(await b) ? b : c", JsAwaitExpression); + assert_not_needs_parentheses!("a ? await b : c", JsAwaitExpression); + assert_not_needs_parentheses!("a ? b : await c", JsAwaitExpression); } } diff --git a/crates/rome_js_formatter/src/js/expressions/big_int_literal_expression.rs b/crates/rome_js_formatter/src/js/expressions/big_int_literal_expression.rs index 57d3e362aa6..cc4a50c9a9d 100644 --- a/crates/rome_js_formatter/src/js/expressions/big_int_literal_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/big_int_literal_expression.rs @@ -4,8 +4,9 @@ use std::borrow::Cow; use crate::prelude::*; use crate::utils::string_utils::ToAsciiLowercaseCow; -use rome_js_syntax::JsBigIntLiteralExpression; -use rome_js_syntax::JsBigIntLiteralExpressionFields; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use rome_js_syntax::{JsAnyExpression, JsAnyLiteralExpression, JsBigIntLiteralExpressionFields}; +use rome_js_syntax::{JsBigIntLiteralExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsBigIntLiteralExpression; @@ -33,4 +34,32 @@ impl FormatNodeRule for FormatJsBigIntLiteralExpressi } } } + + fn needs_parentheses(&self, item: &JsBigIntLiteralExpression) -> bool { + item.needs_parentheses() + } +} + +impl ExpressionNode for JsBigIntLiteralExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::from(self.clone())) + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::from(self)) + } +} + +impl NeedsParentheses for JsBigIntLiteralExpression { + #[inline(always)] + fn needs_parentheses(&self) -> bool { + false + } + + #[inline(always)] + fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool { + false + } } diff --git a/crates/rome_js_formatter/src/js/expressions/binary_expression.rs b/crates/rome_js_formatter/src/js/expressions/binary_expression.rs index 7cb50929cc4..56f3e79d233 100644 --- a/crates/rome_js_formatter/src/js/expressions/binary_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/binary_expression.rs @@ -1,7 +1,10 @@ use crate::prelude::*; -use crate::utils::{format_binary_like_expression, JsAnyBinaryLikeExpression}; +use crate::utils::{ + format_binary_like_expression, needs_binary_like_parentheses, JsAnyBinaryLikeExpression, +}; -use rome_js_syntax::JsBinaryExpression; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use rome_js_syntax::{JsAnyExpression, JsBinaryExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsBinaryExpression; @@ -18,3 +21,77 @@ impl FormatNodeRule for FormatJsBinaryExpression { ) } } + +impl NeedsParentheses for JsBinaryExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + needs_binary_like_parentheses(&JsAnyBinaryLikeExpression::from(self.clone()), parent) + } +} + +impl ExpressionNode for JsBinaryExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::{JsBinaryExpression, SourceType}; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("class X extends (4 + 4) {}", JsBinaryExpression); + + assert_needs_parentheses!("(4 + 4) as number", JsBinaryExpression); + assert_needs_parentheses!("(4 + 4)", JsBinaryExpression); + assert_needs_parentheses!("!(4 + 4)", JsBinaryExpression); + assert_needs_parentheses!("await (4 + 4)", JsBinaryExpression); + assert_needs_parentheses!("(4 + 4)!", JsBinaryExpression); + + assert_needs_parentheses!("(4 + 4)()", JsBinaryExpression); + assert_needs_parentheses!("(4 + 4)?.()", JsBinaryExpression); + assert_needs_parentheses!("new (4 + 4)()", JsBinaryExpression); + assert_needs_parentheses!("(4 + 4)`template`", JsBinaryExpression); + assert_needs_parentheses!("[...(4 + 4)]", JsBinaryExpression); + assert_needs_parentheses!("({...(4 + 4)})", JsBinaryExpression); + assert_needs_parentheses!( + "", + JsBinaryExpression, + SourceType::tsx() + ); + assert_needs_parentheses!( + "{...(4 + 4)}", + JsBinaryExpression, + SourceType::tsx() + ); + + assert_needs_parentheses!("(4 + 4).member", JsBinaryExpression); + assert_needs_parentheses!("(4 + 4)[member]", JsBinaryExpression); + assert_not_needs_parentheses!("object[4 + 4]", JsBinaryExpression); + + assert_needs_parentheses!("(4 + 4) * 3", JsBinaryExpression[1]); + assert_not_needs_parentheses!("(4 + 4) * 3", JsBinaryExpression[0]); + + assert_needs_parentheses!("a ** b ** c", JsBinaryExpression[1]); + assert_not_needs_parentheses!("a ** b ** c", JsBinaryExpression[0]); + + assert_needs_parentheses!("a * r >> 5", JsBinaryExpression[1]); + assert_not_needs_parentheses!("a * r >> 5", JsBinaryExpression[0]); + + assert_needs_parentheses!("a * r | 4", JsBinaryExpression[1]); + assert_not_needs_parentheses!("a * r | 5", JsBinaryExpression[0]); + + assert_needs_parentheses!("a % 4 + 4", JsBinaryExpression[1]); + assert_not_needs_parentheses!("a % 4 + 4", JsBinaryExpression[0]); + + assert_needs_parentheses!("a == b == c", JsBinaryExpression[1]); + assert_not_needs_parentheses!("a == b == c", JsBinaryExpression[0]); + } +} diff --git a/crates/rome_js_formatter/src/js/expressions/boolean_literal_expression.rs b/crates/rome_js_formatter/src/js/expressions/boolean_literal_expression.rs index 30343bc67f6..883f1869170 100644 --- a/crates/rome_js_formatter/src/js/expressions/boolean_literal_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/boolean_literal_expression.rs @@ -1,8 +1,9 @@ use crate::prelude::*; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; use rome_formatter::write; -use rome_js_syntax::JsBooleanLiteralExpression; -use rome_js_syntax::JsBooleanLiteralExpressionFields; +use rome_js_syntax::{JsAnyExpression, JsAnyLiteralExpression, JsBooleanLiteralExpressionFields}; +use rome_js_syntax::{JsBooleanLiteralExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsBooleanLiteralExpression; @@ -17,4 +18,31 @@ impl FormatNodeRule for FormatJsBooleanLiteralExpres write![f, [value_token.format()]] } + + fn needs_parentheses(&self, item: &JsBooleanLiteralExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsBooleanLiteralExpression { + #[inline(always)] + fn needs_parentheses(&self) -> bool { + false + } + #[inline(always)] + fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool { + false + } +} + +impl ExpressionNode for JsBooleanLiteralExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::from(self.clone())) + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::from(self)) + } } diff --git a/crates/rome_js_formatter/src/js/expressions/call_arguments.rs b/crates/rome_js_formatter/src/js/expressions/call_arguments.rs index f64b053bab0..437c28adec1 100644 --- a/crates/rome_js_formatter/src/js/expressions/call_arguments.rs +++ b/crates/rome_js_formatter/src/js/expressions/call_arguments.rs @@ -1,4 +1,5 @@ use crate::builders::{format_close_delimiter, format_open_delimiter}; +use crate::parentheses::ExpressionNode; use crate::prelude::*; use crate::utils::{is_call_like_expression, write_arguments_multi_line}; use rome_formatter::{format_args, write}; @@ -43,21 +44,20 @@ impl FormatNodeRule for FormatJsCallArguments { let first_argument = first_argument?; let second_argument = second_argument?; - let is_framework_test_call = if let Some(call_expression) = - node.syntax().parent().and_then(JsCallExpression::cast) - { - let callee = call_expression.callee()?; - - is_framework_test_call(IsTestFrameworkCallPayload { - first_argument: &first_argument, - second_argument: &second_argument, - third_argument: &third_argument, - arguments_len, - callee: &callee, - })? - } else { - false - }; + let is_framework_test_call = + if let Some(call_expression) = node.parent::() { + let callee = call_expression.callee()?; + + is_framework_test_call(IsTestFrameworkCallPayload { + first_argument: &first_argument, + second_argument: &second_argument, + third_argument: &third_argument, + arguments_len, + callee: &callee, + })? + } else { + false + }; let is_react_hook_with_deps_array = is_react_hook_with_deps_array(&first_argument, &second_argument)? @@ -104,20 +104,16 @@ impl FormatNodeRule for FormatJsCallArguments { .map(|e| e.memoized()) .collect(); - let mut any_breaks = false; let an_argument_breaks = separated .iter_mut() .enumerate() .any(|(index, element)| match element.inspect(f) { Ok(element) => { - if element.will_break() { - any_breaks = true; - should_group_first_argument && index > 0 - || (should_group_last_argument && index < args.len() - 1) - } else { - false - } + let in_relevant_range = should_group_first_argument && index > 0 + || (should_group_last_argument && index < args.len() - 1); + + in_relevant_range && element.will_break() } Err(_) => false, }); @@ -168,10 +164,6 @@ impl FormatNodeRule for FormatJsCallArguments { return write!(f, [all_arguments_expanded]); } - if any_breaks { - write!(f, [expand_parent()])?; - } - let edge_arguments_do_not_break = format_with(|f| { // `should_group_first_argument` and `should_group_last_argument` are mutually exclusive // which means that if one is `false`, then the other is `true`. @@ -212,9 +204,9 @@ impl FormatNodeRule for FormatJsCallArguments { l_leading_trivia, l_paren, l_trailing_trivia, - group(&format_args![format_with(|f| { + group(&format_with(|f| { write_arguments_multi_line(separated.iter(), f) - })]), + })), r_leading_trivia, r_paren, r_trailing_trivia @@ -228,19 +220,19 @@ impl FormatNodeRule for FormatJsCallArguments { f, [ l_leading_trivia, - &group(&format_args![ + group(&format_args![ l_paren, l_trailing_trivia, - &soft_block_indent(&format_with(|f| { + soft_block_indent(&format_with(|f| { let separated = args .format_separated(JsSyntaxKind::COMMA) .with_trailing_separator(TrailingSeparator::Omit) .nodes_grouped(); write_arguments_multi_line(separated, f) - }),), + })), r_leading_trivia, r_paren, - ],), + ]), r_trailing_trivia ] ) @@ -260,38 +252,39 @@ fn should_group_first_argument(list: &JsCallArgumentList) -> SyntaxResult let has_comments = first.syntax().has_comments_direct(); - let is_function_like = if let JsAnyCallArgument::JsAnyExpression(expression) = first { - match expression { - JsAnyExpression::JsFunctionExpression(_) => true, - JsAnyExpression::JsArrowFunctionExpression(arrow) => { - matches!(arrow.body()?, JsAnyFunctionBody::JsFunctionBody(_)) - } - _ => false, + let is_function_like = match resolve_call_argument_expression(&first) { + Some(JsAnyExpression::JsFunctionExpression(_)) => true, + Some(JsAnyExpression::JsArrowFunctionExpression(arrow)) => { + matches!(arrow.body()?, JsAnyFunctionBody::JsFunctionBody(_)) } - } else { - false + _ => false, }; - let second_arg_is_function_like = matches!( - second, - JsAnyCallArgument::JsAnyExpression( - JsAnyExpression::JsFunctionExpression(_) - | JsAnyExpression::JsArrowFunctionExpression(_) - | JsAnyExpression::JsConditionalExpression(_) - ) - ); - Ok(!has_comments - && is_function_like - && !second_arg_is_function_like - && !could_group_argument(&second, false)?) + let (second_arg_is_function_like, can_group) = match resolve_call_argument_expression(&second) { + Some(second_expression) => { + let second_arg_is_function_like = matches!( + &second_expression, + JsAnyExpression::JsFunctionExpression(_) + | JsAnyExpression::JsArrowFunctionExpression(_) + | JsAnyExpression::JsConditionalExpression(_) + ); + ( + second_arg_is_function_like, + could_group_expression_argument(&second_expression, false)?, + ) + } + None => (false, false), + }; + + Ok(!has_comments && is_function_like && !second_arg_is_function_like && !can_group) } /// Checks if the last group requires grouping fn should_group_last_argument(list: &JsCallArgumentList) -> SyntaxResult { let list_len = list.len(); - let mut iter = list.iter().rev(); - let last = iter.next(); - let penultimate = iter.next(); + let mut iter = list.iter(); + let last = iter.next_back(); + let penultimate = iter.next_back(); if let Some(last) = last { let last = last?; @@ -303,6 +296,7 @@ fn should_group_last_argument(list: &JsCallArgumentList) -> SyntaxResult { || !JsArrayExpression::can_cast(penultimate.syntax().kind()) || !JsArrowFunctionExpression::can_cast(last.syntax().kind()); + // TODO implement no poor printed array let _no_poor_printed_array = !list_len > 1 && JsArrayExpression::can_cast(last.syntax().kind()); different_kind && no_array_and_arrow_function @@ -310,63 +304,64 @@ fn should_group_last_argument(list: &JsCallArgumentList) -> SyntaxResult { true }; - Ok(!last.syntax().has_comments_direct() - && could_group_argument(&last, false)? - && check_with_penultimate) + let can_group = match &last { + JsAnyCallArgument::JsAnyExpression(expression) => { + could_group_expression_argument(expression, false)? + } + _ => false, + }; + + Ok(!last.syntax().has_comments_direct() && can_group && check_with_penultimate) } else { Ok(false) } } /// Checks if the current argument could be grouped -fn could_group_argument( - argument: &JsAnyCallArgument, +fn could_group_expression_argument( + argument: &JsAnyExpression, is_arrow_recursion: bool, ) -> SyntaxResult { - let result = if let JsAnyCallArgument::JsAnyExpression(argument) = argument { - match argument { - JsAnyExpression::JsObjectExpression(object_expression) => { - object_expression.members().len() > 0 - || object_expression - .syntax() - .first_or_last_token_have_comments() - } + let result = match argument.resolve() { + JsAnyExpression::JsObjectExpression(object_expression) => { + object_expression.members().len() > 0 + || object_expression + .syntax() + .first_or_last_token_have_comments() + } - JsAnyExpression::JsArrayExpression(array_expression) => { - array_expression.elements().len() > 0 - || array_expression - .syntax() - .first_or_last_token_have_comments() - } - JsAnyExpression::TsTypeAssertionExpression(assertion_expression) => { - could_group_argument( - &JsAnyCallArgument::JsAnyExpression(assertion_expression.expression()?), - false, - )? - } + JsAnyExpression::JsArrayExpression(array_expression) => { + array_expression.elements().len() > 0 + || array_expression + .syntax() + .first_or_last_token_have_comments() + } + JsAnyExpression::TsTypeAssertionExpression(assertion_expression) => { + could_group_expression_argument(&assertion_expression.expression()?, false)? + } - JsAnyExpression::TsAsExpression(as_expression) => could_group_argument( - &JsAnyCallArgument::JsAnyExpression(as_expression.expression()?), - false, - )?, - JsAnyExpression::JsArrowFunctionExpression(arrow_function) => { - let body = arrow_function.body()?; - let return_type_annotation = arrow_function.return_type_annotation(); - - // Handles cases like: - // - // app.get("/", (req, res): void => { - // res.send("Hello World!"); - // }); - // - // export class Thing implements OtherThing { - // do: (type: Type) => Provider = memoize( - // (type: ObjectType): Provider => {} - // ); - // } - let can_group_type = !return_type_annotation.and_then(|rty| rty.ty().ok()).map_or( - false, - |any_type| { + JsAnyExpression::TsAsExpression(as_expression) => { + could_group_expression_argument(&as_expression.expression()?, false)? + } + JsAnyExpression::JsArrowFunctionExpression(arrow_function) => { + let body = arrow_function.body()?; + let return_type_annotation = arrow_function.return_type_annotation(); + + // Handles cases like: + // + // app.get("/", (req, res): void => { + // res.send("Hello World!"); + // }); + // + // export class Thing implements OtherThing { + // do: (type: Type) => Provider = memoize( + // (type: ObjectType): Provider => {} + // ); + // } + let can_group_type = + !return_type_annotation + .and_then(|rty| rty.ty().ok()) + .map_or(false, |any_type| { TsReferenceType::can_cast(any_type.syntax().kind()) || if let JsAnyFunctionBody::JsFunctionBody(function_body) = &body { function_body @@ -376,61 +371,53 @@ fn could_group_argument( } else { true } - }, - ); + }); + + let expression_body = match &body { + JsAnyFunctionBody::JsFunctionBody(_) => None, + JsAnyFunctionBody::JsAnyExpression(expression) => Some(expression.resolve()), + }; - let body_is_delimited = matches!( - body, - JsAnyFunctionBody::JsFunctionBody(_) - | JsAnyFunctionBody::JsAnyExpression(JsAnyExpression::JsObjectExpression( - _ - )) - | JsAnyFunctionBody::JsAnyExpression(JsAnyExpression::JsArrayExpression(_)) + let body_is_delimited = matches!(body, JsAnyFunctionBody::JsFunctionBody(_)) + || matches!( + expression_body, + Some( + JsAnyExpression::JsObjectExpression(_) + | JsAnyExpression::JsArrayExpression(_) + ) ); - if let JsAnyFunctionBody::JsAnyExpression(any_expression) = body.clone() { - let is_nested_arrow_function = - if let JsAnyExpression::JsArrowFunctionExpression( - arrow_function_expression, - ) = &any_expression - { - arrow_function_expression - .body() - .ok() - .and_then(|body| body.as_js_any_expression().cloned()) - .and_then(|body| { - could_group_argument( - &JsAnyCallArgument::JsAnyExpression(body), - true, - ) - .ok() - }) - .unwrap_or(false) - } else { - false - }; - - body_is_delimited - && is_nested_arrow_function - && can_group_type - && (!is_arrow_recursion - && (is_call_like_expression(&any_expression) - || matches!( - body, - JsAnyFunctionBody::JsAnyExpression( - JsAnyExpression::JsConditionalExpression(_) - ) - ))) - } else { - body_is_delimited && can_group_type - } + if let Some(any_expression) = expression_body { + let is_nested_arrow_function = + if let JsAnyExpression::JsArrowFunctionExpression(arrow_function_expression) = + &any_expression + { + arrow_function_expression + .body() + .ok() + .and_then(|body| body.as_js_any_expression().cloned()) + .and_then(|body| could_group_expression_argument(&body, true).ok()) + .unwrap_or(false) + } else { + false + }; + + body_is_delimited + && is_nested_arrow_function + && can_group_type + && (!is_arrow_recursion + && (is_call_like_expression(&any_expression) + || matches!( + any_expression, + JsAnyExpression::JsConditionalExpression(_) + ))) + } else { + body_is_delimited && can_group_type } - - JsAnyExpression::JsFunctionExpression(_) => true, - _ => false, } - } else { - false + + JsAnyExpression::JsFunctionExpression(_) => true, + _ => false, }; Ok(result) @@ -445,9 +432,14 @@ fn is_react_hook_with_deps_array( first_argument: &JsAnyCallArgument, second_argument: &JsAnyCallArgument, ) -> SyntaxResult { - let first_node_matches = if let JsAnyCallArgument::JsAnyExpression( - JsAnyExpression::JsArrowFunctionExpression(arrow_function), - ) = first_argument + let first_expression = match first_argument { + JsAnyCallArgument::JsAnyExpression(expression) => Some(expression.resolve()), + _ => None, + }; + + let first_node_matches = if let Some(JsAnyExpression::JsArrowFunctionExpression( + arrow_function, + )) = first_expression { let no_parameters = arrow_function.parameters()?.is_empty(); let body = arrow_function.body()?; @@ -528,9 +520,19 @@ fn is_framework_test_call(payload: IsTestFrameworkCallPayload) -> SyntaxResult resolve_call_argument_expression(argument), + _ => None, + }); + let first_argument_is_literal_like = matches!( - first_argument, - JsAnyCallArgument::JsAnyExpression( + first_argument_expression, + Some( JsAnyExpression::JsAnyLiteralExpression( JsAnyLiteralExpression::JsStringLiteralExpression(_) ) | JsAnyExpression::JsTemplate(_) @@ -538,35 +540,31 @@ fn is_framework_test_call(payload: IsTestFrameworkCallPayload) -> SyntaxResult { ... }, 2500)` - if let Some(Ok(third_argument)) = third_argument { - if !matches!( - third_argument, - JsAnyCallArgument::JsAnyExpression(JsAnyExpression::JsAnyLiteralExpression( - JsAnyLiteralExpression::JsNumberLiteralExpression(_) - )) - ) { - return Ok(false); - } - } - if arguments_len == 2 { Ok(matches!( - second_argument, - JsAnyCallArgument::JsAnyExpression( + second_argument_expression, + Some( JsAnyExpression::JsArrowFunctionExpression(_) | JsAnyExpression::JsFunctionExpression(_) ) )) } else { - let result = match second_argument { - JsAnyCallArgument::JsAnyExpression(JsAnyExpression::JsFunctionExpression(node)) => { + // if the third argument is not a numeric literal, we bail + // example: `it("name", () => { ... }, 2500)` + if !matches!( + third_argument_expression, + Some(JsAnyExpression::JsAnyLiteralExpression( + JsAnyLiteralExpression::JsNumberLiteralExpression(_) + )) + ) { + return Ok(false); + } + + let result = match second_argument_expression { + Some(JsAnyExpression::JsFunctionExpression(node)) => { node.parameters()?.items().len() <= 1 } - JsAnyCallArgument::JsAnyExpression(JsAnyExpression::JsArrowFunctionExpression( - node, - )) => { + Some(JsAnyExpression::JsArrowFunctionExpression(node)) => { let body = node.body()?; let has_enough_parameters = node.parameters()?.len() <= 1; matches!(body, JsAnyFunctionBody::JsFunctionBody(_)) && has_enough_parameters @@ -580,6 +578,15 @@ fn is_framework_test_call(payload: IsTestFrameworkCallPayload) -> SyntaxResult Option { + match argument { + JsAnyCallArgument::JsAnyExpression(expression) => Some(expression.resolve()), + _ => None, + } +} + /// This function checks if a call expressions has one of the following members: /// - `it` /// - `it.only` @@ -665,24 +672,33 @@ fn matches_test_call(callee: &JsAnyExpression) -> SyntaxResult { - let value = name.value_token()?; - test_call.push(value.token_text_trimmed()); - current_node = member_expression.object()?; + let mut i = 0; + + while i < MAX_DEPTH { + i += 1; + current_node = match current_node { + JsAnyExpression::JsIdentifierExpression(identifier) => { + let value_token = identifier.name()?.value_token()?; + let value = value_token.token_text_trimmed(); + test_call.push(value); + break; + } + JsAnyExpression::JsStaticMemberExpression(member_expression) => { + match member_expression.member()? { + JsAnyName::JsName(name) => { + let value = name.value_token()?; + test_call.push(value.token_text_trimmed()); + member_expression.object()? + } + _ => break, } - _ => break, - }; - } else { - break; - } + } + JsAnyExpression::JsParenthesizedExpression(parenthesized) => { + i -= 1; // Don't increment the depth + parenthesized.expression()? + } + _ => break, + }; } test_call.reverse(); Ok(test_call) @@ -751,6 +767,15 @@ mod test { ); } + #[test] + fn matches_parentheses() { + let call_expression = extract_call_expression("(test.describe.parallel).only();"); + assert_eq!( + contains_a_test_pattern(&call_expression.callee().unwrap()), + Ok(true) + ); + } + #[test] fn doesnt_static_member_expression_deep() { let call_expression = extract_call_expression("test.describe.parallel.only.AHAHA();"); 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 8942f029549..7566246f39d 100644 --- a/crates/rome_js_formatter/src/js/expressions/call_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/call_expression.rs @@ -1,14 +1,57 @@ use crate::prelude::*; -use crate::utils::format_call_expression; -use rome_js_syntax::JsCallExpression; -use rome_rowan::AstNode; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use crate::utils::get_member_chain; +use rome_js_syntax::{JsAnyExpression, JsCallExpression, JsSyntaxKind, JsSyntaxNode}; #[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 { + matches!(parent.kind(), JsSyntaxKind::JS_NEW_EXPRESSION) + } +} + +impl ExpressionNode for JsCallExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[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 ceeebfe7174..167b372b120 100644 --- a/crates/rome_js_formatter/src/js/expressions/class_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/class_expression.rs @@ -1,7 +1,10 @@ use crate::prelude::*; use crate::utils::format_class::FormatClass; -use rome_js_syntax::JsClassExpression; +use crate::parentheses::{ + is_callee, is_first_in_statement, ExpressionNode, FirstInStatementMode, NeedsParentheses, +}; +use rome_js_syntax::{JsAnyExpression, JsClassExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsClassExpression; @@ -10,4 +13,48 @@ impl FormatNodeRule for FormatJsClassExpression { fn fmt_fields(&self, node: &JsClassExpression, f: &mut JsFormatter) -> FormatResult<()> { FormatClass::from(&node.clone().into()).fmt(f) } + + fn needs_parentheses(&self, item: &JsClassExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsClassExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + is_callee(self.syntax(), parent) + || is_first_in_statement( + self.clone().into(), + FirstInStatementMode::ExpressionOrExportDefault, + ) + } +} + +impl ExpressionNode for JsClassExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::JsClassExpression; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("console.log((class {})())", JsClassExpression); + assert_needs_parentheses!("console.log(new (class {})())", JsClassExpression); + + assert_needs_parentheses!("(class {}).test", JsClassExpression); + assert_not_needs_parentheses!("a => class {} ", JsClassExpression); + + assert_needs_parentheses!("export default (class {})", 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 eb98a8fa1e1..c9a84b5eb16 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,9 +1,11 @@ use crate::prelude::*; +use crate::js::expressions::static_member_expression::member_chain_callee_needs_parens; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; use rome_formatter::{format_args, write}; -use rome_js_syntax::JsSyntaxToken; use rome_js_syntax::{ - JsAnyExpression, JsAnyLiteralExpression, JsComputedMemberAssignment, JsComputedMemberExpression, + JsAnyExpression, JsAnyLiteralExpression, JsComputedMemberAssignment, + JsComputedMemberExpression, JsSyntaxKind, JsSyntaxNode, JsSyntaxToken, }; use rome_rowan::{declare_node_union, SyntaxResult}; @@ -18,6 +20,10 @@ impl FormatNodeRule for FormatJsComputedMemberExpres ) -> FormatResult<()> { JsAnyComputedMemberLike::from(node.clone()).fmt(f) } + + fn needs_parentheses(&self, item: &JsComputedMemberExpression) -> bool { + item.needs_parentheses() + } } declare_node_union! { @@ -104,3 +110,46 @@ impl JsAnyComputedMemberLike { } } } + +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; + } + + member_chain_callee_needs_parens(self.clone().into(), parent) + } +} + +impl ExpressionNode for JsComputedMemberExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[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_needs_parentheses!("new (a?.b[c])()", 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 a3b37d9d783..aecf825431c 100644 --- a/crates/rome_js_formatter/src/js/expressions/conditional_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/conditional_expression.rs @@ -1,7 +1,11 @@ use crate::prelude::*; use crate::utils::JsAnyConditional; -use rome_js_syntax::JsConditionalExpression; +use crate::parentheses::{ + is_binary_like_left_or_right, is_conditional_test, is_spread, + update_or_lower_expression_needs_parentheses, ExpressionNode, NeedsParentheses, +}; +use rome_js_syntax::{JsAnyExpression, JsConditionalExpression, JsSyntaxKind, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsConditionalExpression; @@ -14,4 +18,106 @@ impl FormatNodeRule for FormatJsConditionalExpression { ) -> FormatResult<()> { JsAnyConditional::from(node.clone()).fmt(formatter) } + + fn needs_parentheses(&self, item: &JsConditionalExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsConditionalExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match parent.kind() { + JsSyntaxKind::JS_UNARY_EXPRESSION + | JsSyntaxKind::JS_AWAIT_EXPRESSION + | JsSyntaxKind::TS_TYPE_ASSERTION_EXPRESSION + | JsSyntaxKind::TS_AS_EXPRESSION => true, + + _ => { + is_conditional_test(self.syntax(), parent) + || update_or_lower_expression_needs_parentheses(self.syntax(), parent) + || is_binary_like_left_or_right(self.syntax(), parent) + || is_spread(self.syntax(), parent) + } + } + } +} + +impl ExpressionNode for JsConditionalExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::{JsConditionalExpression, SourceType}; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("((a ? b : c)).member", JsConditionalExpression); + assert_needs_parentheses!("(a ? b : c).member", JsConditionalExpression); + assert_needs_parentheses!("(a ? b : c)[member]", JsConditionalExpression); + assert_not_needs_parentheses!("object[(a ? b : c)]", JsConditionalExpression); + + assert_needs_parentheses!("new (true ? A : B)()", JsConditionalExpression); + assert_not_needs_parentheses!("new A(a ? b : c)", JsConditionalExpression); + + assert_needs_parentheses!("(true ? A : B)()", JsConditionalExpression); + assert_not_needs_parentheses!("call(a ? b : c)", JsConditionalExpression); + + assert_needs_parentheses!( + "(a ? b : c)`tagged template literal`", + JsConditionalExpression + ); + assert_not_needs_parentheses!("tag`content ${a ? b : c}`", JsConditionalExpression); + + assert_needs_parentheses!("-(a ? b : c)", JsConditionalExpression); + + assert_needs_parentheses!("[...(a ? b : c)]", JsConditionalExpression); + assert_needs_parentheses!("({...(a ? b : c)})", JsConditionalExpression); + assert_needs_parentheses!("call(...(a ? b : c))", JsConditionalExpression); + + assert_needs_parentheses!("a + (b ? c : d)", JsConditionalExpression); + assert_needs_parentheses!("(a ? b : c) + d", JsConditionalExpression); + + assert_needs_parentheses!("a instanceof (b ? c : d)", JsConditionalExpression); + assert_needs_parentheses!("(a ? b : c) instanceof d", JsConditionalExpression); + + assert_needs_parentheses!("a in (b ? c : d)", JsConditionalExpression); + assert_needs_parentheses!("(a ? b : c) in d", JsConditionalExpression); + + assert_needs_parentheses!("a && (b ? c : d)", JsConditionalExpression); + assert_needs_parentheses!("(a ? b : c) && d", JsConditionalExpression); + + assert_needs_parentheses!("await (a ? b : c)", JsConditionalExpression); + assert_needs_parentheses!( + "", + JsConditionalExpression, + SourceType::tsx() + ); + + assert_needs_parentheses!("(a ? b : c) as number;", JsConditionalExpression); + assert_needs_parentheses!("(a ? b : c);", JsConditionalExpression); + + assert_needs_parentheses!("class Test extends (a ? B : C) {}", JsConditionalExpression); + assert_needs_parentheses!("(a ? B : C)!", JsConditionalExpression); + + assert_not_needs_parentheses!("a ? b : c", JsConditionalExpression); + assert_not_needs_parentheses!("(a ? b : c)", JsConditionalExpression); + + assert_needs_parentheses!("({ a: 'test' } ? B : C)!", JsConditionalExpression); + assert_not_needs_parentheses!( + "console.log({ a: 'test' } ? B : C )", + JsConditionalExpression + ); + assert_not_needs_parentheses!("a ? b : c", JsConditionalExpression); + } } 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 5afa3eaa30a..87c649ab839 100644 --- a/crates/rome_js_formatter/src/js/expressions/function_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/function_expression.rs @@ -1,8 +1,12 @@ use crate::prelude::*; use crate::js::declarations::function_declaration::FormatFunction; +use crate::parentheses::{ + is_callee, is_first_in_statement, is_tag, ExpressionNode, FirstInStatementMode, + NeedsParentheses, +}; use rome_formatter::write; -use rome_js_syntax::JsFunctionExpression; +use rome_js_syntax::{JsAnyExpression, JsFunctionExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsFunctionExpression; @@ -11,4 +15,54 @@ impl FormatNodeRule for FormatJsFunctionExpression { fn fmt_fields(&self, node: &JsFunctionExpression, f: &mut JsFormatter) -> FormatResult<()> { write![f, [FormatFunction::from(node.clone())]] } + + fn needs_parentheses(&self, item: &JsFunctionExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsFunctionExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + is_callee(self.syntax(), parent) + || is_tag(self.syntax(), parent) + || is_first_in_statement( + self.clone().into(), + FirstInStatementMode::ExpressionOrExportDefault, + ) + } +} + +impl ExpressionNode for JsFunctionExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::JsFunctionExpression; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("console.log((function () {})())", JsFunctionExpression); + assert_needs_parentheses!("console.log(new (function () {})())", JsFunctionExpression); + + assert_needs_parentheses!("(function() {}).test", JsFunctionExpression); + assert_not_needs_parentheses!("a => function () {} ", JsFunctionExpression); + + assert_needs_parentheses!( + "console.log((function () {})`template`)", + JsFunctionExpression + ); + + assert_needs_parentheses!("export default (function () {})", JsFunctionExpression); + } } diff --git a/crates/rome_js_formatter/src/js/expressions/identifier_expression.rs b/crates/rome_js_formatter/src/js/expressions/identifier_expression.rs index cbdb4739916..c5cd76d023e 100644 --- a/crates/rome_js_formatter/src/js/expressions/identifier_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/identifier_expression.rs @@ -1,8 +1,9 @@ use crate::prelude::*; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; use rome_formatter::write; -use rome_js_syntax::JsIdentifierExpression; -use rome_js_syntax::JsIdentifierExpressionFields; +use rome_js_syntax::{JsAnyExpression, JsIdentifierExpressionFields}; +use rome_js_syntax::{JsIdentifierExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsIdentifierExpression; @@ -13,4 +14,31 @@ impl FormatNodeRule for FormatJsIdentifierExpression { write![f, [name.format()]] } + + fn needs_parentheses(&self, item: &JsIdentifierExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsIdentifierExpression { + #[inline(always)] + fn needs_parentheses(&self) -> bool { + false + } + #[inline(always)] + fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool { + false + } +} + +impl ExpressionNode for JsIdentifierExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } } diff --git a/crates/rome_js_formatter/src/js/expressions/import_call_expression.rs b/crates/rome_js_formatter/src/js/expressions/import_call_expression.rs index 0aea1188262..239748810cb 100644 --- a/crates/rome_js_formatter/src/js/expressions/import_call_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/import_call_expression.rs @@ -1,8 +1,9 @@ use crate::prelude::*; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; use rome_formatter::write; -use rome_js_syntax::JsImportCallExpression; -use rome_js_syntax::JsImportCallExpressionFields; +use rome_js_syntax::{JsAnyExpression, JsImportCallExpressionFields}; +use rome_js_syntax::{JsImportCallExpression, JsSyntaxKind, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsImportCallExpression; @@ -16,4 +17,26 @@ impl FormatNodeRule for FormatJsImportCallExpression { write![f, [import_token.format(), arguments.format(),]] } + + fn needs_parentheses(&self, item: &JsImportCallExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsImportCallExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + matches!(parent.kind(), JsSyntaxKind::JS_NEW_EXPRESSION) + } +} + +impl ExpressionNode for JsImportCallExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } } diff --git a/crates/rome_js_formatter/src/js/expressions/in_expression.rs b/crates/rome_js_formatter/src/js/expressions/in_expression.rs index 8aa7575ad88..381443ad447 100644 --- a/crates/rome_js_formatter/src/js/expressions/in_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/in_expression.rs @@ -1,7 +1,14 @@ use crate::prelude::*; -use crate::utils::{format_binary_like_expression, JsAnyBinaryLikeExpression}; +use crate::utils::{ + format_binary_like_expression, needs_binary_like_parentheses, JsAnyBinaryLikeExpression, +}; -use rome_js_syntax::JsInExpression; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; + +use rome_js_syntax::{ + JsAnyExpression, JsAnyStatement, JsForStatement, JsInExpression, JsSyntaxNode, +}; +use rome_rowan::AstNode; #[derive(Debug, Clone, Default)] pub struct FormatJsInExpression; @@ -14,3 +21,121 @@ impl FormatNodeRule for FormatJsInExpression { ) } } + +impl NeedsParentheses for JsInExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + if is_in_for_initializer(self) { + return true; + } + + needs_binary_like_parentheses(&JsAnyBinaryLikeExpression::from(self.clone()), parent) + } +} + +impl ExpressionNode for JsInExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +/// Add parentheses if the `in` is inside of a `for` initializer (see tests). +fn is_in_for_initializer(expression: &JsInExpression) -> bool { + let mut current = expression.clone().into_syntax(); + + while let Some(parent) = current.parent() { + current = match JsForStatement::try_cast(parent) { + Ok(for_statement) => { + return for_statement + .initializer() + .map(AstNode::into_syntax) + .as_ref() + == Some(¤t); + } + Err(parent) => { + if JsAnyStatement::can_cast(parent.kind()) { + // Don't cross statement boundaries + break; + } + + parent + } + } + } + + false +} + +#[cfg(test)] +mod tests { + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::{JsInExpression, SourceType}; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("class X extends (a in b) {}", JsInExpression); + + assert_needs_parentheses!("(a in b) as number", JsInExpression); + assert_needs_parentheses!("(a in b)", JsInExpression); + assert_needs_parentheses!("!(a in b)", JsInExpression); + assert_needs_parentheses!("await (a in b)", JsInExpression); + assert_needs_parentheses!("(a in b)!", JsInExpression); + + assert_needs_parentheses!("(a in b)()", JsInExpression); + assert_needs_parentheses!("(a in b)?.()", JsInExpression); + assert_needs_parentheses!("new (a in b)()", JsInExpression); + assert_needs_parentheses!("(a in b)`template`", JsInExpression); + assert_needs_parentheses!("[...(a in b)]", JsInExpression); + assert_needs_parentheses!("({...(a in b)})", JsInExpression); + assert_needs_parentheses!("", JsInExpression, SourceType::tsx()); + assert_needs_parentheses!( + "{...(a in b)}", + JsInExpression, + SourceType::tsx() + ); + + assert_needs_parentheses!("(a in b).member", JsInExpression); + assert_needs_parentheses!("(a in b)[member]", JsInExpression); + assert_not_needs_parentheses!("object[a in b]", JsInExpression); + + assert_needs_parentheses!("(a in b) + c", JsInExpression); + + assert_not_needs_parentheses!("a in b > c", JsInExpression); + assert_not_needs_parentheses!("a in b instanceof C", JsInExpression); + assert_not_needs_parentheses!("a in b in c", JsInExpression[0]); + assert_not_needs_parentheses!("a in b in c", JsInExpression[1]); + } + + #[test] + fn for_in_needs_parentheses() { + assert_needs_parentheses!("for (let a = (b in c);;);", JsInExpression); + assert_needs_parentheses!("for (a && (b in c);;);", JsInExpression); + assert_needs_parentheses!("for (a => (b in c);;);", JsInExpression); + assert_needs_parentheses!( + "function* g() { + for (yield (a in b);;); +}", + JsInExpression + ); + assert_needs_parentheses!( + "async function f() { + for (await (a in b);;); +}", + JsInExpression + ); + + assert_not_needs_parentheses!("for (;a in b;);", JsInExpression); + assert_not_needs_parentheses!("for (;;a in b);", JsInExpression); + assert_not_needs_parentheses!( + r#" + for (function () { a in b }();;); + "#, + JsInExpression + ); + } +} diff --git a/crates/rome_js_formatter/src/js/expressions/instanceof_expression.rs b/crates/rome_js_formatter/src/js/expressions/instanceof_expression.rs index a9e6e2d4e32..76dfdf1de58 100644 --- a/crates/rome_js_formatter/src/js/expressions/instanceof_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/instanceof_expression.rs @@ -1,7 +1,10 @@ use crate::prelude::*; -use crate::utils::{format_binary_like_expression, JsAnyBinaryLikeExpression}; +use crate::utils::{ + format_binary_like_expression, needs_binary_like_parentheses, JsAnyBinaryLikeExpression, +}; -use rome_js_syntax::JsInstanceofExpression; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use rome_js_syntax::{JsAnyExpression, JsInstanceofExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsInstanceofExpression; @@ -18,3 +21,69 @@ impl FormatNodeRule for FormatJsInstanceofExpression { ) } } + +impl NeedsParentheses for JsInstanceofExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + needs_binary_like_parentheses(&JsAnyBinaryLikeExpression::from(self.clone()), parent) + } +} + +impl ExpressionNode for JsInstanceofExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::{JsInstanceofExpression, SourceType}; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!( + "class X extends (a instanceof b) {}", + JsInstanceofExpression + ); + + assert_needs_parentheses!("(a instanceof B) as number", JsInstanceofExpression); + assert_needs_parentheses!("(a instanceof B)", JsInstanceofExpression); + assert_needs_parentheses!("!(a instanceof B)", JsInstanceofExpression); + assert_needs_parentheses!("await (a instanceof B)", JsInstanceofExpression); + assert_needs_parentheses!("(a instanceof B)!", JsInstanceofExpression); + + assert_needs_parentheses!("(a instanceof B)()", JsInstanceofExpression); + assert_needs_parentheses!("(a instanceof B)?.()", JsInstanceofExpression); + assert_needs_parentheses!("new (a instanceof B)()", JsInstanceofExpression); + assert_needs_parentheses!("(a instanceof B)`template`", JsInstanceofExpression); + assert_needs_parentheses!("[...(a instanceof B)]", JsInstanceofExpression); + assert_needs_parentheses!("({...(a instanceof B)})", JsInstanceofExpression); + assert_needs_parentheses!( + "", + JsInstanceofExpression, + SourceType::tsx() + ); + assert_needs_parentheses!( + "{...(a instanceof B)}", + JsInstanceofExpression, + SourceType::tsx() + ); + + assert_needs_parentheses!("(a instanceof B).member", JsInstanceofExpression); + assert_needs_parentheses!("(a instanceof B)[member]", JsInstanceofExpression); + assert_not_needs_parentheses!("object[a instanceof B]", JsInstanceofExpression); + + assert_needs_parentheses!("(a instanceof B) + c", JsInstanceofExpression); + + assert_not_needs_parentheses!("a instanceof B > c", JsInstanceofExpression); + assert_not_needs_parentheses!("a instanceof B in c", JsInstanceofExpression); + assert_not_needs_parentheses!("a instanceof B instanceof c", JsInstanceofExpression[0]); + assert_not_needs_parentheses!("a instanceof B instanceof c", JsInstanceofExpression[1]); + } +} diff --git a/crates/rome_js_formatter/src/js/expressions/logical_expression.rs b/crates/rome_js_formatter/src/js/expressions/logical_expression.rs index aec16bf932e..a3184d00653 100644 --- a/crates/rome_js_formatter/src/js/expressions/logical_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/logical_expression.rs @@ -1,7 +1,10 @@ use crate::prelude::*; -use crate::utils::{format_binary_like_expression, JsAnyBinaryLikeExpression}; +use crate::utils::{ + format_binary_like_expression, needs_binary_like_parentheses, JsAnyBinaryLikeExpression, +}; -use rome_js_syntax::JsLogicalExpression; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use rome_js_syntax::{JsAnyExpression, JsLogicalExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsLogicalExpression; @@ -18,3 +21,72 @@ impl FormatNodeRule for FormatJsLogicalExpression { ) } } + +impl NeedsParentheses for JsLogicalExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + if let Some(parent) = JsLogicalExpression::cast(parent.clone()) { + return parent.operator() != self.operator(); + } + + needs_binary_like_parentheses(&JsAnyBinaryLikeExpression::from(self.clone()), parent) + } +} + +impl ExpressionNode for JsLogicalExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::{JsLogicalExpression, SourceType}; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("class X extends (a && b) {}", JsLogicalExpression); + + assert_needs_parentheses!("(a && b) as number", JsLogicalExpression); + assert_needs_parentheses!("(a && b)", JsLogicalExpression); + assert_needs_parentheses!("!(a && b)", JsLogicalExpression); + assert_needs_parentheses!("await (a && b)", JsLogicalExpression); + assert_needs_parentheses!("(a && b)!", JsLogicalExpression); + + assert_needs_parentheses!("(a && b)()", JsLogicalExpression); + assert_needs_parentheses!("(a && b)?.()", JsLogicalExpression); + assert_needs_parentheses!("new (a && b)()", JsLogicalExpression); + assert_needs_parentheses!("(a && b)`template`", JsLogicalExpression); + assert_needs_parentheses!("[...(a && b)]", JsLogicalExpression); + assert_needs_parentheses!("({...(a && b)})", JsLogicalExpression); + assert_needs_parentheses!( + "", + JsLogicalExpression, + SourceType::tsx() + ); + assert_needs_parentheses!( + "{...(a && b)}", + JsLogicalExpression, + SourceType::tsx() + ); + + assert_needs_parentheses!("(a && b).member", JsLogicalExpression); + assert_needs_parentheses!("(a && b)[member]", JsLogicalExpression); + assert_not_needs_parentheses!("object[a && b]", JsLogicalExpression); + + assert_needs_parentheses!("(a && b) || c", JsLogicalExpression[1]); + assert_needs_parentheses!("(a && b) in c", JsLogicalExpression); + assert_needs_parentheses!("(a && b) instanceof c", JsLogicalExpression); + assert_needs_parentheses!("(a && b) + c", JsLogicalExpression); + + assert_not_needs_parentheses!("a && b && c", JsLogicalExpression[0]); + assert_not_needs_parentheses!("a && b && c", JsLogicalExpression[1]); + } +} diff --git a/crates/rome_js_formatter/src/js/expressions/new_expression.rs b/crates/rome_js_formatter/src/js/expressions/new_expression.rs index a4f323f3e04..08fbc328234 100644 --- a/crates/rome_js_formatter/src/js/expressions/new_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/new_expression.rs @@ -1,8 +1,9 @@ use crate::prelude::*; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; use rome_formatter::write; -use rome_js_syntax::JsNewExpressionFields; -use rome_js_syntax::{JsNewExpression, JsSyntaxKind}; +use rome_js_syntax::{JsAnyExpression, JsNewExpression, JsSyntaxKind}; +use rome_js_syntax::{JsNewExpressionFields, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsNewExpression; @@ -41,4 +42,26 @@ impl FormatNodeRule for FormatJsNewExpression { } } } + + fn needs_parentheses(&self, item: &JsNewExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsNewExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + matches!(parent.kind(), JsSyntaxKind::JS_EXTENDS_CLAUSE) + } +} + +impl ExpressionNode for JsNewExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } } diff --git a/crates/rome_js_formatter/src/js/expressions/null_literal_expression.rs b/crates/rome_js_formatter/src/js/expressions/null_literal_expression.rs index a28ebf2145c..b98f3037aa6 100644 --- a/crates/rome_js_formatter/src/js/expressions/null_literal_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/null_literal_expression.rs @@ -1,8 +1,9 @@ use crate::prelude::*; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; use rome_formatter::write; -use rome_js_syntax::JsNullLiteralExpression; -use rome_js_syntax::JsNullLiteralExpressionFields; +use rome_js_syntax::{JsAnyExpression, JsAnyLiteralExpression, JsNullLiteralExpressionFields}; +use rome_js_syntax::{JsNullLiteralExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsNullLiteralExpression; @@ -13,4 +14,31 @@ impl FormatNodeRule for FormatJsNullLiteralExpression { write![f, [value_token.format()]] } + + fn needs_parentheses(&self, item: &JsNullLiteralExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsNullLiteralExpression { + #[inline(always)] + fn needs_parentheses(&self) -> bool { + false + } + #[inline(always)] + fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool { + false + } +} + +impl ExpressionNode for JsNullLiteralExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::from(self.clone())) + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::from(self)) + } } 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 803c68f3162..2138c228a94 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 @@ -1,8 +1,9 @@ use crate::prelude::*; +use crate::parentheses::{is_member_object, ExpressionNode, NeedsParentheses}; use rome_formatter::write; -use rome_js_syntax::JsNumberLiteralExpressionFields; -use rome_js_syntax::{JsNumberLiteralExpression, JsStaticMemberExpression}; +use rome_js_syntax::{JsAnyExpression, JsAnyLiteralExpression, JsNumberLiteralExpression}; +use rome_js_syntax::{JsNumberLiteralExpressionFields, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsNumberLiteralExpression; @@ -16,19 +17,42 @@ impl FormatNodeRule for FormatJsNumberLiteralExpressi let JsNumberLiteralExpressionFields { value_token } = node.as_fields(); let value_token = value_token?; - if let Some(static_member_expression) = node.parent::() { - if static_member_expression.object()?.syntax() == node.syntax() { - return write!( - f, - [format_parenthesize( - Some(&value_token), - &value_token.format(), - Some(&value_token) - )] - ); - } - } - write![f, [value_token.format()]] } + + fn needs_parentheses(&self, item: &JsNumberLiteralExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsNumberLiteralExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + is_member_object(self.syntax(), parent) + } +} + +impl ExpressionNode for JsNumberLiteralExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::from(self.clone())) + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::from(self)) + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::JsNumberLiteralExpression; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("(5).test", JsNumberLiteralExpression); + assert_needs_parentheses!("(5)[test]", JsNumberLiteralExpression); + assert_not_needs_parentheses!("test[5]", 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 3913b1b4735..5746064a641 100644 --- a/crates/rome_js_formatter/src/js/expressions/object_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/object_expression.rs @@ -1,7 +1,10 @@ +use crate::parentheses::{ + is_first_in_statement, ExpressionNode, FirstInStatementMode, NeedsParentheses, +}; use crate::prelude::*; use crate::utils::JsObjectLike; use rome_formatter::write; -use rome_js_syntax::JsObjectExpression; +use rome_js_syntax::{JsAnyExpression, JsObjectExpression, JsSyntaxKind, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsObjectExpression; @@ -10,4 +13,56 @@ impl FormatNodeRule for FormatJsObjectExpression { fn fmt_fields(&self, node: &JsObjectExpression, f: &mut JsFormatter) -> FormatResult<()> { write!(f, [JsObjectLike::from(node.clone())]) } + + fn needs_parentheses(&self, item: &JsObjectExpression) -> bool { + item.needs_parentheses() + } +} + +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.clone().into(), + FirstInStatementMode::ExpressionStatementOrArrow, + ) + } +} + +impl ExpressionNode for JsObjectExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + use crate::assert_needs_parentheses; + use rome_js_syntax::JsObjectExpression; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("class A extends ({}) {}", JsObjectExpression); + assert_needs_parentheses!("({a: 5})", JsObjectExpression); + assert_needs_parentheses!("a => ({ a: 5})", JsObjectExpression); + + assert_needs_parentheses!("a => ({ a: 'test' })`template`", JsObjectExpression); + assert_needs_parentheses!("({ a: 'test' }).member", JsObjectExpression); + assert_needs_parentheses!("({ a: 'test' })[member]", JsObjectExpression); + assert_needs_parentheses!("({ a: 'test' })()", JsObjectExpression); + assert_needs_parentheses!("new ({ a: 'test' })()", JsObjectExpression); + assert_needs_parentheses!("({ a: 'test' }) as number", JsObjectExpression); + assert_needs_parentheses!("({ a: 'test' })!", JsObjectExpression); + assert_needs_parentheses!("({ a: 'test' }), b, c", JsObjectExpression); + assert_needs_parentheses!("({ a: 'test' }) + 5", JsObjectExpression); + assert_needs_parentheses!("({ a: 'test' }) && true", JsObjectExpression); + assert_needs_parentheses!("({ a: 'test' }) instanceof A", JsObjectExpression); + assert_needs_parentheses!("({ a: 'test' }) in B", JsObjectExpression); + } } 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 0d850b5a11b..ca0bf10e70c 100644 --- a/crates/rome_js_formatter/src/js/expressions/parenthesized_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/parenthesized_expression.rs @@ -1,15 +1,16 @@ use crate::prelude::*; use crate::utils::{ - binary_argument_needs_parens, is_simple_expression, resolve_expression, FormatPrecedence, + binary_argument_needs_parens, is_simple_expression, FormatPrecedence, JsAnyBinaryLikeLeftExpression, }; use rome_formatter::write; use crate::utils::JsAnyBinaryLikeExpression; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; use rome_js_syntax::{ - JsAnyExpression, JsAnyLiteralExpression, JsParenthesizedExpression, - JsParenthesizedExpressionFields, JsSyntaxKind, + JsAnyExpression, JsParenthesizedExpression, JsParenthesizedExpressionFields, JsSyntaxKind, + JsSyntaxNode, }; use rome_rowan::{AstNode, SyntaxResult}; @@ -28,26 +29,22 @@ impl FormatNodeRule for FormatJsParenthesizedExpressi r_paren_token, } = node.as_fields(); - let parenthesis_can_be_omitted = parenthesis_can_be_omitted(node)?; - let expression = expression?; - if is_simple_parenthesized_expression(node)? { - if parenthesis_can_be_omitted { - write!(f, [format_removed(&l_paren_token?)])?; - } else { - write![f, [l_paren_token.format()]]?; - }; + if is_expression_handling_parens(&expression) { + return write!( + f, + [ + format_removed(&l_paren_token?), + expression.format(), + format_removed(&r_paren_token?) + ] + ); + } - write![f, [expression.format()]]?; + let parenthesis_can_be_omitted = parenthesis_can_be_omitted(node, &expression)?; - if parenthesis_can_be_omitted { - write!(f, [format_removed(&r_paren_token?)])?; - } else { - write![f, [r_paren_token.format()]]?; - } - } else if parenthesis_can_be_omitted { - // we mimic the format delimited utility function + if parenthesis_can_be_omitted { write![ f, [ @@ -55,61 +52,29 @@ impl FormatNodeRule for FormatJsParenthesizedExpressi expression.format(), format_removed(&r_paren_token?), ] - ]?; + ] + } else if is_simple_parenthesized_expression(node)? { + write![ + f, + [ + l_paren_token.format(), + expression.format(), + r_paren_token.format() + ] + ] } else { - match expression { - // if the expression inside the parenthesis is a stringLiteralExpression, we should leave it as is rather than - // add extra soft_block_indent, for example: - // ```js - // ("escaped carriage return \ - // "); - // ``` - // if we add soft_block_indent, we will get: - // ```js - // ( - // "escaped carriage return \ - // " - // ); - // ``` - // which will not match prettier's formatting behavior, if we add this extra branch to handle this case, it become: - // ```js - // ("escaped carriage return \ - // "); - // ``` - // this is what we want - JsAnyExpression::JsAnyLiteralExpression( - JsAnyLiteralExpression::JsStringLiteralExpression(_), - ) => { - write![ - f, - [ - l_paren_token.format(), - expression.format(), - r_paren_token.format(), - ] - ] - } - JsAnyExpression::JsxTagExpression(expression) => { - write![ - f, - [ - format_removed(&l_paren_token?), - expression.format(), - format_removed(&r_paren_token?), - ] - ] - } - _ => write![ - f, - [ - format_delimited(&l_paren_token?, &expression.format(), &r_paren_token?,) - .soft_block_indent() - ] - ], - }?; + write![ + f, + [ + format_delimited(&l_paren_token?, &expression.format(), &r_paren_token?,) + .soft_block_indent() + ] + ] } + } - Ok(()) + fn needs_parentheses(&self, item: &JsParenthesizedExpression) -> bool { + item.needs_parentheses() } } @@ -131,10 +96,32 @@ fn is_simple_parenthesized_expression(node: &JsParenthesizedExpression) -> Synta Ok(true) } -fn parenthesis_can_be_omitted(node: &JsParenthesizedExpression) -> SyntaxResult { - let parent = node.syntax().parent(); +// Allow list of nodes that use the new `need_parens` formatting to determine if parentheses are necessary or not. +pub(crate) fn is_expression_handling_parens(expression: &JsAnyExpression) -> bool { + use JsAnyExpression::*; + + if let JsAnyExpression::JsParenthesizedExpression(inner) = expression { + if let Ok(inner) = inner.expression() { + is_expression_handling_parens(&inner) + } else { + false + } + } else { + !matches!( + expression, + JsInstanceofExpression(_) + | JsBinaryExpression(_) + | JsInExpression(_) + | JsLogicalExpression(_) + ) + } +} - let expression = resolve_expression(node.expression()?); +fn parenthesis_can_be_omitted( + node: &JsParenthesizedExpression, + expression: &JsAnyExpression, +) -> SyntaxResult { + let parent = node.syntax().parent(); if let Some(parent) = &parent { match parent.kind() { @@ -142,45 +129,14 @@ fn parenthesis_can_be_omitted(node: &JsParenthesizedExpression) -> SyntaxResult< JsSyntaxKind::JS_RETURN_STATEMENT | JsSyntaxKind::JS_THROW_STATEMENT => { return Ok(true) } - JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION => { - // Nested parentheses can always be removed - return Ok(true); - } + JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION => return Ok(true), _ => { // fall through } } - - if let JsAnyExpression::JsConditionalExpression(_) = &expression { - // The formatting of conditional expression makes sure to insert parens if needed - return Ok(true); - } } - // if expression is a StringLiteralExpression, we need to check it before precedence comparison, here is an example: - // ```js - // a[("test")] - // ``` - // if we use precedence comparison, we will get: - // parent_precedence should be `High` due to the parenthesized_expression's parent is ComputedMemberExpression, - // and node_precedence should be `Low` due to expression is StringLiteralExpression. `parent_precedence > node_precedence` will return false, - // the parenthesis will not be omitted. - // But the expected behavior is that the parenthesis will be omitted. The code above should be formatted as: - // ```js - // a["test"] - // ``` - // So we need to add extra branch to handle this case. - if matches!( - expression, - JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::JsStringLiteralExpression( - _ - )) - ) { - return Ok(!matches!( - parent.map(|p| p.kind()), - Some(JsSyntaxKind::JS_EXPRESSION_STATEMENT) - )); - } + let expression = expression.resolve(); let parent_precedence = FormatPrecedence::with_precedence_for_parenthesis(parent.as_ref()); @@ -189,19 +145,38 @@ fn parenthesis_can_be_omitted(node: &JsParenthesizedExpression) -> SyntaxResult< } if let Some(parent) = parent { - if let Some(binary_like) = JsAnyBinaryLikeExpression::cast(parent) { - let operator = binary_like.operator()?; - let is_right = expression.syntax() == binary_like.right()?.syntax(); - - if !binary_argument_needs_parens( - operator, - &JsAnyBinaryLikeLeftExpression::from(expression), - is_right, - )? { - return Ok(true); - } + if JsAnyBinaryLikeExpression::can_cast(parent.kind()) + && !binary_argument_needs_parens(&JsAnyBinaryLikeLeftExpression::from(expression)) + { + return Ok(true); } } Ok(false) } + +impl NeedsParentheses for JsParenthesizedExpression { + #[inline(always)] + fn needs_parentheses(&self) -> bool { + false + } + + #[inline(always)] + fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool { + false + } +} + +impl ExpressionNode for JsParenthesizedExpression { + fn resolve(&self) -> JsAnyExpression { + let inner = self.expression(); + + inner.unwrap_or_else(|_| self.clone().into()) + } + + fn into_resolved(self) -> JsAnyExpression { + let inner = self.expression(); + + inner.unwrap_or_else(|_| self.into()) + } +} 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 d3e264962cf..9a87865f119 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 @@ -1,8 +1,11 @@ use crate::prelude::*; use rome_formatter::write; -use rome_js_syntax::JsPostUpdateExpression; -use rome_js_syntax::JsPostUpdateExpressionFields; +use crate::parentheses::{ + unary_like_expression_needs_parentheses, ExpressionNode, NeedsParentheses, +}; +use rome_js_syntax::{JsAnyExpression, JsPostUpdateExpressionFields}; +use rome_js_syntax::{JsPostUpdateExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsPostUpdateExpression; @@ -14,6 +17,54 @@ impl FormatNodeRule for FormatJsPostUpdateExpression { operator_token, } = node.as_fields(); - write![f, [operand.format(), operator_token.format(),]] + write![f, [operand.format(), operator_token.format()]] + } + + fn needs_parentheses(&self, item: &JsPostUpdateExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsPostUpdateExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + unary_like_expression_needs_parentheses(self.syntax(), parent) + } +} + +impl ExpressionNode for JsPostUpdateExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::JsPostUpdateExpression; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("class A extends (A++) {}", JsPostUpdateExpression); + + assert_needs_parentheses!("(a++).b", JsPostUpdateExpression); + assert_needs_parentheses!("(a++)[b]", JsPostUpdateExpression); + assert_not_needs_parentheses!("a[b++]", JsPostUpdateExpression); + + assert_needs_parentheses!("(a++)`template`", JsPostUpdateExpression); + + assert_needs_parentheses!("(a++)()", JsPostUpdateExpression); + assert_needs_parentheses!("new (a++)()", JsPostUpdateExpression); + + assert_needs_parentheses!("(a++)!", JsPostUpdateExpression); + + assert_needs_parentheses!("(a++) ** 3", JsPostUpdateExpression); + assert_not_needs_parentheses!("(a++) + 3", 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 9b35e5d618b..34b2db5b703 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 @@ -1,8 +1,14 @@ +use crate::parentheses::{ + unary_like_expression_needs_parentheses, ExpressionNode, NeedsParentheses, +}; use crate::prelude::*; -use rome_formatter::write; -use rome_js_syntax::JsPreUpdateExpression; -use rome_js_syntax::JsPreUpdateExpressionFields; +use rome_formatter::write; +use rome_js_syntax::{ + JsAnyExpression, JsPreUpdateExpression, JsPreUpdateOperator, JsSyntaxNode, JsUnaryExpression, + JsUnaryOperator, +}; +use rome_js_syntax::{JsPreUpdateExpressionFields, JsSyntaxKind}; #[derive(Debug, Clone, Default)] pub struct FormatJsPreUpdateExpression; @@ -16,4 +22,66 @@ impl FormatNodeRule for FormatJsPreUpdateExpression { write![f, [operator_token.format(), operand.format(),]] } + + fn needs_parentheses(&self, item: &JsPreUpdateExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsPreUpdateExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match parent.kind() { + JsSyntaxKind::JS_UNARY_EXPRESSION => { + let unary = JsUnaryExpression::unwrap_cast(parent.clone()); + let parent_operator = unary.operator(); + let operator = self.operator(); + + (parent_operator == Ok(JsUnaryOperator::Plus) + && operator == Ok(JsPreUpdateOperator::Increment)) + || (parent_operator == Ok(JsUnaryOperator::Minus) + && operator == Ok(JsPreUpdateOperator::Decrement)) + } + _ => unary_like_expression_needs_parentheses(self.syntax(), parent), + } + } +} + +impl ExpressionNode for JsPreUpdateExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::JsPreUpdateExpression; + + #[test] + fn needs_parentheses() { + // valid, but should become +(++a) + assert_needs_parentheses!("+ ++a", JsPreUpdateExpression); + assert_needs_parentheses!("class A extends (++A) {}", JsPreUpdateExpression); + + assert_needs_parentheses!("(++a).b", JsPreUpdateExpression); + assert_needs_parentheses!("(++a)[b]", JsPreUpdateExpression); + assert_not_needs_parentheses!("a[++b]", JsPreUpdateExpression); + + assert_needs_parentheses!("(++a)`template`", JsPreUpdateExpression); + + assert_needs_parentheses!("(++a)()", JsPreUpdateExpression); + assert_needs_parentheses!("new (++a)()", JsPreUpdateExpression); + + assert_needs_parentheses!("(++a)!", JsPreUpdateExpression); + + assert_needs_parentheses!("(++a) ** 3", JsPreUpdateExpression); + assert_not_needs_parentheses!("(++a) + 3", JsPreUpdateExpression); + } } diff --git a/crates/rome_js_formatter/src/js/expressions/regex_literal_expression.rs b/crates/rome_js_formatter/src/js/expressions/regex_literal_expression.rs index 32b1e001ac1..a1174c4bd61 100644 --- a/crates/rome_js_formatter/src/js/expressions/regex_literal_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/regex_literal_expression.rs @@ -1,8 +1,9 @@ use crate::prelude::*; use rome_formatter::write; -use rome_js_syntax::JsRegexLiteralExpression; -use rome_js_syntax::JsRegexLiteralExpressionFields; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use rome_js_syntax::{JsAnyExpression, JsAnyLiteralExpression, JsRegexLiteralExpressionFields}; +use rome_js_syntax::{JsRegexLiteralExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsRegexLiteralExpression; @@ -40,4 +41,31 @@ impl FormatNodeRule for FormatJsRegexLiteralExpression write!(f, [format_replaced(&value_token, &sorted_regex_literal)]) } + + fn needs_parentheses(&self, item: &JsRegexLiteralExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsRegexLiteralExpression { + #[inline(always)] + fn needs_parentheses(&self) -> bool { + false + } + #[inline(always)] + fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool { + false + } +} + +impl ExpressionNode for JsRegexLiteralExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::from(self.clone())) + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::from(self)) + } } 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 b5ea618af78..aa0cf8dab80 100644 --- a/crates/rome_js_formatter/src/js/expressions/sequence_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/sequence_expression.rs @@ -1,8 +1,11 @@ use crate::prelude::*; -use rome_formatter::write; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use rome_formatter::{format_args, write}; use rome_js_syntax::JsSyntaxKind::{JS_PARENTHESIZED_EXPRESSION, JS_SEQUENCE_EXPRESSION}; -use rome_js_syntax::{JsSequenceExpression, JsSequenceExpressionFields, JsSyntaxKind}; +use rome_js_syntax::{ + JsAnyExpression, JsSequenceExpression, JsSequenceExpressionFields, JsSyntaxKind, JsSyntaxNode, +}; use rome_rowan::AstNode; #[derive(Debug, Clone, Default)] @@ -41,7 +44,7 @@ impl FormatNodeRule for FormatJsSequenceExpression { left.format(), comma_token.format(), line_suffix_boundary(), - soft_line_indent_or_space(&right.format()) + indent(&format_args![soft_line_break_or_space(), right.format()]) ] ); } @@ -65,4 +68,52 @@ impl FormatNodeRule for FormatJsSequenceExpression { write!(f, [group(&format_inner)]) } } + + fn needs_parentheses(&self, item: &JsSequenceExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsSequenceExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + !matches!( + parent.kind(), + JsSyntaxKind::JS_RETURN_STATEMENT | + // There's a precedence for writing `x++, y++` + JsSyntaxKind::JS_FOR_STATEMENT | + JsSyntaxKind::JS_EXPRESSION_STATEMENT | + JsSyntaxKind::JS_SEQUENCE_EXPRESSION | + // Handled as part of the arrow function formatting + JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION + ) + } +} + +impl ExpressionNode for JsSequenceExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::assert_not_needs_parentheses; + use rome_js_syntax::JsSequenceExpression; + + #[test] + fn needs_parentheses() { + assert_not_needs_parentheses!("function test() { return a, b }", JsSequenceExpression); + assert_not_needs_parentheses!("for (let i, x; i++, x++;) {}", JsSequenceExpression); + assert_not_needs_parentheses!("a, b;", JsSequenceExpression); + assert_not_needs_parentheses!("a, b, c", JsSequenceExpression[0]); + assert_not_needs_parentheses!("a, b, c", JsSequenceExpression[1]); + assert_not_needs_parentheses!("a => a, b", JsSequenceExpression); + } } 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 ea1fc40f45d..1268a07dd56 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,10 +1,12 @@ use crate::prelude::*; use crate::js::expressions::computed_member_expression::JsAnyComputedMemberLike; +use crate::parentheses::{resolve_parent, ExpressionNode, NeedsParentheses}; use rome_formatter::{format_args, write}; use rome_js_syntax::{ JsAnyAssignment, JsAnyAssignmentPattern, JsAnyExpression, JsAnyName, JsAssignmentExpression, - JsInitializerClause, JsStaticMemberAssignment, JsStaticMemberExpression, JsSyntaxToken, + JsInitializerClause, JsParenthesizedExpression, JsStaticMemberAssignment, + JsStaticMemberExpression, JsSyntaxKind, JsSyntaxNode, JsSyntaxToken, }; use rome_rowan::{declare_node_union, AstNode, SyntaxResult}; @@ -15,6 +17,10 @@ impl FormatNodeRule for FormatJsStaticMemberExpression fn fmt_fields(&self, node: &JsStaticMemberExpression, f: &mut JsFormatter) -> FormatResult<()> { JsAnyStaticMemberLike::from(node.clone()).fmt(f) } + + fn needs_parentheses(&self, item: &JsStaticMemberExpression) -> bool { + item.needs_parentheses() + } } #[derive(Debug, Copy, Clone)] @@ -79,8 +85,8 @@ impl JsAnyStaticMemberLike { } fn layout(&self) -> SyntaxResult { - let parent = self.syntax().parent(); - let object = self.object()?; + let parent = resolve_parent(self.syntax()); + let object = self.object()?.resolve(); let is_nested = match &parent { Some(parent) => { @@ -118,8 +124,9 @@ impl JsAnyStaticMemberLike { } let first_non_static_member_ancestor = self.syntax().ancestors().find(|parent| { - !JsAnyStaticMemberLike::can_cast(parent.kind()) + !(JsAnyStaticMemberLike::can_cast(parent.kind()) || JsAnyComputedMemberLike::can_cast(parent.kind()) + || JsParenthesizedExpression::can_cast(parent.kind())) }); let layout = match first_non_static_member_ancestor.and_then(JsAnyExpression::cast) { @@ -142,3 +149,69 @@ impl JsAnyStaticMemberLike { Ok(layout) } } + +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; + } + + member_chain_callee_needs_parens(self.clone().into(), parent) + } +} + +pub(crate) fn member_chain_callee_needs_parens( + node: JsAnyExpression, + parent: &JsSyntaxNode, +) -> bool { + use JsAnyExpression::*; + + 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(), + JsParenthesizedExpression(expression) => expression.expression().ok(), + _ => None, + }); + + object_chain.any(|object| matches!(object, JsCallExpression(_))) + } + _ => false, + } +} + +impl ExpressionNode for JsStaticMemberExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[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 5de68069a85..ffd6d6a8a5d 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 @@ -2,9 +2,10 @@ use crate::prelude::*; use crate::utils::{FormatLiteralStringToken, StringLiteralParentKind}; -use rome_js_syntax::JsStringLiteralExpression; -use rome_js_syntax::JsStringLiteralExpressionFields; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use rome_js_syntax::{JsAnyExpression, JsAnyLiteralExpression, JsStringLiteralExpressionFields}; use rome_js_syntax::{JsExpressionStatement, JsSyntaxKind}; +use rome_js_syntax::{JsStringLiteralExpression, JsSyntaxNode}; use rome_rowan::AstNode; #[derive(Debug, Clone, Default)] @@ -19,30 +20,89 @@ impl FormatNodeRule for FormatJsStringLiteralExpressi let JsStringLiteralExpressionFields { value_token } = node.as_fields(); let value_token = value_token?; - let formatted = FormatLiteralStringToken::new(&value_token, StringLiteralParentKind::Expression); - // Prevents that a string literal expression becomes a directive - let needs_parens = - if let Some(expression_statement) = node.parent::() { - expression_statement - .syntax() - .parent() - .map_or(false, |grand_parent| { - matches!( - grand_parent.kind(), - JsSyntaxKind::JS_STATEMENT_LIST | JsSyntaxKind::JS_MODULE_ITEM_LIST - ) - }) - } else { - false - }; - - if needs_parens { - format_parenthesize(Some(&value_token), &formatted, Some(&value_token)).fmt(f) + formatted.fmt(f) + } + + fn needs_parentheses(&self, item: &JsStringLiteralExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsStringLiteralExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + if let Some(expression_statement) = JsExpressionStatement::cast(parent.clone()) { + expression_statement + .syntax() + .parent() + .map_or(false, |grand_parent| { + matches!( + grand_parent.kind(), + JsSyntaxKind::JS_STATEMENT_LIST | JsSyntaxKind::JS_MODULE_ITEM_LIST + ) + }) } else { - formatted.fmt(f) + false } } } + +impl ExpressionNode for JsStringLiteralExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::from(self.clone())) + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + JsAnyExpression::JsAnyLiteralExpression(JsAnyLiteralExpression::from(self)) + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::{JsStringLiteralExpression, ModuleKind, SourceType}; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("{ 'test'; }", JsStringLiteralExpression); + assert_needs_parentheses!( + r#" + { + console.log(5); + 'test'; + } + "#, + JsStringLiteralExpression + ); + assert_needs_parentheses!( + r#" + function Test () { + ('test'); + } + "#, + JsStringLiteralExpression + ); + assert_needs_parentheses!( + r#" + class A { + static { + ('test'); + } + } + "#, + JsStringLiteralExpression + ); + assert_needs_parentheses!( + "('test');", + JsStringLiteralExpression, + SourceType::ts().with_module_kind(ModuleKind::Module) + ); + + assert_not_needs_parentheses!("console.log('a')", JsStringLiteralExpression); + } +} diff --git a/crates/rome_js_formatter/src/js/expressions/super_expression.rs b/crates/rome_js_formatter/src/js/expressions/super_expression.rs index 38c134a8efe..e03e6cd7907 100644 --- a/crates/rome_js_formatter/src/js/expressions/super_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/super_expression.rs @@ -1,8 +1,9 @@ use crate::prelude::*; use rome_formatter::write; -use rome_js_syntax::JsSuperExpression; -use rome_js_syntax::JsSuperExpressionFields; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use rome_js_syntax::{JsAnyExpression, JsSuperExpressionFields}; +use rome_js_syntax::{JsSuperExpression, JsSyntaxNode}; #[derive(Debug, Clone, Default)] pub struct FormatJsSuperExpression; @@ -13,4 +14,31 @@ impl FormatNodeRule for FormatJsSuperExpression { write![f, [super_token.format()]] } + + fn needs_parentheses(&self, item: &JsSuperExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsSuperExpression { + #[inline(always)] + fn needs_parentheses(&self) -> bool { + false + } + #[inline(always)] + fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool { + false + } +} + +impl ExpressionNode for JsSuperExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } } diff --git a/crates/rome_js_formatter/src/js/expressions/template.rs b/crates/rome_js_formatter/src/js/expressions/template.rs index c268e424f5f..20c1f8cb9b9 100644 --- a/crates/rome_js_formatter/src/js/expressions/template.rs +++ b/crates/rome_js_formatter/src/js/expressions/template.rs @@ -1,9 +1,10 @@ use crate::prelude::*; use rome_formatter::write; -use rome_js_syntax::{ - JsAnyExpression, JsSyntaxToken, JsTemplate, TsTemplateLiteralType, TsTypeArguments, -}; +use crate::js::expressions::static_member_expression::member_chain_callee_needs_parens; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use rome_js_syntax::{JsAnyExpression, JsSyntaxNode, JsTemplate, TsTemplateLiteralType}; +use rome_js_syntax::{JsSyntaxToken, TsTypeArguments}; use rome_rowan::{declare_node_union, SyntaxResult}; #[derive(Debug, Clone, Default)] @@ -13,6 +14,10 @@ impl FormatNodeRule for FormatJsTemplate { fn fmt_fields(&self, node: &JsTemplate, f: &mut JsFormatter) -> FormatResult<()> { JsAnyTemplate::from(node.clone()).fmt(f) } + + fn needs_parentheses(&self, item: &JsTemplate) -> bool { + item.needs_parentheses() + } } declare_node_union! { @@ -77,3 +82,26 @@ impl JsAnyTemplate { } } } + +/// `TemplateLiteral`'s are `PrimaryExpression's that never need parentheses. +impl NeedsParentheses for JsTemplate { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + if self.tag().is_some() { + member_chain_callee_needs_parens(self.clone().into(), parent) + } else { + false + } + } +} + +impl ExpressionNode for JsTemplate { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} diff --git a/crates/rome_js_formatter/src/js/expressions/this_expression.rs b/crates/rome_js_formatter/src/js/expressions/this_expression.rs index feb5c62c1a1..ac6f92c59ca 100644 --- a/crates/rome_js_formatter/src/js/expressions/this_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/this_expression.rs @@ -1,8 +1,9 @@ use crate::prelude::*; use rome_formatter::write; -use rome_js_syntax::JsThisExpression; -use rome_js_syntax::JsThisExpressionFields; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use rome_js_syntax::{JsAnyExpression, JsThisExpressionFields}; +use rome_js_syntax::{JsSyntaxNode, JsThisExpression}; #[derive(Debug, Clone, Default)] pub struct FormatJsThisExpression; @@ -13,4 +14,30 @@ impl FormatNodeRule for FormatJsThisExpression { write![f, [this_token.format()]] } + + fn needs_parentheses(&self, item: &JsThisExpression) -> bool { + item.needs_parentheses() + } +} +impl NeedsParentheses for JsThisExpression { + #[inline(always)] + fn needs_parentheses(&self) -> bool { + false + } + #[inline(always)] + fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool { + false + } +} + +impl ExpressionNode for JsThisExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } } 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 3e93f3e02c3..d2673b5bbfa 100644 --- a/crates/rome_js_formatter/src/js/expressions/unary_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/unary_expression.rs @@ -1,9 +1,12 @@ use crate::prelude::*; -use crate::utils::is_simple_expression; use rome_formatter::write; -use rome_js_syntax::JsPreUpdateOperator; -use rome_js_syntax::{JsAnyExpression, JsUnaryExpression}; +use crate::parentheses::{ + unary_like_expression_needs_parentheses, ExpressionNode, NeedsParentheses, +}; + +use rome_js_syntax::{JsAnyExpression, JsSyntaxNode}; +use rome_js_syntax::{JsSyntaxKind, JsUnaryExpression}; use rome_js_syntax::{JsUnaryExpressionFields, JsUnaryOperator}; #[derive(Debug, Clone, Default)] @@ -20,55 +23,75 @@ impl FormatNodeRule for FormatJsUnaryExpression { let operator_token = operator_token?; let argument = argument?; - // Insert a space between the operator and argument if its a keyword + write!(f, [operator_token.format()])?; + let is_keyword_operator = matches!( operation, JsUnaryOperator::Delete | JsUnaryOperator::Void | JsUnaryOperator::Typeof ); if is_keyword_operator { - return write![f, [operator_token.format(), space(), argument.format(),]]; + write!(f, [space()])?; } - // Parenthesize the inner expression if it's a binary or pre-update - // operation with an ambiguous operator (+ and ++ or - and --) - let is_ambiguous_expression = match &argument { - JsAnyExpression::JsUnaryExpression(expr) => { - let inner_op = expr.operator()?; - matches!( - (operation, inner_op), - (JsUnaryOperator::Plus, JsUnaryOperator::Plus) - | (JsUnaryOperator::Minus, JsUnaryOperator::Minus) - ) - } - JsAnyExpression::JsPreUpdateExpression(expr) => { - let inner_op = expr.operator()?; - matches!( - (operation, inner_op), - (JsUnaryOperator::Plus, JsPreUpdateOperator::Increment) - | (JsUnaryOperator::Minus, JsPreUpdateOperator::Decrement) - ) - } - _ => false, - }; - - if is_ambiguous_expression { - operator_token.format().fmt(f)?; + write![f, [argument.format(),]] + } - let first_token = argument.syntax().first_token(); - let last_token = argument.syntax().last_token(); - let format_argument = argument.format(); + fn needs_parentheses(&self, item: &JsUnaryExpression) -> bool { + item.needs_parentheses() + } +} - let parenthesize = - format_parenthesize(first_token.as_ref(), &format_argument, last_token.as_ref()); +impl NeedsParentheses for JsUnaryExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match parent.kind() { + JsSyntaxKind::JS_UNARY_EXPRESSION => { + let parent_unary = JsUnaryExpression::unwrap_cast(parent.clone()); + let parent_operator = parent_unary.operator(); + let operator = self.operator(); - if is_simple_expression(&argument)? { - parenthesize.fmt(f) - } else { - parenthesize.grouped_with_soft_block_indent().fmt(f) + matches!(operator, Ok(JsUnaryOperator::Plus | JsUnaryOperator::Minus)) + && parent_operator == operator } - } else { - write![f, [operator_token.format(), argument.format(),]] + _ => unary_like_expression_needs_parentheses(self.syntax(), parent), } } } + +impl ExpressionNode for JsUnaryExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::JsUnaryExpression; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("class A extends (!B) {}", JsUnaryExpression); + + assert_needs_parentheses!("(+a).b", JsUnaryExpression); + assert_needs_parentheses!("(+a)[b]", JsUnaryExpression); + assert_not_needs_parentheses!("a[+b]", JsUnaryExpression); + + assert_needs_parentheses!("(+a)`template`", JsUnaryExpression); + + assert_needs_parentheses!("(+a)()", JsUnaryExpression); + assert_needs_parentheses!("new (+a)()", JsUnaryExpression); + + assert_needs_parentheses!("(+a)!", JsUnaryExpression); + + assert_needs_parentheses!("(+a) ** 3", JsUnaryExpression); + assert_not_needs_parentheses!("(+a) + 3", 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 a31d8d1c3fc..ba12871d163 100644 --- a/crates/rome_js_formatter/src/js/expressions/yield_expression.rs +++ b/crates/rome_js_formatter/src/js/expressions/yield_expression.rs @@ -1,8 +1,10 @@ use crate::prelude::*; use rome_formatter::write; -use rome_js_syntax::JsYieldExpression; -use rome_js_syntax::JsYieldExpressionFields; +use crate::js::expressions::await_expression::await_or_yield_needs_parens; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use rome_js_syntax::{JsAnyExpression, JsSyntaxKind, JsYieldExpressionFields}; +use rome_js_syntax::{JsSyntaxNode, JsYieldExpression}; #[derive(Debug, Clone, Default)] pub struct FormatJsYieldExpression; @@ -16,4 +18,77 @@ impl FormatNodeRule for FormatJsYieldExpression { write![f, [yield_token.format(), argument.format()]] } + + fn needs_parentheses(&self, item: &JsYieldExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsYieldExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + matches!(parent.kind(), JsSyntaxKind::JS_AWAIT_EXPRESSION) + || await_or_yield_needs_parens(parent, self.syntax()) + } +} + +impl ExpressionNode for JsYieldExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::JsYieldExpression; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!( + "function* test() { (yield a)`template` }", + JsYieldExpression + ); + assert_needs_parentheses!("function* test() { +(yield a) }", JsYieldExpression); + + assert_needs_parentheses!("function* test() { (yield a).b }", JsYieldExpression); + assert_needs_parentheses!("function* test() { (yield a)[b] }", JsYieldExpression); + assert_not_needs_parentheses!("function* test() { a[yield b] }", JsYieldExpression); + + assert_needs_parentheses!("function* test() { (yield a)() }", JsYieldExpression); + assert_needs_parentheses!("function* test() { new (yield a)() }", JsYieldExpression); + + assert_needs_parentheses!("function* test() { (yield a) && b }", JsYieldExpression); + assert_needs_parentheses!("function* test() { (yield a) + b }", JsYieldExpression); + assert_needs_parentheses!( + "function* test() { (yield a) instanceof b }", + JsYieldExpression + ); + assert_needs_parentheses!("function* test() { (yield a) in b }", JsYieldExpression); + + assert_needs_parentheses!("function* test() { [...(yield a)] }", JsYieldExpression); + assert_needs_parentheses!("function* test() { ({...(yield b)}) }", JsYieldExpression); + assert_needs_parentheses!("function* test() { call(...(yield b)) }", JsYieldExpression); + + assert_needs_parentheses!( + "function* test() { class A extends (yield b) {} }", + JsYieldExpression + ); + + assert_needs_parentheses!( + "function* test() { (yield b) as number }", + JsYieldExpression + ); + assert_needs_parentheses!("function* test() { (yield b)! }", JsYieldExpression); + + assert_needs_parentheses!("function* test() { (yield b) ? b : c }", JsYieldExpression); + assert_not_needs_parentheses!("function* test() { a ? yield b : c }", JsYieldExpression); + assert_not_needs_parentheses!("function* test() { a ? b : yield c }", JsYieldExpression); + } } diff --git a/crates/rome_js_formatter/src/js/module/import_meta.rs b/crates/rome_js_formatter/src/js/module/import_meta.rs index d40ad8521f2..eec52142bb6 100644 --- a/crates/rome_js_formatter/src/js/module/import_meta.rs +++ b/crates/rome_js_formatter/src/js/module/import_meta.rs @@ -1,8 +1,9 @@ use crate::prelude::*; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; use rome_formatter::write; -use rome_js_syntax::ImportMeta; -use rome_js_syntax::ImportMetaFields; +use rome_js_syntax::{ImportMeta, JsSyntaxNode}; +use rome_js_syntax::{ImportMetaFields, JsAnyExpression}; #[derive(Debug, Clone, Default)] pub struct FormatImportMeta; @@ -27,4 +28,30 @@ impl FormatNodeRule for FormatImportMeta { ] ] } + + fn needs_parentheses(&self, item: &ImportMeta) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for ImportMeta { + fn needs_parentheses(&self) -> bool { + false + } + + fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool { + false + } +} + +impl ExpressionNode for ImportMeta { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } } diff --git a/crates/rome_js_formatter/src/js/statements/return_statement.rs b/crates/rome_js_formatter/src/js/statements/return_statement.rs index 322a3ad893a..1eed0b8e7d0 100644 --- a/crates/rome_js_formatter/src/js/statements/return_statement.rs +++ b/crates/rome_js_formatter/src/js/statements/return_statement.rs @@ -1,8 +1,9 @@ use crate::prelude::*; -use crate::utils::{FormatWithSemicolon, JsAnyBinaryLikeExpression}; +use crate::utils::{FormatWithSemicolon, JsAnyBinaryLikeExpression, JsAnyBinaryLikeLeftExpression}; use rome_formatter::{format_args, write}; +use crate::parentheses::get_expression_left_side; use rome_js_syntax::{ JsAnyExpression, JsReturnStatement, JsReturnStatementFields, JsSequenceExpression, }; @@ -79,8 +80,11 @@ impl Format for FormatReturnOrThrowArgument<'_> { } /// Tests if the passed in argument has any leading comments. This is the case if -/// * the argument's first token has a leading comment -/// * the argument is a parenthesized expression and the inner expression has a leading comment. +/// * the argument has any leading comment +/// * the argument's left side has any leading comment (see [get_expression_left_side]). +/// +/// Traversing the left nodes is necessary in case the first node is parenthesized because +/// parentheses will be removed (and be re-added by the return statement, but only if the argument breaks) fn has_argument_leading_comments(argument: &JsAnyExpression) -> SyntaxResult { if matches!(argument, JsAnyExpression::JsxTagExpression(_)) { // JSX formatting takes care of adding parens @@ -91,11 +95,14 @@ fn has_argument_leading_comments(argument: &JsAnyExpression) -> SyntaxResult { - has_argument_leading_comments(&inner.expression()?)? + let result = match get_expression_left_side(argument) { + Some(JsAnyBinaryLikeLeftExpression::JsAnyExpression(expression)) => { + has_argument_leading_comments(&expression)? } - _ => false, + Some(JsAnyBinaryLikeLeftExpression::JsPrivateName(name)) => { + name.syntax().has_leading_comments() + } + None => false, }; Ok(result) diff --git a/crates/rome_js_formatter/src/js/unknown/unknown_expression.rs b/crates/rome_js_formatter/src/js/unknown/unknown_expression.rs index 9fa15eb181b..c00eaedf3bc 100644 --- a/crates/rome_js_formatter/src/js/unknown/unknown_expression.rs +++ b/crates/rome_js_formatter/src/js/unknown/unknown_expression.rs @@ -1,6 +1,9 @@ use crate::prelude::*; -use rome_js_syntax::JsUnknownExpression; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; +use rome_js_syntax::{ + JsAnyExpression, JsParenthesizedExpression, JsSyntaxNode, JsUnknownExpression, +}; use rome_rowan::AstNode; #[derive(Debug, Clone, Default)] @@ -14,4 +17,33 @@ impl FormatNodeRule for FormatJsUnknownExpression { ) -> FormatResult<()> { format_unknown_node(node.syntax()).fmt(formatter) } + + fn needs_parentheses(&self, item: &JsUnknownExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for JsUnknownExpression { + fn needs_parentheses(&self) -> bool { + // Keep parens if it is parenthesized. + self.syntax().parent().map_or(false, |parent| { + JsParenthesizedExpression::can_cast(parent.kind()) + }) + } + + fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool { + self.needs_parentheses() + } +} + +impl ExpressionNode for JsUnknownExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } } 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..a281168cb01 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, ExpressionNode, NeedsParentheses}; use crate::prelude::*; use crate::utils::jsx::{get_wrap_state, WrapState}; use rome_formatter::{format_args, write}; -use rome_js_syntax::JsxTagExpression; +use rome_js_syntax::{ + JsAnyExpression, 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,52 @@ 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(ExpressionNode::into_resolved_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), + } + } +} + +impl ExpressionNode for JsxTagExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } } diff --git a/crates/rome_js_formatter/src/lib.rs b/crates/rome_js_formatter/src/lib.rs index 5a223a596f7..50df690fc74 100644 --- a/crates/rome_js_formatter/src/lib.rs +++ b/crates/rome_js_formatter/src/lib.rs @@ -258,7 +258,7 @@ use rome_rowan::AstNode; use rome_rowan::SyntaxResult; use rome_rowan::TextRange; -use crate::builders::format_suppressed_node; +use crate::builders::{format_parenthesize, format_suppressed_node}; use crate::context::JsFormatContext; use crate::cst::FormatJsSyntaxNode; use std::iter::FusedIterator; @@ -412,16 +412,29 @@ where let syntax = node.syntax(); if f.context().comments().is_suppressed(syntax) { - write!(f, [format_suppressed_node(syntax)])?; + write!(f, [format_suppressed_node(syntax)]) + } else if self.needs_parentheses(node) { + write!( + f, + [format_parenthesize( + node.syntax().first_token().as_ref(), + &format_once(|f| self.fmt_fields(node, f)), + node.syntax().last_token().as_ref(), + )] + ) } else { - self.fmt_fields(node, f)?; - }; - - Ok(()) + self.fmt_fields(node, f) + } } /// Formats the node's fields. fn fmt_fields(&self, item: &N, f: &mut JsFormatter) -> FormatResult<()>; + + /// Returns whether the node requires parens. + fn needs_parentheses(&self, item: &N) -> bool { + let _ = item; + false + } } /// Format implementation specific to JavaScript tokens. @@ -687,6 +700,7 @@ mod check_reformat; mod generated; pub(crate) mod builders; pub mod context; +mod parentheses; pub(crate) mod separated; #[cfg(test)] @@ -704,7 +718,7 @@ mod test { test.expect(t => { t.true(a); }, false); - "#; +"#; let syntax = SourceType::tsx(); let tree = parse(src, 0, syntax); let result = format_node(JsFormatContext::default(), &tree.syntax()) diff --git a/crates/rome_js_formatter/src/parentheses.rs b/crates/rome_js_formatter/src/parentheses.rs new file mode 100644 index 00000000000..22a6b60aa83 --- /dev/null +++ b/crates/rome_js_formatter/src/parentheses.rs @@ -0,0 +1,998 @@ +//! JavaScript supports parenthesizing expressions, assignments, and TypeScript types. +//! Parenthesizing an expression can be desired to change the precedence of an expression or to ease +//! readability. +//! +//! Rome is opinionated about which parentheses to keep or where to insert parentheses. +//! It removes parentheses that aren't necessary to keep the same semantics as in the source document, nor aren't improving readability. +//! Rome also inserts parentheses around nodes where we believe that they're helpful to improve readability. +//! +//! The [NeedsParentheses] trait forms the foundation of Rome's parentheses formatting and is implemented +//! by all nodes supporting parentheses (expressions, assignments, and types). The trait's main method +//! is the [`needs_parentheses`](NeedsParentheses::needs_parentheses) +//! method that implements the rules when a node requires parentheses. +//! A node requires parentheses to: +//! * improve readability: `a << b << 3` is harder to read than `(a << b) << 3` +//! * form valid syntax: `class A extends 3 + 3 {}` isn't valid, but `class A extends (3 + 3) {}` is +//! * preserve operator precedence: `(a + 3) * 4` has a different meaning than `a + 3 * 4` +//! +//! The challenge of formatting parenthesized nodes is that a tree with parentheses and a tree without +//! parentheses (that have the same semantics) must result in the same output. For example, +//! formatting `(a + 3) + 5` must yield the same formatted output as `a + 3 + 5` or `a + (3 + 5)` or even +//! `(((a + 3) + 5))` even though all these trees differ by the number of parenthesized expressions. +//! +//! There are two measures taken by Rome to ensure formatting is stable regardless of the number of parenthesized nodes in a tree: +//! +//! ## Removing and adding of parentheses +//! The [FormatNodeRule](rome_js_formatter::FormatNodeRule) always inserts parentheses around a node if the rules `needs_parentheses` method +//! returns `true`. This by itself would result in the formatter adding an extra pair of parentheses with every format pass for nodes where parentheses are necessary. +//! This is why the [rome_js_formatter::FormatJsParenthesizedExpression] rule always removes the parentheses and relies on the +//! [FormatNodeRule](rome_js_formatter::FormatNodeRule) to add the parentheses again when necessary. +//! +//! ## Testing for a a child or parent node. +//! +//! There are many places where a formatting rule applies different formatting depending on the type of a +//! child node or parent node. The decision taken by these rules shouldn't differ just because a node happens to be parenthesized +//! because doing so would yield different results if the formatter removes the parentheses in the first pass. +//! +//! The [NeedsParentheses] trait offers a [`resolve_parent`](NeedsParentheses::resolve_parent] method +//! that returns the first parent of a node that isn't parenthesized. +//! For example, calling [JsSyntaxNode::parent] on the `a` identifier in `(a).b` returns the [JsParenthesizedExpression](rome_js_syntax::JsParenthesizedExpression) +//! but calling [`resolve_parent`](NeedsParentheses::resolve_parent] returns the [JsStaticMemberExpression](rome_js_syntax::JsStaticMemberExpression). +//! +//! This module further offers node specific traits like [ExpressionNode] that implement additional methods to resolve a node. +//! Calling [`resolve`](ExpressionNode::resolve) returns the node itself if it isn't a [JsParenthesizedExpression](rome_js_syntax::JsParenthesizedExpression) +//! or traverses down the parenthesized expression and returns the first non [JsParenthesizedExpression](rome_js_syntax::JsParenthesizedExpression) node. +//! For example, calling resolve on `a` returns `a` but calling resolve on `((a))` also returns `a`. + +use crate::utils::{JsAnyBinaryLikeExpression, JsAnyBinaryLikeLeftExpression}; + +use rome_js_syntax::{ + JsAnyExpression, JsAnyFunctionBody, JsAnyLiteralExpression, JsArrowFunctionExpression, + JsAssignmentExpression, JsBinaryExpression, JsBinaryOperator, JsComputedMemberAssignment, + JsComputedMemberExpression, JsConditionalExpression, JsLanguage, JsSequenceExpression, + JsSyntaxKind, JsSyntaxNode, +}; +use rome_rowan::AstNode; + +/// Node that may be parenthesized to ensure it forms valid syntax or to improve readability +pub trait NeedsParentheses: AstNode { + fn needs_parentheses(&self) -> bool { + self.resolve_parent() + .map_or(false, |parent| self.needs_parentheses_with_parent(&parent)) + } + + fn resolve_parent(&self) -> Option { + resolve_parent(self.syntax()) + } + + /// Returns `true` if this node requires parentheses to form valid syntax or improve readability. + /// + /// Returns `false` if the parentheses can be omitted safely without changing semantics. + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool; +} + +/// Trait implemented by all JavaScript expressions. +pub trait ExpressionNode: NeedsParentheses { + /// Resolves an expression to the first non parenthesized expression. + fn resolve(&self) -> JsAnyExpression; + + /// Consumes `self` and returns the first expression that isn't a parenthesized expression. + fn into_resolved(self) -> JsAnyExpression; + + /// Resolves an expression to the first non parenthesized expression and returns its [JsSyntaxNode]. + fn resolve_syntax(&self) -> JsSyntaxNode { + self.resolve().into_syntax() + } + + /// Consumes `self` and returns the [JsSyntaxNode] of the first expression that isn't a parenthesized expression. + fn into_resolved_syntax(self) -> JsSyntaxNode { + self.into_resolved().into_syntax() + } +} + +/// Resolves to the first parent that isn't a parenthesized expression, assignment, or type. +pub(crate) fn resolve_parent(node: &JsSyntaxNode) -> Option { + let mut current = node.parent(); + + while let Some(parent) = current { + if matches!( + parent.kind(), + JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION + | JsSyntaxKind::JS_PARENTHESIZED_ASSIGNMENT + | JsSyntaxKind::TS_PARENTHESIZED_TYPE + ) { + current = parent.parent(); + } else { + return Some(parent); + } + } + + None +} + +impl NeedsParentheses for JsAnyLiteralExpression { + #[inline] + fn needs_parentheses(&self) -> bool { + match self { + JsAnyLiteralExpression::JsBigIntLiteralExpression(big_int) => { + big_int.needs_parentheses() + } + JsAnyLiteralExpression::JsBooleanLiteralExpression(boolean) => { + boolean.needs_parentheses() + } + JsAnyLiteralExpression::JsNullLiteralExpression(null_literal) => { + null_literal.needs_parentheses() + } + JsAnyLiteralExpression::JsNumberLiteralExpression(number_literal) => { + number_literal.needs_parentheses() + } + JsAnyLiteralExpression::JsRegexLiteralExpression(regex) => regex.needs_parentheses(), + JsAnyLiteralExpression::JsStringLiteralExpression(string) => string.needs_parentheses(), + } + } + + #[inline] + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match self { + JsAnyLiteralExpression::JsBigIntLiteralExpression(big_int) => { + big_int.needs_parentheses_with_parent(parent) + } + JsAnyLiteralExpression::JsBooleanLiteralExpression(boolean) => { + boolean.needs_parentheses_with_parent(parent) + } + JsAnyLiteralExpression::JsNullLiteralExpression(null_literal) => { + null_literal.needs_parentheses_with_parent(parent) + } + JsAnyLiteralExpression::JsNumberLiteralExpression(number_literal) => { + number_literal.needs_parentheses_with_parent(parent) + } + JsAnyLiteralExpression::JsRegexLiteralExpression(regex) => { + regex.needs_parentheses_with_parent(parent) + } + JsAnyLiteralExpression::JsStringLiteralExpression(string) => { + string.needs_parentheses_with_parent(parent) + } + } + } +} + +impl ExpressionNode for JsAnyLiteralExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + match self { + JsAnyLiteralExpression::JsBigIntLiteralExpression(big_int) => big_int.resolve(), + JsAnyLiteralExpression::JsBooleanLiteralExpression(boolean) => boolean.resolve(), + JsAnyLiteralExpression::JsNullLiteralExpression(null_literal) => null_literal.resolve(), + JsAnyLiteralExpression::JsNumberLiteralExpression(number_literal) => { + number_literal.resolve() + } + JsAnyLiteralExpression::JsRegexLiteralExpression(regex) => regex.resolve(), + JsAnyLiteralExpression::JsStringLiteralExpression(string) => string.resolve(), + } + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + match self { + JsAnyLiteralExpression::JsBigIntLiteralExpression(big_int) => big_int.into_resolved(), + JsAnyLiteralExpression::JsBooleanLiteralExpression(boolean) => boolean.into_resolved(), + JsAnyLiteralExpression::JsNullLiteralExpression(null_literal) => { + null_literal.into_resolved() + } + JsAnyLiteralExpression::JsNumberLiteralExpression(number_literal) => { + number_literal.into_resolved() + } + JsAnyLiteralExpression::JsRegexLiteralExpression(regex) => regex.into_resolved(), + JsAnyLiteralExpression::JsStringLiteralExpression(string) => string.into_resolved(), + } + } +} + +impl NeedsParentheses for JsAnyExpression { + fn needs_parentheses(&self) -> bool { + match self { + JsAnyExpression::ImportMeta(meta) => meta.needs_parentheses(), + JsAnyExpression::JsAnyLiteralExpression(literal) => literal.needs_parentheses(), + JsAnyExpression::JsArrayExpression(array) => array.needs_parentheses(), + JsAnyExpression::JsArrowFunctionExpression(arrow) => arrow.needs_parentheses(), + JsAnyExpression::JsAssignmentExpression(assignment) => assignment.needs_parentheses(), + JsAnyExpression::JsAwaitExpression(await_expression) => { + await_expression.needs_parentheses() + } + JsAnyExpression::JsBinaryExpression(binary) => binary.needs_parentheses(), + JsAnyExpression::JsCallExpression(call) => call.needs_parentheses(), + JsAnyExpression::JsClassExpression(class) => class.needs_parentheses(), + JsAnyExpression::JsComputedMemberExpression(member) => member.needs_parentheses(), + JsAnyExpression::JsConditionalExpression(conditional) => { + conditional.needs_parentheses() + } + JsAnyExpression::JsFunctionExpression(function) => function.needs_parentheses(), + JsAnyExpression::JsIdentifierExpression(identifier) => identifier.needs_parentheses(), + JsAnyExpression::JsImportCallExpression(import_call) => import_call.needs_parentheses(), + JsAnyExpression::JsInExpression(in_expression) => in_expression.needs_parentheses(), + JsAnyExpression::JsInstanceofExpression(instanceof) => instanceof.needs_parentheses(), + JsAnyExpression::JsLogicalExpression(logical) => logical.needs_parentheses(), + JsAnyExpression::JsNewExpression(new) => new.needs_parentheses(), + JsAnyExpression::JsObjectExpression(object) => object.needs_parentheses(), + JsAnyExpression::JsParenthesizedExpression(parenthesized) => { + parenthesized.needs_parentheses() + } + JsAnyExpression::JsPostUpdateExpression(update) => update.needs_parentheses(), + JsAnyExpression::JsPreUpdateExpression(update) => update.needs_parentheses(), + JsAnyExpression::JsSequenceExpression(sequence) => sequence.needs_parentheses(), + JsAnyExpression::JsStaticMemberExpression(member) => member.needs_parentheses(), + JsAnyExpression::JsSuperExpression(sup) => sup.needs_parentheses(), + JsAnyExpression::JsTemplate(template) => template.needs_parentheses(), + JsAnyExpression::JsThisExpression(this) => this.needs_parentheses(), + JsAnyExpression::JsUnaryExpression(unary) => unary.needs_parentheses(), + JsAnyExpression::JsUnknownExpression(unknown) => unknown.needs_parentheses(), + JsAnyExpression::JsYieldExpression(yield_expression) => { + yield_expression.needs_parentheses() + } + JsAnyExpression::JsxTagExpression(jsx) => jsx.needs_parentheses(), + JsAnyExpression::NewTarget(target) => target.needs_parentheses(), + JsAnyExpression::TsAsExpression(as_expression) => as_expression.needs_parentheses(), + JsAnyExpression::TsNonNullAssertionExpression(non_null) => non_null.needs_parentheses(), + JsAnyExpression::TsTypeAssertionExpression(type_assertion) => { + type_assertion.needs_parentheses() + } + } + } + + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match self { + JsAnyExpression::ImportMeta(meta) => meta.needs_parentheses_with_parent(parent), + JsAnyExpression::JsAnyLiteralExpression(literal) => { + literal.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsArrayExpression(array) => { + array.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsArrowFunctionExpression(arrow) => { + arrow.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsAssignmentExpression(assignment) => { + assignment.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsAwaitExpression(await_expression) => { + await_expression.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsBinaryExpression(binary) => { + binary.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsCallExpression(call) => call.needs_parentheses_with_parent(parent), + JsAnyExpression::JsClassExpression(class) => { + class.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsComputedMemberExpression(member) => { + member.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsConditionalExpression(conditional) => { + conditional.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsFunctionExpression(function) => { + function.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsIdentifierExpression(identifier) => { + identifier.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsImportCallExpression(import_call) => { + import_call.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsInExpression(in_expression) => { + in_expression.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsInstanceofExpression(instanceof) => { + instanceof.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsLogicalExpression(logical) => { + logical.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsNewExpression(new) => new.needs_parentheses_with_parent(parent), + JsAnyExpression::JsObjectExpression(object) => { + object.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsParenthesizedExpression(parenthesized) => { + parenthesized.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsPostUpdateExpression(update) => { + update.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsPreUpdateExpression(update) => { + update.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsSequenceExpression(sequence) => { + sequence.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsStaticMemberExpression(member) => { + member.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsSuperExpression(sup) => sup.needs_parentheses_with_parent(parent), + JsAnyExpression::JsTemplate(template) => template.needs_parentheses_with_parent(parent), + JsAnyExpression::JsThisExpression(this) => this.needs_parentheses_with_parent(parent), + JsAnyExpression::JsUnaryExpression(unary) => { + unary.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsUnknownExpression(unknown) => { + unknown.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsYieldExpression(yield_expression) => { + yield_expression.needs_parentheses_with_parent(parent) + } + JsAnyExpression::JsxTagExpression(jsx) => jsx.needs_parentheses_with_parent(parent), + JsAnyExpression::NewTarget(target) => target.needs_parentheses_with_parent(parent), + JsAnyExpression::TsAsExpression(as_expression) => { + as_expression.needs_parentheses_with_parent(parent) + } + JsAnyExpression::TsNonNullAssertionExpression(non_null) => { + non_null.needs_parentheses_with_parent(parent) + } + JsAnyExpression::TsTypeAssertionExpression(type_assertion) => { + type_assertion.needs_parentheses_with_parent(parent) + } + } + } +} + +impl ExpressionNode for JsAnyExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + match self { + JsAnyExpression::ImportMeta(meta) => meta.resolve(), + JsAnyExpression::JsAnyLiteralExpression(literal) => literal.resolve(), + JsAnyExpression::JsArrayExpression(array) => array.resolve(), + JsAnyExpression::JsArrowFunctionExpression(arrow) => arrow.resolve(), + JsAnyExpression::JsAssignmentExpression(assignment) => assignment.resolve(), + JsAnyExpression::JsAwaitExpression(await_expression) => await_expression.resolve(), + JsAnyExpression::JsBinaryExpression(binary) => binary.resolve(), + JsAnyExpression::JsCallExpression(call) => call.resolve(), + JsAnyExpression::JsClassExpression(class) => class.resolve(), + JsAnyExpression::JsComputedMemberExpression(member) => member.resolve(), + JsAnyExpression::JsConditionalExpression(conditional) => conditional.resolve(), + JsAnyExpression::JsFunctionExpression(function) => function.resolve(), + JsAnyExpression::JsIdentifierExpression(identifier) => identifier.resolve(), + JsAnyExpression::JsImportCallExpression(import_call) => import_call.resolve(), + JsAnyExpression::JsInExpression(in_expression) => in_expression.resolve(), + JsAnyExpression::JsInstanceofExpression(instanceof) => instanceof.resolve(), + JsAnyExpression::JsLogicalExpression(logical) => logical.resolve(), + JsAnyExpression::JsNewExpression(new) => new.resolve(), + JsAnyExpression::JsObjectExpression(object) => object.resolve(), + JsAnyExpression::JsParenthesizedExpression(parenthesized) => parenthesized.resolve(), + JsAnyExpression::JsPostUpdateExpression(update) => update.resolve(), + JsAnyExpression::JsPreUpdateExpression(update) => update.resolve(), + JsAnyExpression::JsSequenceExpression(sequence) => sequence.resolve(), + JsAnyExpression::JsStaticMemberExpression(member) => member.resolve(), + JsAnyExpression::JsSuperExpression(sup) => sup.resolve(), + JsAnyExpression::JsTemplate(template) => template.resolve(), + JsAnyExpression::JsThisExpression(this) => this.resolve(), + JsAnyExpression::JsUnaryExpression(unary) => unary.resolve(), + JsAnyExpression::JsUnknownExpression(unknown) => unknown.resolve(), + JsAnyExpression::JsYieldExpression(yield_expression) => yield_expression.resolve(), + JsAnyExpression::JsxTagExpression(jsx) => jsx.resolve(), + JsAnyExpression::NewTarget(target) => target.resolve(), + JsAnyExpression::TsAsExpression(as_expression) => as_expression.resolve(), + JsAnyExpression::TsNonNullAssertionExpression(non_null) => non_null.resolve(), + JsAnyExpression::TsTypeAssertionExpression(type_assertion) => type_assertion.resolve(), + } + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + match self { + JsAnyExpression::ImportMeta(meta) => meta.into_resolved(), + JsAnyExpression::JsAnyLiteralExpression(literal) => literal.into_resolved(), + JsAnyExpression::JsArrayExpression(array) => array.into_resolved(), + JsAnyExpression::JsArrowFunctionExpression(arrow) => arrow.into_resolved(), + JsAnyExpression::JsAssignmentExpression(assignment) => assignment.into_resolved(), + JsAnyExpression::JsAwaitExpression(await_expression) => { + await_expression.into_resolved() + } + JsAnyExpression::JsBinaryExpression(binary) => binary.into_resolved(), + JsAnyExpression::JsCallExpression(call) => call.into_resolved(), + JsAnyExpression::JsClassExpression(class) => class.into_resolved(), + JsAnyExpression::JsComputedMemberExpression(member) => member.into_resolved(), + JsAnyExpression::JsConditionalExpression(conditional) => conditional.into_resolved(), + JsAnyExpression::JsFunctionExpression(function) => function.into_resolved(), + JsAnyExpression::JsIdentifierExpression(identifier) => identifier.into_resolved(), + JsAnyExpression::JsImportCallExpression(import_call) => import_call.into_resolved(), + JsAnyExpression::JsInExpression(in_expression) => in_expression.into_resolved(), + JsAnyExpression::JsInstanceofExpression(instanceof) => instanceof.into_resolved(), + JsAnyExpression::JsLogicalExpression(logical) => logical.into_resolved(), + JsAnyExpression::JsNewExpression(new) => new.into_resolved(), + JsAnyExpression::JsObjectExpression(object) => object.into_resolved(), + JsAnyExpression::JsParenthesizedExpression(parenthesized) => { + parenthesized.into_resolved() + } + JsAnyExpression::JsPostUpdateExpression(update) => update.into_resolved(), + JsAnyExpression::JsPreUpdateExpression(update) => update.into_resolved(), + JsAnyExpression::JsSequenceExpression(sequence) => sequence.into_resolved(), + JsAnyExpression::JsStaticMemberExpression(member) => member.into_resolved(), + JsAnyExpression::JsSuperExpression(sup) => sup.into_resolved(), + JsAnyExpression::JsTemplate(template) => template.into_resolved(), + JsAnyExpression::JsThisExpression(this) => this.into_resolved(), + JsAnyExpression::JsUnaryExpression(unary) => unary.into_resolved(), + JsAnyExpression::JsUnknownExpression(unknown) => unknown.into_resolved(), + JsAnyExpression::JsYieldExpression(yield_expression) => { + yield_expression.into_resolved() + } + JsAnyExpression::JsxTagExpression(jsx) => jsx.into_resolved(), + JsAnyExpression::NewTarget(target) => target.into_resolved(), + JsAnyExpression::TsAsExpression(as_expression) => as_expression.into_resolved(), + JsAnyExpression::TsNonNullAssertionExpression(non_null) => non_null.into_resolved(), + JsAnyExpression::TsTypeAssertionExpression(type_assertion) => { + type_assertion.into_resolved() + } + } + } +} + +/// Returns the left most expression of `expression`. +/// +/// For example, returns `a` for `(a ? b : c) + d` because it first resolves the +/// left hand expression of the binary expression, then resolves to the inner expression of the parenthesized +/// expression, and finally resolves to the test condition of the conditional expression. +pub(crate) fn resolve_left_most_expression( + expression: &JsAnyExpression, +) -> JsAnyBinaryLikeLeftExpression { + let mut current: JsAnyExpression = expression.clone(); + + while let Some(left) = get_expression_left_side(¤t) { + match left { + JsAnyBinaryLikeLeftExpression::JsAnyExpression(expression) => { + current = expression; + } + left => { + return left; + } + } + } + + current.into() +} + +/// Returns the left side of an expression (an expression where the first child is a `Node` or [None] +/// if the expression has no left side. +pub(crate) fn get_expression_left_side( + expression: &JsAnyExpression, +) -> Option { + use JsAnyExpression::*; + + let left_expression = match expression { + JsParenthesizedExpression(parenthesized) => parenthesized.expression().ok(), + JsSequenceExpression(sequence) => sequence.left().ok(), + JsStaticMemberExpression(member) => member.object().ok(), + JsComputedMemberExpression(member) => member.object().ok(), + JsTemplate(template) => template.tag(), + JsNewExpression(new) => new.callee().ok(), + JsCallExpression(call) => call.callee().ok(), + JsConditionalExpression(conditional) => conditional.test().ok(), + TsAsExpression(as_expression) => as_expression.expression().ok(), + TsNonNullAssertionExpression(non_null) => non_null.expression().ok(), + expression => { + return JsAnyBinaryLikeExpression::cast(expression.syntax().clone()) + .and_then(|binary_like| binary_like.left().ok()); + } + }; + + left_expression.map(|left| left.into()) +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub(crate) enum FirstInStatementMode { + /// Considers [JsExpressionStatement] and the body of [JsArrowFunctionExpression] as the first statement. + ExpressionStatementOrArrow, + + /// Considers [JsExpressionStatement] and [JsExportDefaultExpressionClause] as the first statement. + ExpressionOrExportDefault, +} + +/// Returns `true` if this node is at the start of an expression (depends on the passed `mode`). +/// +/// 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 { + let mut current = node; + + while let Some(parent) = current.parent() { + let parent = match parent.kind() { + JsSyntaxKind::JS_EXPRESSION_STATEMENT => { + return true; + } + + JsSyntaxKind::JS_PARENTHESIZED_EXPRESSION + | JsSyntaxKind::JS_STATIC_MEMBER_EXPRESSION + | JsSyntaxKind::JS_STATIC_MEMBER_ASSIGNMENT + | JsSyntaxKind::JS_TEMPLATE + | JsSyntaxKind::JS_CALL_EXPRESSION + | JsSyntaxKind::JS_NEW_EXPRESSION + | JsSyntaxKind::TS_AS_EXPRESSION + | JsSyntaxKind::TS_NON_NULL_ASSERTION_EXPRESSION + | JsSyntaxKind::JS_PARENTHESIZED_ASSIGNMENT => parent, + JsSyntaxKind::JS_SEQUENCE_EXPRESSION => { + let sequence = JsSequenceExpression::unwrap_cast(parent); + + let is_left = sequence.left().map(AstNode::into_syntax).as_ref() == Ok(¤t); + + if is_left { + sequence.into_syntax() + } else { + break; + } + } + + 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); + + if is_object { + member_expression.into_syntax() + } else { + break; + } + } + + JsSyntaxKind::JS_COMPUTED_MEMBER_ASSIGNMENT => { + let assignment = JsComputedMemberAssignment::unwrap_cast(parent); + + let is_object = + assignment.object().map(AstNode::into_syntax).as_ref() == Ok(¤t); + + if is_object { + assignment.into_syntax() + } else { + break; + } + } + + JsSyntaxKind::JS_ASSIGNMENT_EXPRESSION => { + let assignment = JsAssignmentExpression::unwrap_cast(parent); + + let is_left = assignment.left().map(AstNode::into_syntax).as_ref() == Ok(¤t); + + if is_left { + assignment.into_syntax() + } else { + break; + } + } + + JsSyntaxKind::JS_CONDITIONAL_EXPRESSION => { + let conditional = JsConditionalExpression::unwrap_cast(parent); + + if conditional.test().map(AstNode::into_syntax).as_ref() == Ok(¤t) { + conditional.into_syntax() + } else { + break; + } + } + + JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION + if mode == FirstInStatementMode::ExpressionStatementOrArrow => + { + let arrow = JsArrowFunctionExpression::unwrap_cast(parent); + + let is_body = arrow.body().map_or(false, |body| match body { + JsAnyFunctionBody::JsAnyExpression(expression) => { + expression.syntax() == ¤t + } + _ => false, + }); + + if is_body { + return true; + } + + break; + } + + JsSyntaxKind::JS_EXPORT_DEFAULT_EXPRESSION_CLAUSE + if mode == FirstInStatementMode::ExpressionOrExportDefault => + { + return true; + } + + kind if JsAnyBinaryLikeExpression::can_cast(kind) => { + let binary_like = JsAnyBinaryLikeExpression::unwrap_cast(parent); + + let is_left = binary_like.left().map_or(false, |left| match left { + JsAnyBinaryLikeLeftExpression::JsAnyExpression(expression) => { + expression.syntax() == ¤t + } + _ => false, + }); + + if is_left { + binary_like.into_syntax() + } else { + break; + } + } + _ => break, + }; + + current = parent; + } + + false +} + +/// Implements the shared logic for when parentheses are necessary for [JsPreUpdateExpression], [JsPostUpdateExpression], or [JsUnaryExpression] expressions. +/// Each expression may implement node specific rules, which is why calling `needs_parens` on the node is preferred. +pub(crate) fn unary_like_expression_needs_parentheses( + expression: &JsSyntaxNode, + parent: &JsSyntaxNode, +) -> bool { + debug_assert!(matches!( + expression.kind(), + JsSyntaxKind::JS_PRE_UPDATE_EXPRESSION + | JsSyntaxKind::JS_POST_UPDATE_EXPRESSION + | JsSyntaxKind::JS_UNARY_EXPRESSION + )); + debug_assert_is_parent(expression, parent); + + match parent.kind() { + JsSyntaxKind::JS_BINARY_EXPRESSION => { + let binary = JsBinaryExpression::unwrap_cast(parent.clone()); + + matches!(binary.operator(), Ok(JsBinaryOperator::Exponent)) + && binary + .left() + .map(ExpressionNode::into_resolved_syntax) + .as_ref() + == Ok(expression) + } + _ => update_or_lower_expression_needs_parentheses(expression, parent), + } +} + +/// Returns `true` if an expression with lower precedence than an update expression needs parentheses. +/// +/// This is generally the case if the expression is used in a left hand side, or primary expression context. +pub(crate) fn update_or_lower_expression_needs_parentheses( + expression: &JsSyntaxNode, + parent: &JsSyntaxNode, +) -> bool { + debug_assert_is_expression(expression); + debug_assert_is_parent(expression, parent); + + match parent.kind() { + JsSyntaxKind::JS_EXTENDS_CLAUSE => true, + _ => match parent.kind() { + JsSyntaxKind::TS_NON_NULL_ASSERTION_EXPRESSION => true, + + _ => { + is_callee(expression, parent) + || is_member_object(expression, parent) + || is_tag(expression, parent) + } + }, + } +} + +/// Returns `true` if `node< is the `object` of a [JsStaticMemberExpression] or [JsComputedMemberExpression] +pub(crate) fn is_member_object(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool { + debug_assert_is_expression(node); + debug_assert_is_parent(node, parent); + + match parent.kind() { + // Only allows expression in the `object` child. + JsSyntaxKind::JS_STATIC_MEMBER_EXPRESSION => true, + JsSyntaxKind::JS_STATIC_MEMBER_ASSIGNMENT => true, + + JsSyntaxKind::JS_COMPUTED_MEMBER_EXPRESSION => { + let member_expression = JsComputedMemberExpression::unwrap_cast(parent.clone()); + + member_expression + .object() + .map(ExpressionNode::into_resolved_syntax) + .as_ref() + == Ok(node) + } + + JsSyntaxKind::JS_COMPUTED_MEMBER_ASSIGNMENT => { + let member_assignment = JsComputedMemberAssignment::unwrap_cast(parent.clone()); + + member_assignment + .object() + .map(ExpressionNode::into_resolved_syntax) + .as_ref() + == Ok(node) + } + _ => false, + } +} + +/// Returns `true` if `node` is the `callee` of a [JsNewExpression] or [JsCallExpression]. +pub(crate) fn is_callee(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool { + debug_assert_is_expression(node); + debug_assert_is_parent(node, parent); + + // It isn't necessary to test if the node is the `callee` because the nodes only + // allow expressions in the `callee` position; + matches!( + parent.kind(), + JsSyntaxKind::JS_CALL_EXPRESSION | JsSyntaxKind::JS_NEW_EXPRESSION + ) +} + +/// Returns `true` if `node` is the `test` of a [JsConditionalExpression]. +/// +/// # Examples +/// +/// ```text +/// is_conditional_test(`a`, `a ? b : c`) -> true +/// is_conditional_test(`b`, `a ? b : c`) -> false +/// ``` +pub(crate) fn is_conditional_test(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool { + match parent.kind() { + JsSyntaxKind::JS_CONDITIONAL_EXPRESSION => { + let conditional = JsConditionalExpression::unwrap_cast(parent.clone()); + + conditional + .test() + .map(ExpressionNode::into_resolved_syntax) + .as_ref() + == Ok(node) + } + _ => false, + } +} + +pub(crate) fn is_arrow_function_body(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool { + debug_assert_is_expression(node); + + match parent.kind() { + JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION => { + let arrow = JsArrowFunctionExpression::unwrap_cast(parent.clone()); + + match arrow.body() { + Ok(JsAnyFunctionBody::JsAnyExpression(expression)) => { + &expression.resolve_syntax() == node + } + _ => false, + } + } + _ => false, + } +} + +/// Returns `true` if `node` is the `tag` of a [JsTemplate] expression +pub(crate) fn is_tag(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool { + debug_assert_is_expression(node); + debug_assert_is_parent(node, parent); + + matches!(parent.kind(), JsSyntaxKind::JS_TEMPLATE) +} + +/// Returns `true` if `node` is a spread `...node` +pub(crate) fn is_spread(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool { + debug_assert_is_expression(node); + debug_assert_is_parent(node, parent); + + matches!( + parent.kind(), + JsSyntaxKind::JSX_SPREAD_CHILD + | JsSyntaxKind::JS_SPREAD + | JsSyntaxKind::JSX_SPREAD_ATTRIBUTE + ) +} + +/// Returns `true` if `parent` is a [JsAnyBinaryLikeExpression] and `node` is the `left` or `right` of that expression. +pub(crate) fn is_binary_like_left_or_right(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool { + debug_assert_is_expression(node); + debug_assert_is_parent(node, parent); + + JsAnyBinaryLikeExpression::can_cast(parent.kind()) +} + +fn debug_assert_is_expression(node: &JsSyntaxNode) { + debug_assert!( + JsAnyExpression::can_cast(node.kind()), + "Expected {node:#?} to be an expression." + ) +} + +fn debug_assert_is_parent(node: &JsSyntaxNode, parent: &JsSyntaxNode) { + debug_assert!( + resolve_parent(node).as_ref() == Some(parent), + "Node {node:#?} is not a child of ${parent:#?}" + ) +} + +#[cfg(test)] +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.get(0).unwrap(), + _ => { + 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()); + } + + 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 '{input}' but found none.", + core::any::type_name::(), + ) + } + 1 => matching_nodes.get(0).unwrap(), + _ => { + 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()); + } + + /// Helper macro to test the [NeedsParentheses] implementation of a node. + /// + /// # Example + /// + /// + /// ``` + /// # use rome_js_formatter::assert_needs_parentheses; + /// use rome_js_syntax::JsStaticMemberExpression; + /// + /// assert_needs_parentheses!("new (test().a)()", JsStaticMemberExpression); + /// ``` + /// + /// Asserts that [NeedsParentheses.needs_parentheses()] returns true for the only [JsStaticMemberExpression] in the program. + /// + /// ``` + /// # use rome_js_syntax::JsStaticMemberExpression; + /// use rome_js_formatter::assert_needs_parentheses; + /// + /// assert_needs_parentheses!("new (test().a).b)()", JsStaticMemberExpression[1]); + /// ``` + /// + /// Asserts that [NeedsParentheses.needs_parentheses()] returns true for the second (in pre-order) [JsStaticMemberExpression] in the program. + #[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[$index:expr]) => {{ + $crate::assert_needs_parentheses!( + $input, + $Node[$index], + rome_js_syntax::SourceType::ts() + ) + }}; + + ($input:expr, $Node:ident, $source_type: expr) => {{ + $crate::parentheses::tests::assert_needs_parentheses_impl::<$Node>( + $input, + None, + $source_type, + ) + }}; + + ($input:expr, $Node:ident[$index:expr], $source_type: expr) => {{ + $crate::parentheses::tests::assert_needs_parentheses_impl::<$Node>( + $input, + Some($index), + $source_type, + ) + }}; + } + + /// Helper macro to test the [NeedsParentheses] implementation of a node. + /// + /// # Example + /// + /// + /// ``` + /// # use rome_js_syntax::JsStaticMemberExpression; + /// use rome_js_formatter::assert_not_needs_parentheses; + /// + /// assert_not_needs_parentheses!("a.b", JsStaticMemberExpression); + /// ``` + /// + /// Asserts that [NeedsParentheses.needs_parentheses()] returns true for the only [JsStaticMemberExpression] in the program. + /// + /// ``` + /// # use rome_js_syntax::JsStaticMemberExpression; + /// use rome_js_formatter::assert_not_needs_parentheses; + /// + /// assert_not_needs_parentheses!("a.b.c", JsStaticMemberExpression[0]); + /// ``` + /// + /// Asserts that [NeedsParentheses.needs_parentheses()] returns true for the first (in pre-order) [JsStaticMemberExpression] in the program. + #[macro_export] + macro_rules! assert_not_needs_parentheses { + ($input:expr, $Node:ident) => {{ + $crate::assert_not_needs_parentheses!($input, $Node, rome_js_syntax::SourceType::ts()) + }}; + + ($input:expr, $Node:ident[$index:expr]) => {{ + $crate::assert_not_needs_parentheses!( + $input, + $Node[$index], + rome_js_syntax::SourceType::ts() + ) + }}; + + ($input:expr, $Node:ident[$index:expr], $source_type: expr) => {{ + $crate::parentheses::tests::assert_not_needs_parentheses_impl::<$Node>( + $input, + Some($index), + $source_type, + ) + }}; + + ($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/as_expression.rs b/crates/rome_js_formatter/src/ts/expressions/as_expression.rs index 55bd2596738..ceea654aacf 100644 --- a/crates/rome_js_formatter/src/ts/expressions/as_expression.rs +++ b/crates/rome_js_formatter/src/ts/expressions/as_expression.rs @@ -1,8 +1,12 @@ use crate::prelude::*; +use crate::parentheses::{ + is_binary_like_left_or_right, is_callee, is_member_object, ExpressionNode, NeedsParentheses, +}; +use crate::ts::expressions::type_assertion_expression::type_cast_like_needs_parens; use rome_formatter::write; -use rome_js_syntax::TsAsExpression; -use rome_js_syntax::TsAsExpressionFields; +use rome_js_syntax::{JsAnyExpression, TsAsExpressionFields}; +use rome_js_syntax::{JsSyntaxKind, JsSyntaxNode, TsAsExpression}; #[derive(Debug, Clone, Default)] pub struct FormatTsAsExpression; @@ -15,15 +19,114 @@ impl FormatNodeRule for FormatTsAsExpression { expression, } = node.as_fields(); - write![ - f, - [ - expression.format(), - space(), - as_token.format(), - space(), - ty.format(), + let format_inner = format_with(|f| { + write![ + f, + [ + expression.format(), + space(), + as_token.format(), + space(), + ty.format(), + ] ] - ] + }); + + let parent = node.resolve_parent(); + + let is_callee_or_object = parent.map_or(false, |parent| { + is_callee(node.syntax(), &parent) || is_member_object(node.syntax(), &parent) + }); + + if is_callee_or_object { + write!(f, [group(&soft_block_indent(&format_inner))]) + } else { + write!(f, [format_inner]) + } + } + + fn needs_parentheses(&self, item: &TsAsExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for TsAsExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match parent.kind() { + JsSyntaxKind::JS_CONDITIONAL_EXPRESSION => true, + + _ => { + type_cast_like_needs_parens(self.syntax(), parent) + || is_binary_like_left_or_right(self.syntax(), parent) + } + } + } +} + +impl ExpressionNode for TsAsExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +#[cfg(test)] +mod tests { + + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::{SourceType, TsAsExpression}; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("5 as number ? true : false", TsAsExpression); + assert_needs_parentheses!("cond ? x as number : false", TsAsExpression); + assert_needs_parentheses!("cond ? true : x as number", TsAsExpression); + + assert_needs_parentheses!("class X extends (B as number) {}", TsAsExpression); + + assert_needs_parentheses!("(x as Function)()", TsAsExpression); + assert_needs_parentheses!("(x as Function)?.()", TsAsExpression); + assert_needs_parentheses!("new (x as Function)()", TsAsExpression); + + assert_needs_parentheses!("(x as any)", TsAsExpression); + assert_needs_parentheses!("(x as any)`template`", TsAsExpression); + assert_needs_parentheses!("!(x as any)", TsAsExpression); + assert_needs_parentheses!("[...(x as any)]", TsAsExpression); + assert_needs_parentheses!("({...(x as any)})", TsAsExpression); + assert_needs_parentheses!( + "", + TsAsExpression, + SourceType::tsx() + ); + assert_needs_parentheses!( + "{...(x as any)}", + TsAsExpression, + SourceType::tsx() + ); + assert_needs_parentheses!("await (x as any)", TsAsExpression); + assert_needs_parentheses!("(x as any)!", TsAsExpression); + + assert_needs_parentheses!("(x as any).member", TsAsExpression); + assert_needs_parentheses!("(x as any)[member]", TsAsExpression); + assert_not_needs_parentheses!("object[x as any]", TsAsExpression); + + assert_needs_parentheses!("(x as any) + (y as any)", TsAsExpression[0]); + assert_needs_parentheses!("(x as any) + (y as any)", TsAsExpression[1]); + + assert_needs_parentheses!("(x as any) && (y as any)", TsAsExpression[0]); + assert_needs_parentheses!("(x as any) && (y as any)", TsAsExpression[1]); + + assert_needs_parentheses!("(x as any) in (y as any)", TsAsExpression[0]); + assert_needs_parentheses!("(x as any) in (y as any)", TsAsExpression[1]); + + assert_needs_parentheses!("(x as any) instanceof (y as any)", TsAsExpression[0]); + assert_needs_parentheses!("(x as any) instanceof (y as any)", TsAsExpression[1]); + + assert_not_needs_parentheses!("x as number as string", TsAsExpression[1]); } } 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..5dc096bfc9c 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::member_chain_callee_needs_parens; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; use rome_formatter::write; -use rome_js_syntax::TsNonNullAssertionExpression; -use rome_js_syntax::TsNonNullAssertionExpressionFields; +use rome_js_syntax::{JsAnyExpression, JsSyntaxKind, TsNonNullAssertionExpressionFields}; +use rome_js_syntax::{JsSyntaxNode, TsNonNullAssertionExpression}; #[derive(Debug, Clone, Default)] pub struct FormatTsNonNullAssertionExpression; @@ -20,4 +22,27 @@ 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) + || member_chain_callee_needs_parens(self.clone().into(), parent) + } +} + +impl ExpressionNode for TsNonNullAssertionExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } } diff --git a/crates/rome_js_formatter/src/ts/expressions/type_assertion_expression.rs b/crates/rome_js_formatter/src/ts/expressions/type_assertion_expression.rs index 6b613a64af1..f8a360cae2e 100644 --- a/crates/rome_js_formatter/src/ts/expressions/type_assertion_expression.rs +++ b/crates/rome_js_formatter/src/ts/expressions/type_assertion_expression.rs @@ -1,8 +1,11 @@ use crate::prelude::*; +use crate::parentheses::{ + is_callee, is_member_object, is_spread, is_tag, ExpressionNode, NeedsParentheses, +}; use rome_formatter::write; -use rome_js_syntax::TsTypeAssertionExpression; -use rome_js_syntax::TsTypeAssertionExpressionFields; +use rome_js_syntax::{JsAnyExpression, JsSyntaxNode}; +use rome_js_syntax::{JsSyntaxKind, TsTypeAssertionExpression, TsTypeAssertionExpressionFields}; #[derive(Debug, Clone, Default)] pub struct FormatTsTypeAssertionExpression; @@ -29,4 +32,82 @@ impl FormatNodeRule for FormatTsTypeAssertionExpressi ] ] } + + fn needs_parentheses(&self, item: &TsTypeAssertionExpression) -> bool { + item.needs_parentheses() + } +} + +impl NeedsParentheses for TsTypeAssertionExpression { + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match parent.kind() { + JsSyntaxKind::TS_AS_EXPRESSION => true, + _ => type_cast_like_needs_parens(self.syntax(), parent), + } + } +} + +impl ExpressionNode for TsTypeAssertionExpression { + #[inline] + fn resolve(&self) -> JsAnyExpression { + self.clone().into() + } + + #[inline] + fn into_resolved(self) -> JsAnyExpression { + self.into() + } +} + +pub(super) fn type_cast_like_needs_parens(node: &JsSyntaxNode, parent: &JsSyntaxNode) -> bool { + debug_assert!(matches!( + node.kind(), + JsSyntaxKind::TS_TYPE_ASSERTION_EXPRESSION | JsSyntaxKind::TS_AS_EXPRESSION + )); + + match parent.kind() { + JsSyntaxKind::JS_EXTENDS_CLAUSE + | JsSyntaxKind::TS_TYPE_ASSERTION_EXPRESSION + | JsSyntaxKind::JS_UNARY_EXPRESSION + | JsSyntaxKind::JS_AWAIT_EXPRESSION + | JsSyntaxKind::TS_NON_NULL_ASSERTION_EXPRESSION => true, + + _ => { + is_callee(node, parent) + || is_tag(node, parent) + || is_spread(node, parent) + || is_member_object(node, parent) + } + } +} + +#[cfg(test)] +mod tests { + use crate::{assert_needs_parentheses, assert_not_needs_parentheses}; + use rome_js_syntax::TsTypeAssertionExpression; + + #[test] + fn needs_parentheses() { + assert_needs_parentheses!("( x) as any", TsTypeAssertionExpression); + + assert_needs_parentheses!("class X extends (B) {}", TsTypeAssertionExpression); + + assert_needs_parentheses!("(x)()", TsTypeAssertionExpression); + assert_needs_parentheses!("(x)?.()", TsTypeAssertionExpression); + assert_needs_parentheses!("new (x)()", TsTypeAssertionExpression); + + assert_needs_parentheses!("(x)", TsTypeAssertionExpression[1]); + assert_needs_parentheses!("(x)", TsTypeAssertionExpression[1]); + assert_needs_parentheses!("(x)`template`", TsTypeAssertionExpression); + assert_needs_parentheses!("!(x)", TsTypeAssertionExpression); + assert_needs_parentheses!("[...(x)]", TsTypeAssertionExpression); + assert_needs_parentheses!("({...(x)})", TsTypeAssertionExpression); + + assert_needs_parentheses!("await (x)", TsTypeAssertionExpression); + assert_needs_parentheses!("(x)!", TsTypeAssertionExpression); + + assert_needs_parentheses!("(x).member", TsTypeAssertionExpression); + assert_needs_parentheses!("(x)[member]", TsTypeAssertionExpression); + assert_not_needs_parentheses!("object[x]", TsTypeAssertionExpression); + } } diff --git a/crates/rome_js_formatter/src/utils/assignment_like.rs b/crates/rome_js_formatter/src/utils/assignment_like.rs index cf9d897d7b4..b58989a4121 100644 --- a/crates/rome_js_formatter/src/utils/assignment_like.rs +++ b/crates/rome_js_formatter/src/utils/assignment_like.rs @@ -1,7 +1,10 @@ +use crate::parentheses::{ + get_expression_left_side, resolve_parent, ExpressionNode, NeedsParentheses, +}; use crate::prelude::*; use crate::utils::member_chain::is_member_call_chain; use crate::utils::object::write_member_name; -use crate::utils::JsAnyBinaryLikeExpression; +use crate::utils::{JsAnyBinaryLikeExpression, JsAnyBinaryLikeLeftExpression}; use rome_formatter::{format_args, write, CstFormatContext, VecBuffer}; use rome_js_syntax::{ JsAnyAssignmentPattern, JsAnyBindingPattern, JsAnyCallArgument, JsAnyClassMemberName, @@ -195,7 +198,7 @@ impl Format for RightAssignmentLike { /// - Assignment /// - Object property member /// - Variable declaration -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Copy, Clone)] pub(crate) enum AssignmentLikeLayout { /// This is a special layout usually used for variable declarations. /// This layout is hit, usually, when a [variable declarator](JsVariableDeclarator) doesn't have initializer: @@ -306,29 +309,29 @@ const MIN_OVERLAP_FOR_BREAK: u8 = 3; impl JsAnyAssignmentLike { fn right(&self) -> SyntaxResult { - match self { - JsAnyAssignmentLike::JsPropertyObjectMember(property) => Ok(property.value()?.into()), - JsAnyAssignmentLike::JsAssignmentExpression(assignment) => { - Ok(assignment.right()?.into()) - } + let right = match self { + JsAnyAssignmentLike::JsPropertyObjectMember(property) => property.value()?.into(), + JsAnyAssignmentLike::JsAssignmentExpression(assignment) => assignment.right()?.into(), JsAnyAssignmentLike::JsObjectAssignmentPatternProperty(assignment_pattern) => { - Ok(assignment_pattern.pattern()?.into()) + assignment_pattern.pattern()?.into() } JsAnyAssignmentLike::JsVariableDeclarator(variable_declarator) => { // SAFETY: Calling `unwrap` here is safe because we check `has_only_left_hand_side` variant at the beginning of the `layout` function - Ok(variable_declarator.initializer().unwrap().into()) + variable_declarator.initializer().unwrap().into() } JsAnyAssignmentLike::TsTypeAliasDeclaration(type_alias_declaration) => { - Ok(type_alias_declaration.ty()?.into()) + type_alias_declaration.ty()?.into() } JsAnyAssignmentLike::JsPropertyClassMember(n) => { // SAFETY: Calling `unwrap` here is safe because we check `has_only_left_hand_side` variant at the beginning of the `layout` function - Ok(n.value().unwrap().into()) + n.value().unwrap().into() } JsAnyAssignmentLike::TsPropertySignatureClassMember(_) => { unreachable!("TsPropertySignatureClassMember doesn't have any right side. If you're here, `has_only_left_hand_side` hasn't been called") } - } + }; + + Ok(right) } fn left(&self) -> SyntaxResult { @@ -585,13 +588,12 @@ impl JsAnyAssignmentLike { return Ok(AssignmentLikeLayout::SuppressedInitializer); } } + let right_expression = right.as_expression().map(ExpressionNode::into_resolved); - if let Some(layout) = self.chain_formatting_layout(&right)? { + if let Some(layout) = self.chain_formatting_layout(right_expression.as_ref())? { return Ok(layout); } - let right_expression = right.as_expression(); - if let Some(JsAnyExpression::JsCallExpression(call_expression)) = &right_expression { if call_expression.callee()?.syntax().text() == "require" { return Ok(AssignmentLikeLayout::NeverBreakAfterOperator); @@ -602,7 +604,7 @@ impl JsAnyAssignmentLike { return Ok(AssignmentLikeLayout::BreakLeftHandSide); } - if self.should_break_after_operator()? { + if self.should_break_after_operator(&right)? { return Ok(AssignmentLikeLayout::BreakAfterOperator); } @@ -619,6 +621,9 @@ impl JsAnyAssignmentLike { let right_expression = iter::successors(right_expression, |expression| match expression { JsAnyExpression::JsUnaryExpression(unary) => unary.argument().ok(), JsAnyExpression::TsNonNullAssertionExpression(assertion) => assertion.expression().ok(), + JsAnyExpression::JsParenthesizedExpression(parenthesized) => { + parenthesized.expression().ok() + } _ => None, }) .last(); @@ -632,7 +637,7 @@ impl JsAnyAssignmentLike { return Ok(AssignmentLikeLayout::BreakAfterOperator); } - let is_poorly_breakable = match right_expression { + let is_poorly_breakable = match &right_expression { Some(expression) => is_poorly_breakable_member_or_call_chain(expression, f)?, None => false, }; @@ -642,7 +647,7 @@ impl JsAnyAssignmentLike { } if matches!( - self.right()?.as_expression(), + right_expression, Some( JsAnyExpression::JsClassExpression(_) | JsAnyExpression::JsTemplate(_) @@ -676,11 +681,11 @@ impl JsAnyAssignmentLike { /// and if so, it return the layout type fn chain_formatting_layout( &self, - right: &RightAssignmentLike, + right_expression: Option<&JsAnyExpression>, ) -> SyntaxResult> { let right_is_tail = !matches!( - right, - RightAssignmentLike::JsAnyExpression(JsAnyExpression::JsAssignmentExpression(_)) + right_expression, + Some(JsAnyExpression::JsAssignmentExpression(_)) ); // The chain goes up two levels, by checking up to the great parent if all the conditions @@ -688,14 +693,14 @@ impl JsAnyAssignmentLike { let upper_chain_is_eligible = // First, we check if the current node is an assignment expression if let JsAnyAssignmentLike::JsAssignmentExpression(assignment) = self { - assignment.syntax().parent().map_or(false, |parent| { + assignment.resolve_parent().map_or(false, |parent| { // Then we check if the parent is assignment expression or variable declarator if matches!( parent.kind(), JsSyntaxKind::JS_ASSIGNMENT_EXPRESSION - | JsSyntaxKind::JS_VARIABLE_DECLARATOR + | JsSyntaxKind::JS_INITIALIZER_CLAUSE ) { - let great_parent_kind = parent.parent().map(|n| n.kind()); + let great_parent_kind = resolve_parent(&parent).map(|n| n.kind()); // Finally, we check the great parent. // The great parent triggers the eligibility when // - the current node that we were inspecting is not a "tail" @@ -720,20 +725,21 @@ impl JsAnyAssignmentLike { if !right_is_tail { Some(AssignmentLikeLayout::Chain) } else { - match right { - RightAssignmentLike::JsAnyExpression( - JsAnyExpression::JsArrowFunctionExpression(arrow), - ) => { + match right_expression { + Some(JsAnyExpression::JsArrowFunctionExpression(arrow)) => { let this_body = arrow.body()?; - if matches!( - this_body, - JsAnyFunctionBody::JsAnyExpression( - JsAnyExpression::JsArrowFunctionExpression(_) - ) - ) { - Some(AssignmentLikeLayout::ChainTailArrowFunction) - } else { - Some(AssignmentLikeLayout::ChainTail) + match this_body { + JsAnyFunctionBody::JsAnyExpression(expression) => { + if matches!( + expression.resolve(), + JsAnyExpression::JsArrowFunctionExpression(_) + ) { + Some(AssignmentLikeLayout::ChainTailArrowFunction) + } else { + Some(AssignmentLikeLayout::ChainTail) + } + } + _ => Some(AssignmentLikeLayout::ChainTail), } } @@ -799,13 +805,11 @@ impl JsAnyAssignmentLike { /// /// This function is small wrapper around [should_break_after_operator] because it has to work /// for nodes that belong to TypeScript too. - fn should_break_after_operator(&self) -> SyntaxResult { - let right = self.right()?; - + fn should_break_after_operator(&self, right: &RightAssignmentLike) -> SyntaxResult { let result = if let Some(expression) = right.as_expression() { should_break_after_operator(&expression)? } else { - has_new_line_before_comment(right.syntax()) + has_leading_own_line_comment(right.syntax()) }; Ok(result) @@ -814,15 +818,30 @@ impl JsAnyAssignmentLike { /// Checks if the function is entitled to be printed with layout [AssignmentLikeLayout::BreakAfterOperator] pub(crate) fn should_break_after_operator(right: &JsAnyExpression) -> SyntaxResult { - if has_new_line_before_comment(right.syntax()) { - return Ok(true); + // Traverse from the right expression to the left most node and check if any has a leading comment + // that causes a line break. + let mut current: JsAnyBinaryLikeLeftExpression = right.clone().into(); + loop { + if has_leading_own_line_comment(current.syntax()) { + return Ok(true); + } + + if let JsAnyBinaryLikeLeftExpression::JsAnyExpression(expression) = current { + if let Some(left) = get_expression_left_side(&expression) { + current = left; + continue; + } + } + break; } + let right = right.resolve(); + let result = match right { // head is a long chain, meaning that right -> right are both assignment expressions JsAnyExpression::JsAssignmentExpression(assignment) => { matches!( - assignment.right()?, + assignment.right()?.resolve(), JsAnyExpression::JsAssignmentExpression(_) ) } @@ -844,16 +863,29 @@ pub(crate) fn should_break_after_operator(right: &JsAnyExpression) -> SyntaxResu Ok(result) } -/// If checks if among leading trivias, we there's a sequence of [Newline, Comment] -pub(crate) fn has_new_line_before_comment(node: &JsSyntaxNode) -> bool { +/// Tests if the node has any leading comment that will be placed on its own line. +pub(crate) fn has_leading_own_line_comment(node: &JsSyntaxNode) -> bool { if let Some(leading_trivia) = node.first_leading_trivia() { - let mut seen_newline = false; + let mut first_comment = true; + let mut after_comment = false; + let mut after_new_line = false; + for piece in leading_trivia.pieces() { - if piece.is_comments() && seen_newline { - return true; - } - if piece.is_newline() { - seen_newline = true + if piece.is_comments() { + if after_new_line && first_comment { + return true; + } else { + first_comment = false; + after_comment = true; + } + } else if piece.is_newline() { + if after_comment { + return true; + } else { + after_new_line = true; + } + } else if piece.is_skipped() { + return false; } } } @@ -995,7 +1027,7 @@ impl Format for JsAnyAssignmentLike { /// or have only one which [is_short_argument], except for member call chains /// [Prettier applies]: https://github.com/prettier/prettier/blob/a043ac0d733c4d53f980aa73807a63fc914f23bd/src/language-js/print/assignment.js#L329 fn is_poorly_breakable_member_or_call_chain( - expression: JsAnyExpression, + expression: &JsAnyExpression, f: &mut Formatter, ) -> SyntaxResult { let threshold = f.context().line_width().value() / 4; @@ -1012,27 +1044,32 @@ fn is_poorly_breakable_member_or_call_chain( // Keeping track of all call expressions in the chain to check them later let mut call_expressions = vec![]; - let mut expression = Some(expression); + let mut expression = Some(expression.clone()); while let Some(node) = expression.take() { - match node { + expression = match node { JsAnyExpression::JsCallExpression(call_expression) => { is_chain = true; - expression = Some(call_expression.callee()?); + let callee = call_expression.callee()?; call_expressions.push(call_expression); + Some(callee) } JsAnyExpression::JsStaticMemberExpression(node) => { is_chain = true; - expression = Some(node.object()?); + Some(node.object()?) } JsAnyExpression::JsComputedMemberExpression(node) => { is_chain = true; - expression = Some(node.object()?); + Some(node.object()?) } + JsAnyExpression::JsParenthesizedExpression(node) => Some(node.expression()?), JsAnyExpression::JsIdentifierExpression(_) | JsAnyExpression::JsThisExpression(_) => { is_chain_head_simple = true; + break; + } + _ => { + break; } - _ => {} } } @@ -1083,7 +1120,7 @@ fn is_short_argument(argument: JsAnyCallArgument, threshold: u16) -> SyntaxResul } if let JsAnyCallArgument::JsAnyExpression(expression) = argument { - let is_short_argument = match expression { + let is_short_argument = match expression.resolve() { JsAnyExpression::JsThisExpression(_) => true, JsAnyExpression::JsIdentifierExpression(identifier) => { identifier.name()?.value_token()?.text_trimmed().len() <= threshold as usize diff --git a/crates/rome_js_formatter/src/utils/binary_like_expression.rs b/crates/rome_js_formatter/src/utils/binary_like_expression.rs index d663e790d6f..ddb855b377d 100644 --- a/crates/rome_js_formatter/src/utils/binary_like_expression.rs +++ b/crates/rome_js_formatter/src/utils/binary_like_expression.rs @@ -6,6 +6,9 @@ use rome_js_syntax::{ JsSyntaxNode, JsSyntaxToken, OperatorPrecedence, }; +use crate::parentheses::{ + is_callee, is_member_object, is_spread, is_tag, ExpressionNode, NeedsParentheses, +}; use crate::utils::should_break_after_operator; use rome_rowan::{declare_node_union, AstNode, SyntaxResult}; use std::fmt::Debug; @@ -167,10 +170,6 @@ impl BinaryLikeOperator { } } - pub const fn is_logical(&self) -> bool { - matches!(self, BinaryLikeOperator::Logical(_)) - } - pub const fn is_remainder(&self) -> bool { matches!( self, @@ -191,64 +190,13 @@ impl From for BinaryLikeOperator { } } -/// This function returns `true` when the binary expression should be wrapped in parentheses to either -/// * a) correctly encode precedence -/// * b) Improve readability by adding parentheses around expressions where the precedence may not be obvious to many readers. -pub(crate) fn binary_argument_needs_parens( - parent_operator: BinaryLikeOperator, - node: &JsAnyBinaryLikeLeftExpression, - is_right: bool, -) -> SyntaxResult { - let current_operator = - if let Some(binary_like) = JsAnyBinaryLikeExpression::cast(node.syntax().clone()) { - binary_like.operator()? - } else { - return Ok(false); - }; - - // For logical nodes, add parentheses if the operators aren't the same - if parent_operator.is_logical() && current_operator.is_logical() { - return Ok(parent_operator != current_operator); - } - - let current_precedence = current_operator.precedence(); - let parent_precedence = parent_operator.precedence(); - - #[allow(clippy::if_same_then_else, clippy::needless_bool)] - // If the parent has a higher precedence than parentheses are necessary to not change the semantic meaning - // when re-parsing. - let result = if parent_precedence > current_precedence { - true - } else if is_right && parent_precedence == current_precedence { - true - } - // Add parentheses around bitwise and bit shift operators - // `a * 3 >> 5` -> `(a * 3) >> 5` - else if parent_precedence.is_bitwise() || parent_precedence.is_shift() { - true - } - // `a % 4 + 4` -> `a % (4 + 4)` - else if parent_precedence < current_precedence && current_operator.is_remainder() { - parent_precedence.is_additive() - } else if parent_precedence == current_precedence - && !should_flatten(parent_operator, current_operator) - { - true - } else { - false - }; - - Ok(result) -} - // False positive, Removing the `+ 'a` lifetime fails to compile with `hidden type for `impl Trait` captures lifetime that does not appear in bounds` #[allow(clippy::needless_lifetimes)] fn format_sub_expression<'a>( - parent_operator: BinaryLikeOperator, sub_expression: &'a JsAnyBinaryLikeLeftExpression, ) -> impl Format + 'a { format_with(move |f| { - if binary_argument_needs_parens(parent_operator, sub_expression, true)? { + if binary_argument_needs_parens(sub_expression) { format_parenthesize( sub_expression.syntax().first_token().as_ref(), &sub_expression, @@ -408,11 +356,10 @@ impl FlattenItems { } let left = binary_like_expression.left()?; - let operator = binary_like_expression.operator()?; let operator_token = binary_like_expression.operator_token()?; let operator_has_trailing_comments = operator_token.has_trailing_comments(); - let left_parenthesized = binary_argument_needs_parens(operator, &left, false)?; + let left_parenthesized = binary_argument_needs_parens(&left); let mut left_item = FlattenItem::new( FlattenedBinaryExpressionPart::Group { current: left, @@ -526,7 +473,7 @@ impl FlattenedBinaryExpressionPart { FlattenedBinaryExpressionPart::Right { parent } => { let right = JsAnyBinaryLikeLeftExpression::JsAnyExpression(parent.right()?); - write!(f, [format_sub_expression(parent.operator()?, &right)]) + write!(f, [format_sub_expression(&right)]) } FlattenedBinaryExpressionPart::Left { expression } => { write!(f, [expression]) @@ -878,6 +825,122 @@ impl From for JsAnyExpression { } } +/// This function returns `true` when the binary expression should be wrapped in parentheses to either +/// * a) correctly encode precedence +/// * b) Improve readability by adding parentheses around expressions where the precedence may not be obvious to many readers. +pub(crate) fn binary_argument_needs_parens(node: &JsAnyBinaryLikeLeftExpression) -> bool { + if let Some(binary_like) = JsAnyBinaryLikeExpression::cast(node.syntax().clone()) { + binary_like.needs_parentheses() + } else { + false + } +} + +pub(crate) fn needs_binary_like_parentheses( + node: &JsAnyBinaryLikeExpression, + parent: &JsSyntaxNode, +) -> bool { + match parent.kind() { + JsSyntaxKind::JS_EXTENDS_CLAUSE + | JsSyntaxKind::TS_AS_EXPRESSION + | JsSyntaxKind::TS_TYPE_ASSERTION_EXPRESSION + | JsSyntaxKind::JS_UNARY_EXPRESSION + | JsSyntaxKind::JS_AWAIT_EXPRESSION + | JsSyntaxKind::TS_NON_NULL_ASSERTION_EXPRESSION => true, + + kind if JsAnyBinaryLikeExpression::can_cast(kind) => { + let parent = JsAnyBinaryLikeExpression::unwrap_cast(parent.clone()); + + let operator = node.operator(); + let parent_operator = parent.operator(); + + match (operator, parent_operator) { + (Ok(operator), Ok(parent_operator)) => { + let precedence = operator.precedence(); + let parent_precedence = parent_operator.precedence(); + + #[allow(clippy::if_same_then_else, clippy::needless_bool)] + // If the parent has a higher precedence than parentheses are necessary to not change the semantic meaning + // when re-parsing. + if parent_precedence > precedence { + return true; + } + + let is_right = parent + .right() + .map(ExpressionNode::into_resolved_syntax) + .as_ref() + == Ok(node.syntax()); + + // `a ** b ** c` + if is_right && parent_precedence == precedence { + return true; + } + + // Add parentheses around bitwise and bit shift operators + // `a * 3 >> 5` -> `(a * 3) >> 5` + if parent_precedence.is_bitwise() || parent_precedence.is_shift() { + return true; + } + + // `a % 4 + 4` -> `(a % 4) + 4)` + if parent_precedence < precedence && operator.is_remainder() { + return parent_precedence.is_additive(); + } + + if parent_precedence == precedence && !should_flatten(parent_operator, operator) + { + return true; + } + + false + } + // Just to be sure + _ => true, + } + } + + _ => { + is_callee(node.syntax(), parent) + || is_tag(node.syntax(), parent) + || is_spread(node.syntax(), parent) + || is_member_object(node.syntax(), parent) + } + } +} + +impl NeedsParentheses for JsAnyBinaryLikeExpression { + fn needs_parentheses(&self) -> bool { + match self { + JsAnyBinaryLikeExpression::JsLogicalExpression(logical) => logical.needs_parentheses(), + JsAnyBinaryLikeExpression::JsBinaryExpression(binary) => binary.needs_parentheses(), + JsAnyBinaryLikeExpression::JsInstanceofExpression(instanceof) => { + instanceof.needs_parentheses() + } + JsAnyBinaryLikeExpression::JsInExpression(in_expression) => { + in_expression.needs_parentheses() + } + } + } + + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match self { + JsAnyBinaryLikeExpression::JsLogicalExpression(logical) => { + logical.needs_parentheses_with_parent(parent) + } + JsAnyBinaryLikeExpression::JsBinaryExpression(binary) => { + binary.needs_parentheses_with_parent(parent) + } + JsAnyBinaryLikeExpression::JsInstanceofExpression(instanceof) => { + instanceof.needs_parentheses_with_parent(parent) + } + JsAnyBinaryLikeExpression::JsInExpression(in_expression) => { + in_expression.needs_parentheses_with_parent(parent) + } + } + } +} + /// Returns `true` if a binary expression nested into another binary expression should be flattened together. /// /// This is generally the case if both operator have the same precedence. See the inline comments for the exceptions @@ -886,38 +949,41 @@ fn should_flatten(parent_operator: BinaryLikeOperator, child_operator: BinaryLik let parent_precedence = parent_operator.precedence(); let child_precedence = child_operator.precedence(); - #[allow(clippy::if_same_then_else, clippy::needless_bool)] if parent_precedence != child_precedence { - false + return false; } + // `**` is right associative. - else if parent_precedence.is_exponential() { - false + if parent_precedence.is_exponential() { + return false; } // avoid `a == b == c` -> `(a == b) == c` - else if parent_precedence.is_equality() && child_precedence.is_equality() { - false + + if parent_precedence.is_equality() && child_precedence.is_equality() { + return false; } + // `a % b * c` -> `(a % b) * c` // `a * b % c` -> `(a * b) % c` - else if (child_operator.is_remainder() && parent_precedence.is_multiplicative()) + if (child_operator.is_remainder() && parent_precedence.is_multiplicative()) || (parent_operator.is_remainder() && child_precedence.is_multiplicative()) { - false + return false; } + // `a * b / c` -> `(a * b) / c` - else if child_operator != parent_operator + if child_operator != parent_operator && child_precedence.is_multiplicative() && parent_precedence.is_multiplicative() { - false + return false; } // `a >> b >> c` -> `(a >> b) >> c` - else if parent_precedence.is_shift() && child_precedence.is_shift() { - false - } else { - true + if parent_precedence.is_shift() && child_precedence.is_shift() { + return false; } + + true } declare_node_union! { @@ -933,6 +999,26 @@ impl JsAnyBinaryLikeLeftExpression { } } +impl NeedsParentheses for JsAnyBinaryLikeLeftExpression { + fn needs_parentheses(&self) -> bool { + match self { + JsAnyBinaryLikeLeftExpression::JsAnyExpression(expression) => { + expression.needs_parentheses() + } + JsAnyBinaryLikeLeftExpression::JsPrivateName(_) => false, + } + } + + fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool { + match self { + JsAnyBinaryLikeLeftExpression::JsAnyExpression(expression) => { + expression.needs_parentheses_with_parent(parent) + } + JsAnyBinaryLikeLeftExpression::JsPrivateName(_) => false, + } + } +} + impl Format for JsAnyBinaryLikeLeftExpression { fn fmt(&self, f: &mut JsFormatter) -> FormatResult<()> { match self { diff --git a/crates/rome_js_formatter/src/utils/conditional.rs b/crates/rome_js_formatter/src/utils/conditional.rs index adc3cb79ab6..09805c9e74c 100644 --- a/crates/rome_js_formatter/src/utils/conditional.rs +++ b/crates/rome_js_formatter/src/utils/conditional.rs @@ -1,13 +1,13 @@ use crate::prelude::*; use rome_formatter::write; -use crate::utils::parens::starts_with_no_lookahead_token; +use crate::parentheses::{ExpressionNode, NeedsParentheses}; use rome_js_syntax::{ - JsAnyExpression, JsAnyFunctionBody, JsArrowFunctionExpression, JsAssignmentExpression, - JsCallExpression, JsComputedMemberExpression, JsConditionalExpression, JsInitializerClause, - JsNewExpression, JsParenthesizedExpression, JsReturnStatement, JsStaticMemberExpression, - JsSyntaxKind, JsSyntaxNode, JsSyntaxToken, JsThrowStatement, JsUnaryExpression, - JsYieldArgument, TsAsExpression, TsConditionalType, TsNonNullAssertionExpression, TsType, + JsAnyExpression, JsAssignmentExpression, JsCallExpression, JsComputedMemberExpression, + JsConditionalExpression, JsInitializerClause, JsNewExpression, JsParenthesizedExpression, + JsReturnStatement, JsStaticMemberExpression, JsSyntaxKind, JsSyntaxNode, JsSyntaxToken, + JsThrowStatement, JsUnaryExpression, JsYieldArgument, TsAsExpression, TsConditionalType, + TsNonNullAssertionExpression, TsType, }; use rome_rowan::{declare_node_union, AstNode, SyntaxResult}; @@ -137,25 +137,10 @@ impl Format for JsAnyConditional { } }); - let with_extra_indent = format_with(|f| { - if layout.is_nested_test() || should_extra_indent { - group(&soft_block_indent(&grouped)).fmt(f) - } else { - grouped.fmt(f) - } - }); - - if self.needs_parens(layout.parent()) { - write!( - f, - [format_parenthesize( - self.syntax().first_token().as_ref(), - &with_extra_indent, - self.syntax().last_token().as_ref() - )] - ) + if layout.is_nested_test() || should_extra_indent { + group(&soft_block_indent(&grouped)).fmt(f) } else { - write!(f, [with_extra_indent]) + grouped.fmt(f) } } } @@ -202,9 +187,7 @@ impl ExpressionOrType { /// Resolves to the first non parenthesized expression. Returns self for types. fn resolve(&self) -> JsSyntaxNode { match self { - ExpressionOrType::JsAnyExpression(expression) => { - resolve_expression_syntax(expression.clone()) - } + ExpressionOrType::JsAnyExpression(expression) => expression.resolve_syntax(), ExpressionOrType::TsType(ty) => ty.syntax().clone(), } } @@ -302,7 +285,12 @@ impl ConditionalLayout { impl JsAnyConditional { fn layout(&self) -> ConditionalLayout { - let parent = match resolve_expression_parent(self.syntax()) { + let resolved_parent = match self { + JsAnyConditional::JsConditionalExpression(conditional) => conditional.resolve_parent(), + JsAnyConditional::TsConditionalType(ty) => ty.syntax().parent(), + }; + + let parent = match resolved_parent { None => return ConditionalLayout::Root { parent: None }, Some(parent) => parent, }; @@ -330,23 +318,13 @@ impl JsAnyConditional { } } - fn needs_parens(&self, parent: Option<&JsSyntaxNode>) -> bool { - match self { - JsAnyConditional::JsConditionalExpression(conditional) => parent - .map_or(false, |parent| { - conditional_needs_parens(parent, conditional) - }), - JsAnyConditional::TsConditionalType(_) => false, - } - } - /// Returns `true` if `node` is the `test` of this conditional. fn is_test(&self, node: &JsSyntaxNode) -> bool { match self { JsAnyConditional::JsConditionalExpression(conditional) => conditional .test() .ok() - .map(resolve_expression) + .map(ExpressionNode::into_resolved) .map_or(false, |resolved| resolved.syntax() == node), JsAnyConditional::TsConditionalType(conditional) => { conditional.check_type().map(AstNode::into_syntax).as_ref() == Ok(node) @@ -402,9 +380,8 @@ impl JsAnyConditional { let alternate = match self { JsAnyConditional::JsConditionalExpression(conditional) => conditional .alternate() - .map(resolve_expression) - .ok() - .map(AstNode::into_syntax), + .map(ExpressionNode::into_resolved_syntax) + .ok(), JsAnyConditional::TsConditionalType(ts_conditional) => { ts_conditional.false_type().ok().map(AstNode::into_syntax) } @@ -466,7 +443,11 @@ impl JsAnyConditional { JsSyntaxKind::JS_CALL_EXPRESSION => { let call_expression = JsCallExpression::unwrap_cast(ancestor); - if call_expression.callee().map(resolve_expression).as_ref() == Ok(&expression) + if call_expression + .callee() + .map(ExpressionNode::into_resolved) + .as_ref() + == Ok(&expression) { Ancestor::MemberChain(call_expression.into()) } else { @@ -477,7 +458,10 @@ impl JsAnyConditional { JsSyntaxKind::JS_STATIC_MEMBER_EXPRESSION => { let member_expression = JsStaticMemberExpression::unwrap_cast(ancestor); - if member_expression.object().map(resolve_expression).as_ref() + if member_expression + .object() + .map(ExpressionNode::into_resolved) + .as_ref() == Ok(&expression) { Ancestor::MemberChain(member_expression.into()) @@ -488,7 +472,10 @@ impl JsAnyConditional { JsSyntaxKind::JS_COMPUTED_MEMBER_EXPRESSION => { let member_expression = JsComputedMemberExpression::unwrap_cast(ancestor); - if member_expression.object().map(resolve_expression).as_ref() + if member_expression + .object() + .map(ExpressionNode::into_resolved) + .as_ref() == Ok(&expression) { Ancestor::MemberChain(member_expression.into()) @@ -501,7 +488,7 @@ impl JsAnyConditional { if non_null_assertion .expression() - .map(resolve_expression) + .map(ExpressionNode::into_resolved) .as_ref() == Ok(&expression) { @@ -514,8 +501,13 @@ impl JsAnyConditional { let new_expression = JsNewExpression::unwrap_cast(ancestor); // Skip over new expressions - if new_expression.callee().map(resolve_expression).as_ref() == Ok(&expression) { - parent = new_expression.syntax().parent(); + if new_expression + .callee() + .map(ExpressionNode::into_resolved) + .as_ref() + == Ok(&expression) + { + parent = new_expression.resolve_parent(); expression = new_expression.into(); break; } @@ -525,10 +517,13 @@ impl JsAnyConditional { JsSyntaxKind::TS_AS_EXPRESSION => { let as_expression = TsAsExpression::unwrap_cast(ancestor.clone()); - if as_expression.expression().map(resolve_expression).as_ref() + if as_expression + .expression() + .map(ExpressionNode::into_resolved) + .as_ref() == Ok(&expression) { - parent = as_expression.syntax().parent(); + parent = as_expression.resolve_parent(); expression = as_expression.into(); break; } @@ -590,7 +585,7 @@ impl JsAnyConditional { _ => None, }; - argument.map_or(false, |argument| resolve_expression(argument) == expression) + argument.map_or(false, |argument| argument.resolve() == expression) } } } @@ -609,98 +604,3 @@ impl JsAnyConditional { } } } - -/// Determines if the conditional expression needs parentheses to ease readability OR maintain precedence. -pub(crate) fn conditional_needs_parens( - parent: &JsSyntaxNode, - conditional: &JsConditionalExpression, -) -> bool { - match parent.kind() { - JsSyntaxKind::JS_TEMPLATE - | JsSyntaxKind::JS_STATIC_MEMBER_EXPRESSION - | JsSyntaxKind::JS_UNARY_EXPRESSION - | JsSyntaxKind::JS_SPREAD - | JsSyntaxKind::JS_BINARY_EXPRESSION - | JsSyntaxKind::JS_LOGICAL_EXPRESSION - | JsSyntaxKind::JS_INSTANCEOF_EXPRESSION - | JsSyntaxKind::JS_IN_EXPRESSION - | JsSyntaxKind::JS_AWAIT_EXPRESSION - | JsSyntaxKind::JSX_SPREAD_ATTRIBUTE - | JsSyntaxKind::TS_TYPE_ASSERTION_EXPRESSION - | JsSyntaxKind::TS_AS_EXPRESSION - | JsSyntaxKind::JS_EXTENDS_CLAUSE - | JsSyntaxKind::TS_NON_NULL_ASSERTION_EXPRESSION => true, - - JsSyntaxKind::JS_NEW_EXPRESSION => { - let new_expression = JsNewExpression::unwrap_cast(parent.clone()); - new_expression - .callee() - .map(resolve_expression_syntax) - .as_ref() - == Ok(conditional.syntax()) - } - JsSyntaxKind::JS_CALL_EXPRESSION => { - let call_expression = JsCallExpression::unwrap_cast(parent.clone()); - call_expression - .callee() - .map(resolve_expression_syntax) - .as_ref() - == Ok(conditional.syntax()) - } - JsSyntaxKind::JS_CONDITIONAL_EXPRESSION => { - let conditional_parent = JsConditionalExpression::unwrap_cast(parent.clone()); - conditional_parent - .test() - .map(resolve_expression_syntax) - .as_ref() - == Ok(conditional.syntax()) - } - JsSyntaxKind::JS_COMPUTED_MEMBER_EXPRESSION => { - let member_expression = JsComputedMemberExpression::unwrap_cast(parent.clone()); - member_expression - .object() - .map(resolve_expression_syntax) - .as_ref() - == Ok(conditional.syntax()) - } - JsSyntaxKind::JS_ARROW_FUNCTION_EXPRESSION => { - let arrow = JsArrowFunctionExpression::unwrap_cast(parent.clone()); - - match arrow.body() { - Err(_) => false, - Ok(JsAnyFunctionBody::JsFunctionBody(_)) => false, - Ok(JsAnyFunctionBody::JsAnyExpression(JsAnyExpression::JsSequenceExpression( - _, - ))) => false, - Ok(JsAnyFunctionBody::JsAnyExpression(expression)) => { - let resolved = resolve_expression(expression); - resolved.syntax() == conditional.syntax() - && starts_with_no_lookahead_token(resolved).unwrap_or(false) - } - } - } - _ => false, - } -} - -/// Resolves an expression to the first non parenthesized expression. -pub(crate) fn resolve_expression(expression: JsAnyExpression) -> JsAnyExpression { - match expression { - JsAnyExpression::JsParenthesizedExpression(expression) => expression - .expression() - .unwrap_or(JsAnyExpression::JsParenthesizedExpression(expression)), - _ => expression, - } -} - -/// Resolves an expression to the first non parenthesized expression and returns its [JsSyntaxNode]. -pub(crate) fn resolve_expression_syntax(expression: JsAnyExpression) -> JsSyntaxNode { - resolve_expression(expression).into_syntax() -} - -/// Resolves the parent of the node that is not a parenthesized expression -pub(crate) fn resolve_expression_parent(node: &JsSyntaxNode) -> Option { - node.ancestors() - .skip(1) - .find(|parent| !JsParenthesizedExpression::can_cast(parent.kind())) -} diff --git a/crates/rome_js_formatter/src/utils/jsx.rs b/crates/rome_js_formatter/src/utils/jsx.rs index 071ffa058d3..1fa140fb255 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::NeedsParentheses; 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 = node.resolve_parent(); + + 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 51% 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..4b648c4acc3 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,159 @@ 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; +/// One entry in a member chain. #[derive(Clone, Debug)] +pub(crate) enum ChainEntry { + /// A member that is parenthesized in the source document + Parenthesized { + /// The chain member + member: ChainMember, + /// The top most ancestor of the chain member that is a parenthesized expression. + /// + /// ```text + /// (a.b).c() + /// ^^^ -> member + /// ^----^ -> top_most_parentheses + /// + /// ((a.b)).c() + /// ^^^ -> member + /// ^-----^ -> top most parentheses (skips inner parentheses node) + /// ``` + top_most_parentheses: JsParenthesizedExpression, + }, + Member(ChainMember), +} + +impl ChainEntry { + /// Returns the inner member + pub fn member(&self) -> &ChainMember { + match self { + ChainEntry::Parenthesized { member, .. } => member, + ChainEntry::Member(member) => member, + } + } + + /// Returns the top most parentheses node if any + 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()) + } + + /// Returns true if the member any of it's ancestor parentheses nodes has any leading 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(()) + } +} + /// Data structure that holds the node with its formatted version -pub(crate) enum FlattenItem { +#[derive(Clone, Debug)] +pub(crate) enum ChainMember { /// Holds onto a [rome_js_syntax::JsStaticMemberExpression] StaticMember(JsStaticMemberExpression), /// Holds onto a [rome_js_syntax::JsCallExpression] @@ -24,12 +165,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 +178,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 +229,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 +243,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 +257,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 +273,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 +303,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 +322,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..dc936581a90 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,45 @@ use crate::context::TabWidth; +use crate::parentheses::NeedsParentheses; use crate::prelude::*; -use crate::utils::member_chain::flatten_item::FlattenItem; -use crate::utils::member_chain::simple_argument::SimpleArgument; +use crate::utils::member_chain::chain_member::{ChainEntry, ChainMember}; +use rome_formatter::write; use rome_js_syntax::JsCallExpression; -use rome_rowan::{AstSeparatedList, SyntaxResult}; +use rome_rowan::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 +47,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); } /// 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 +118,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 +140,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 +155,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 +186,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 +199,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 +219,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..5a24aeb2114 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,226 @@ -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](https://github.com/prettier/prettier/blob/main/src/language-js/print/member-chain.js). +///! +///! 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::JsComputedMemberExpression], 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], +///! ] +///! ``` +mod chain_member; mod groups; mod simple_argument; +use crate::parentheses::NeedsParentheses; 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| { +) -> SyntaxResult { + let mut chain_members = vec![]; + let parent_is_expression_statement = call_expression.resolve_parent().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 +233,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 +257,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 +295,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 +319,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 +433,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 d8cfc897f9a..fe39d468a72 100644 --- a/crates/rome_js_formatter/src/utils/mod.rs +++ b/crates/rome_js_formatter/src/utils/mod.rs @@ -11,28 +11,26 @@ mod member_chain; mod object; mod object_like; mod object_pattern_like; -mod parens; #[cfg(test)] mod quickcheck_utils; mod typescript; +pub(crate) use crate::parentheses::resolve_left_most_expression; use crate::prelude::*; pub(crate) use assignment_like::{should_break_after_operator, JsAnyAssignmentLike}; pub(crate) use binary_like_expression::{ - binary_argument_needs_parens, format_binary_like_expression, JsAnyBinaryLikeExpression, - JsAnyBinaryLikeLeftExpression, + binary_argument_needs_parens, format_binary_like_expression, needs_binary_like_parentheses, + JsAnyBinaryLikeExpression, JsAnyBinaryLikeLeftExpression, }; -pub(crate) use conditional::{resolve_expression, JsAnyConditional}; -pub(crate) use member_chain::format_call_expression; +pub(crate) use conditional::JsAnyConditional; +pub(crate) use member_chain::get_member_chain; pub(crate) use object_like::JsObjectLike; pub(crate) use object_pattern_like::JsObjectPatternLike; -pub(crate) use parens::starts_with_no_lookahead_token; use rome_formatter::{format_args, write, Buffer, VecBuffer}; use rome_js_syntax::{JsAnyExpression, JsAnyStatement, JsInitializerClause, JsLanguage, Modifiers}; use rome_js_syntax::{JsSyntaxKind, JsSyntaxNode, JsSyntaxToken}; use rome_rowan::{AstNode, AstNodeList, Direction}; pub(crate) use simple::*; -use std::fmt::Debug; pub(crate) use string_utils::*; pub(crate) use typescript::should_hug_type; diff --git a/crates/rome_js_formatter/src/utils/parens.rs b/crates/rome_js_formatter/src/utils/parens.rs deleted file mode 100644 index c04453a5abd..00000000000 --- a/crates/rome_js_formatter/src/utils/parens.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::utils::{JsAnyBinaryLikeExpression, JsAnyBinaryLikeLeftExpression}; -use rome_js_syntax::JsAnyExpression; -use rome_rowan::{AstNode, SyntaxResult}; - -/// Returns `true` if the `expression`s first token isn't a lookahead token (a `{`). -/// -/// The [`ExpressionStatement`](https://tc39.es/ecma262/#prod-ExpressionStatement) parsing rule (and others) -/// prohibit exclude tokens like a `{` from the lookahead because of ambiguity. This function allows to -/// test if the expression starts with such a lookahead token. -/// -/// Note: The current implementation only supports testing for `{` yet. Other lookahead tokens like -/// `class, `function` and `let [` are not yet supported. -pub(crate) fn starts_with_no_lookahead_token(expression: JsAnyExpression) -> SyntaxResult { - use JsAnyExpression::*; - - let mut node = resolve_left_most_expression(expression)?; - - let result = loop { - node = match node { - JsObjectExpression(_) => break true, - JsParenthesizedExpression(parenthesized) => parenthesized.expression()?, - JsStaticMemberExpression(member) => member.object()?, - JsComputedMemberExpression(member) => member.object()?, - JsTemplate(template) => { - if let Some(tag) = template.tag() { - tag - } else { - break false; - } - } - JsCallExpression(call) => call.callee()?, - JsConditionalExpression(conditional) => conditional.test()?, - JsPostUpdateExpression(_) => { - break false; - } - TsAsExpression(as_expression) => as_expression.expression()?, - TsNonNullAssertionExpression(non_null) => non_null.expression()?, - _ => { - break false; - } - } - }; - - Ok(result) -} - -/// Resolves the (recursively) left hand side if `expression` is a binary like node or the expression itself. -fn resolve_left_most_expression(expression: JsAnyExpression) -> SyntaxResult { - let mut current = expression; - - let result = loop { - current = match JsAnyBinaryLikeExpression::try_cast_node(current) { - Ok(binary_like) => match binary_like.left()? { - JsAnyBinaryLikeLeftExpression::JsAnyExpression(expression) => expression, - JsAnyBinaryLikeLeftExpression::JsPrivateName(_) => { - break JsAnyExpression::from(binary_like); - } - }, - Err(expression) => { - break expression; - } - }; - }; - - Ok(result) -} diff --git a/crates/rome_js_formatter/tests/specs/js/module/arrow/params.js.snap b/crates/rome_js_formatter/tests/specs/js/module/arrow/params.js.snap index ad476d9c76d..53da40020d6 100644 --- a/crates/rome_js_formatter/tests/specs/js/module/arrow/params.js.snap +++ b/crates/rome_js_formatter/tests/specs/js/module/arrow/params.js.snap @@ -565,13 +565,11 @@ foo(( ) => {}); foo(( - a = ( - ({ - a, + a = (({ + a, - b, - }) => {} - )(), + b, + }) => {})(), ) => {}); foo(( diff --git a/crates/rome_js_formatter/tests/specs/js/module/assignment/assignment.js.snap b/crates/rome_js_formatter/tests/specs/js/module/assignment/assignment.js.snap index 14638be1746..abf99cca1c7 100644 --- a/crates/rome_js_formatter/tests/specs/js/module/assignment/assignment.js.snap +++ b/crates/rome_js_formatter/tests/specs/js/module/assignment/assignment.js.snap @@ -193,15 +193,13 @@ a[b] = c[d]; bazzzzzzzzzzzzzzzzzzzzzzzzzz, ] = d; ({ a, b = c, d: e, f: g = h, ...j } = x); -( - { - aaaaaaaaaa, - bbbbbbbbbb = cccccccccc, - dddddddddd: eeeeeeeeee, - ffffffffff: gggggggggg = hhhhhhhhhh, - ...jjjjjjjjjj - } = x -); +({ + aaaaaaaaaa, + bbbbbbbbbb = cccccccccc, + dddddddddd: eeeeeeeeee, + ffffffffff: gggggggggg = hhhhhhhhhh, + ...jjjjjjjjjj +} = x); (s || (s = Object.create(null)))[i] = !0; (s || (s = Object.create(null))).test = !0; @@ -249,9 +247,12 @@ instanceof_expression = "321321312312ddddddddddddddddddddddd312312312312" instanceof Object; in_expression = { long_key: "123123213123213123edwqdqwdasdasdsaewqewqewqdas" } in "long_key"; -sequence_expression = ( - 33333333333333331, "dsadsadasdsadas", 3, "dsadsadasdsadasdsadsadasdsadas", 5 -); +sequence_expression = + (33333333333333331, + "dsadsadasdsadas", + 3, + "dsadsadasdsadasdsadsadasdsadas", + 5); conditional_expression_1 = this.state.longLongLongLongLongLongLongLongLongTooLongProp === true ? {} : {}; conditional_expression_2 = @@ -389,20 +390,20 @@ a = { ## Lines exceeding width of 80 characters - 41: this_is_a_very_long_key_and_the_assignment_should_be_put_on_the_next_line_this_is_a_very_long_key_and_the_assignment_should_be_put_on_the_next_line_1 = require(); - 42: class_member_with_looooooooooooooooongggggggg_nameeeeeeeeee = class MyLooooonnnngggClassNamee { - 47: this_is_a_very_long_key_and_the_assignment_should_be_put_on_the_next_line_this_is_a_very_long_key_and_the_assignment_should_be_put_on_the_next_line_boolean_true = true; - 48: this_is_a_very_long_key_and_the_assignment_should_be_put_on_the_next_line_this_is_a_very_long_key_and_the_assignment_should_be_put_on_the_next_line_boolean_false = false; - 49: number = 1232132132131231232132112321321321312312321321123213213213123123213211232132132131231232132112321321321312312321321; - 50: number_with_dot = 12321321321312312321321123213213213123123213211232132132131231232132112321321321312312321321.12321321321312312321321; - 58: "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; - 60: "http://example.com/12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; + 39: this_is_a_very_long_key_and_the_assignment_should_be_put_on_the_next_line_this_is_a_very_long_key_and_the_assignment_should_be_put_on_the_next_line_1 = require(); + 40: class_member_with_looooooooooooooooongggggggg_nameeeeeeeeee = class MyLooooonnnngggClassNamee { + 45: this_is_a_very_long_key_and_the_assignment_should_be_put_on_the_next_line_this_is_a_very_long_key_and_the_assignment_should_be_put_on_the_next_line_boolean_true = true; + 46: this_is_a_very_long_key_and_the_assignment_should_be_put_on_the_next_line_this_is_a_very_long_key_and_the_assignment_should_be_put_on_the_next_line_boolean_false = false; + 47: number = 1232132132131231232132112321321321312312321321123213213213123123213211232132132131231232132112321321321312312321321; + 48: number_with_dot = 12321321321312312321321123213213213123123213211232132132131231232132112321321321312312321321.12321321321312312321321; + 56: "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; + 58: "http://example.com/12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; + 60: "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; 62: "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; 64: "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; 66: "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; - 68: "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; - 76: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132; - 78: 1332132131231232131232132132132 + 13321321312312321312321321321321332132131231232131232132132132; - 93: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132 - 107: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132 + 74: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132; + 76: 1332132131231232131232132132132 + 13321321312312321312321321321321332132131231232131232132132132; + 94: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132 + 108: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132 diff --git a/crates/rome_js_formatter/tests/specs/js/module/class/class.js.snap b/crates/rome_js_formatter/tests/specs/js/module/class/class.js.snap index ea944976e34..3a5ef1b9906 100644 --- a/crates/rome_js_formatter/tests/specs/js/module/class/class.js.snap +++ b/crates/rome_js_formatter/tests/specs/js/module/class/class.js.snap @@ -135,7 +135,9 @@ x = class {}; x = class foo extends Boar {}; -x = class aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa extends bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb {}; +x = class aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa extends ( + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +) {}; export class Task { constructor(script, duration, threadCount, ...args) { @@ -143,8 +145,3 @@ export class Task { } } - -## Lines exceeding width of 80 characters - - 53: x = class aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa extends bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb {}; - 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 422be607b47..b7bde99f05c 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 @@ -58,15 +58,13 @@ const f = () => ( ____________third ); -( - ____________first, - ____________second, - ____________third, - ____________third, - ____________third, - ____________third, - ____________third -); +____________first, + ____________second, + ____________third, + ____________third, + ____________third, + ____________third, + ____________third; function a() { return ( @@ -81,15 +79,14 @@ function a() { } const object = { - something: ( - ____________first, + something: + (____________first, ____________second, ____________third, ____________third, ____________third, ____________third, - ____________third - ), + ____________third), }; a, diff --git a/crates/rome_js_formatter/tests/specs/js/module/object/property_object_member.js.snap b/crates/rome_js_formatter/tests/specs/js/module/object/property_object_member.js.snap index 0b659dcaba4..e3f8832353b 100644 --- a/crates/rome_js_formatter/tests/specs/js/module/object/property_object_member.js.snap +++ b/crates/rome_js_formatter/tests/specs/js/module/object/property_object_member.js.snap @@ -145,9 +145,12 @@ const breakAfterColonObject = { { "long-key": "123123213123213123edwqdqwdasdasdsaewqewqewqdas", } in "long-key", - "sequence-expression": ( - 33333333333333331, "dsadsadasdsadas", 3, "dsadsadasdsadasdsadsadasdsadas", 5 - ), + "sequence-expression": + (33333333333333331, + "dsadsadasdsadas", + 3, + "dsadsadasdsadasdsadsadasdsadas", + 5), "conditional-expression-1": this.state.longLongLongLongLongLongLongLongLongTooLongProp === true ? {} @@ -183,9 +186,12 @@ const breakAfterColonObject = { { "long-key": "123123213123213123edwqdqwdasdasdsaewqewqewqdas", } in "long-key", - 5: ( - 33333333333333331, "dsadsadasdsadas", 3, "dsadsadasdsadasdsadsadasdsadas", 5 - ), + 5: + (33333333333333331, + "dsadsadasdsadas", + 3, + "dsadsadasdsadasdsadsadasdsadas", + 5), a: this.state.longLongLongLongLongLongLongLongLongTooLongProp === true @@ -266,10 +272,10 @@ const fluidObject = { 28: "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", 36: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132, 38: 1332132131231232131232132132132 - 13321321312312321312321321321321332132131231232131232132132132, - 57: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132 - 71: .longLongLongLongLongLlongLongLongLongLongLongLongLongLongTooLongPropongLongLongLongTooLongProp === true, - 73: longLongLongLongLongLongLongLongLonlongLongLongLongLongLongLongLongLongTooLongPropgLongLongLongLongTooLongVar || 1337, - 75: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132, - 77: 1332132131231232131232132132132 - 13321321312312321312321321321321332132131231232131232132132132, - 96: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132 + 60: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132 + 74: .longLongLongLongLongLlongLongLongLongLongLongLongLongLongTooLongPropongLongLongLongTooLongProp === true, + 76: longLongLongLongLongLongLongLongLonlongLongLongLongLongLongLongLongLongTooLongPropgLongLongLongLongTooLongVar || 1337, + 78: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132, + 80: 1332132131231232131232132132132 - 13321321312312321312321321321321332132131231232131232132132132, + 102: 13321321312312321311332132131231232131232132132132232132132132 + 1332132131231232131232132132132 diff --git a/crates/rome_js_formatter/tests/specs/js/module/parentheses/parentheses.js.snap b/crates/rome_js_formatter/tests/specs/js/module/parentheses/parentheses.js.snap index 88c24a41810..c601ffe04fd 100644 --- a/crates/rome_js_formatter/tests/specs/js/module/parentheses/parentheses.js.snap +++ b/crates/rome_js_formatter/tests/specs/js/module/parentheses/parentheses.js.snap @@ -45,10 +45,8 @@ async () => { class Foo extends (+Bar) {} class Foo extends (Bar ?? Baz) {} const foo = class extends (Bar ?? Baz) {}; -(1); -( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -); +1; +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; (b + c)``; @@ -59,12 +57,12 @@ async function* f() { yield (a && b); } -const a = () => ({}?.() && a); +const a = () => (({})?.() && a); (list || list2)?.[(list || list2)]; ## Lines exceeding width of 80 characters - 12: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + 11: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/arrows-bind/arrows-bind.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/arrows-bind/arrows-bind.js.snap index 1e951ba8319..dc1ee782f27 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/arrows-bind/arrows-bind.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/arrows-bind/arrows-bind.js.snap @@ -22,7 +22,7 @@ a::(b => c); -a::((b) => c); +a => ({} +::b()``[''].c++ && 0 ? 0 : 0) -+((a) => b); ++(a) => b; +::c +a: +:(b => c) @@ -33,7 +33,7 @@ a::(b => c); ```js a => ({} ::b()``[''].c++ && 0 ? 0 : 0) -((a) => b); +(a) => b; ::c a: :(b => c) diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/assignment/chain.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/assignment/chain.js.snap index d6196194a10..09676c40128 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/assignment/chain.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/assignment/chain.js.snap @@ -40,23 +40,7 @@ a=b=c; ```diff --- Prettier +++ Rome -@@ -1,21 +1,17 @@ - let bifornCringerMoshedPerplexSawder = -- (askTrovenaBeenaDependsRowans = -- glimseGlyphsHazardNoopsTieTie = -- averredBathersBoxroomBuggyNurl = -- anodyneCondosMalateOverateRetinol = -- annularCooeedSplicesWalksWayWay = -- kochabCooieGameOnOboleUnweave); -+ askTrovenaBeenaDependsRowans = -+ glimseGlyphsHazardNoopsTieTie = -+ averredBathersBoxroomBuggyNurl = -+ anodyneCondosMalateOverateRetinol = -+ annularCooeedSplicesWalksWayWay = -+ kochabCooieGameOnOboleUnweave; - - bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans = +@@ -11,11 +11,7 @@ glimseGlyphsHazardNoopsTieTie = x = averredBathersBoxroomBuggyNurl = @@ -88,12 +72,12 @@ a=b=c; ```js let bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans = - glimseGlyphsHazardNoopsTieTie = - averredBathersBoxroomBuggyNurl = - anodyneCondosMalateOverateRetinol = - annularCooeedSplicesWalksWayWay = - kochabCooieGameOnOboleUnweave; + (askTrovenaBeenaDependsRowans = + glimseGlyphsHazardNoopsTieTie = + averredBathersBoxroomBuggyNurl = + anodyneCondosMalateOverateRetinol = + annularCooeedSplicesWalksWayWay = + kochabCooieGameOnOboleUnweave); bifornCringerMoshedPerplexSawder = askTrovenaBeenaDependsRowans = 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/assignment/sequence.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/assignment/sequence.js.snap deleted file mode 100644 index dd142a92ff2..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/assignment/sequence.js.snap +++ /dev/null @@ -1,44 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -for ((i = 0), (len = arr.length); i < len; i++) { - console.log(arr[i]) -} - -for (i = 0, len = arr.length; i < len; i++) { - console.log(arr[i]) -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,4 +1,4 @@ --for (i = 0, len = arr.length; i < len; i++) { -+for ((i = 0), (len = arr.length); i < len; i++) { - console.log(arr[i]); - } - -``` - -# Output - -```js -for ((i = 0), (len = arr.length); i < len; i++) { - console.log(arr[i]); -} - -for (i = 0, len = arr.length; i < len; i++) { - console.log(arr[i]); -} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/async/await-parse.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/async/await-parse.js.snap deleted file mode 100644 index 4943a65dccd..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/async/await-parse.js.snap +++ /dev/null @@ -1,65 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -async function f1() { (await f()).length } -async function g() { - invariant( - (await driver.navigator.getUrl()).substr(-7) - ); -} -function *f2(){ - !(yield a); -} -async function f3() { - a = !await f(); -} -async () => { - new A(await x); - obj[await x]; -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -8,7 +8,7 @@ - !(yield a); - } - async function f3() { -- a = !(await f()); -+ a = !await f(); - } - async () => { - new A(await x); -``` - -# Output - -```js -async function f1() { - (await f()).length; -} -async function g() { - invariant((await driver.navigator.getUrl()).substr(-7)); -} -function* f2() { - !(yield a); -} -async function f3() { - a = !await f(); -} -async () => { - new A(await x); - obj[await x]; -}; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/async/conditional-expression.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/async/conditional-expression.js.snap deleted file mode 100644 index 606adee04e6..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/async/conditional-expression.js.snap +++ /dev/null @@ -1,57 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -async function f() { - const result = typeof fn === 'function' ? await fn() : null; -} - -(async function() { - console.log( - await (true ? Promise.resolve("A") : Promise.resolve("B")) - ); -})() - -async function f2() { - await (spellcheck && spellcheck.setChecking(false)); - await spellcheck && spellcheck.setChecking(false) -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -8,5 +8,5 @@ - - async function f2() { - await (spellcheck && spellcheck.setChecking(false)); -- (await spellcheck) && spellcheck.setChecking(false); -+ await spellcheck && spellcheck.setChecking(false); - } -``` - -# Output - -```js -async function f() { - const result = typeof fn === "function" ? await fn() : null; -} - -(async function () { - console.log(await (true ? Promise.resolve("A") : Promise.resolve("B"))); -})(); - -async function f2() { - await (spellcheck && spellcheck.setChecking(false)); - await spellcheck && spellcheck.setChecking(false); -} -``` - - - 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 deleted file mode 100644 index b537923c2e3..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/async/inline-await.js.snap +++ /dev/null @@ -1,55 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -async function f() { - const admins = (await(db.select('*').from('admins').leftJoin('bla').where('id', 'in', [1,2,3,4]))).map(({id, name})=>({id, name})) -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,9 +1,11 @@ - async function f() { - const admins = ( -- await db -- .select("*") -- .from("admins") -- .leftJoin("bla") -- .where("id", "in", [1, 2, 3, 4]) -+ await ( -+ db -+ .select("*") -+ .from("admins") -+ .leftJoin("bla") -+ .where("id", "in", [1, 2, 3, 4]) -+ ) - ).map(({ id, name }) => ({ id, name })); - } -``` - -# Output - -```js -async function f() { - const admins = ( - await ( - db - .select("*") - .from("admins") - .leftJoin("bla") - .where("id", "in", [1, 2, 3, 4]) - ) - ).map(({ id, name }) => ({ id, name })); -} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/async/nested.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/async/nested.js.snap deleted file mode 100644 index 855c13441a9..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/async/nested.js.snap +++ /dev/null @@ -1,51 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -const getAccountCount = async () => - (await - (await ( - await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME) - ).findItem("My bookmarks") - ).getChildren() - ).length -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,8 +1,8 @@ - const getAccountCount = async () => - ( - await ( -- await ( -- await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME) -- ).findItem("My bookmarks") -+ await (await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)).findItem( -+ "My bookmarks", -+ ) - ).getChildren() - ).length; -``` - -# Output - -```js -const getAccountCount = async () => - ( - await ( - await (await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)).findItem( - "My bookmarks", - ) - ).getChildren() - ).length; -``` - - - 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/bind-expressions/bind_parens.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/bind-expressions/bind_parens.js.snap index 2dda2e0a0ba..fc71e6b9d48 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/bind-expressions/bind_parens.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/bind-expressions/bind_parens.js.snap @@ -84,9 +84,9 @@ f[a::b()]; +a || (b +::c) +::obj.prop -+(void 0); ++void 0; +::func() -+(+0); +++0; +::is(-0) +a: +:(b.c) @@ -157,9 +157,9 @@ f[a::b()]; a || (b ::c) ::obj.prop -(void 0); +void 0; ::func() -(+0); ++0; ::is(-0) a: :(b.c) diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/class-comment/superclass.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/class-comment/superclass.js.snap index 01ba3b756af..8e02f094a34 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/class-comment/superclass.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/class-comment/superclass.js.snap @@ -53,7 +53,7 @@ extends Base ```diff --- Prettier +++ Rome -@@ -2,37 +2,43 @@ +@@ -2,11 +2,11 @@ // comment 2 extends B {} @@ -70,27 +70,20 @@ extends Base class A2 /* a */ extends B {} class A3 extends B /* a */ {} - class A4 extends /* a */ B {} - --(class A5 // comment 1 -- // comment 2 -- extends B {}); -+( -+ class A5 // comment 1 -+ // comment 2 -+ extends B {} -+); +@@ -16,23 +16,25 @@ + // comment 2 + extends B {}); -(class A6 extends B { - // comment1 -+( -+ class A6 -+ extends B // comment1 - // comment2 - // comment3 +- // comment2 +- // comment3 -}); -+ {} -+); ++(class A6 ++ extends B // comment1 ++// comment2 ++// comment3 ++{}); (class A7 /* a */ extends B {}); (class A8 extends B /* a */ {}); @@ -131,19 +124,15 @@ class A2 /* a */ extends B {} class A3 extends B /* a */ {} class A4 extends /* a */ B {} -( - class A5 // comment 1 - // comment 2 - extends B {} -); - -( - class A6 - extends B // comment1 - // comment2 - // comment3 - {} -); +(class A5 // comment 1 + // comment 2 + extends B {}); + +(class A6 + extends B // comment1 +// comment2 +// comment3 +{}); (class A7 /* a */ extends B {}); (class A8 extends B /* a */ {}); diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/class-extends/extends.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/class-extends/extends.js.snap deleted file mode 100644 index 14819fff1e8..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/class-extends/extends.js.snap +++ /dev/null @@ -1,147 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -// "ArrowFunctionExpression" -class a1 extends (() => {}) {} - -// "AssignmentExpression" -class a2 extends (b = c) {} - -// "AwaitExpression" -async function f() { - class a extends (await b) {} -} - -// "BinaryExpression" -class a3 extends (b + c) {} - -// "CallExpression" -class a4 extends b() {} - -// "ClassExpression" -class a5 extends class {} {} - -// "ConditionalExpression" -class a6 extends (b ? c : d) {} - -// "FunctionExpression" -class a7 extends (function() {}) {} - -// "LogicalExpression" -class a8 extends (b || c) {} - -// "MemberExpression" -class a9 extends b.c {} - -// "NewExpression" -class a10 extends (new B()) {} - -// "ObjectExpression" -class a11 extends ({}) {} - -// "SequenceExpression" -class a12 extends (b, c) {} - -// "TaggedTemplateExpression" -class a13 extends `` {} - -// "UnaryExpression" -class a14 extends (void b) {} - -// "UpdateExpression" -class a15 extends (++b) {} - -// "YieldExpression" -function* f2() { - // Flow has a bug parsing it. - // class a extends (yield 1) {} -} - -x = class extends (++b) {} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -22,7 +22,7 @@ - class a6 extends (b ? c : d) {} - - // "FunctionExpression" --class a7 extends function () {} {} -+class a7 extends (function () {}) {} - - // "LogicalExpression" - class a8 extends (b || c) {} -``` - -# Output - -```js -// "ArrowFunctionExpression" -class a1 extends (() => {}) {} - -// "AssignmentExpression" -class a2 extends (b = c) {} - -// "AwaitExpression" -async function f() { - class a extends (await b) {} -} - -// "BinaryExpression" -class a3 extends (b + c) {} - -// "CallExpression" -class a4 extends b() {} - -// "ClassExpression" -class a5 extends class {} {} - -// "ConditionalExpression" -class a6 extends (b ? c : d) {} - -// "FunctionExpression" -class a7 extends (function () {}) {} - -// "LogicalExpression" -class a8 extends (b || c) {} - -// "MemberExpression" -class a9 extends b.c {} - -// "NewExpression" -class a10 extends (new B()) {} - -// "ObjectExpression" -class a11 extends ({}) {} - -// "SequenceExpression" -class a12 extends (b, c) {} - -// "TaggedTemplateExpression" -class a13 extends `` {} - -// "UnaryExpression" -class a14 extends (void b) {} - -// "UpdateExpression" -class a15 extends (++b) {} - -// "YieldExpression" -function* f2() { - // Flow has a bug parsing it. - // class a extends (yield 1) {} -} - -x = class extends (++b) {}; -``` - - - 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 deleted file mode 100644 index bad7dfdb08c..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/classes/assignment.js.snap +++ /dev/null @@ -1,113 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg2 = class extends ( - aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg1 -) { - method () { - console.log("foo"); - } -}; - -foo = class extends bar { - method() { - console.log("foo"); - } -}; - -aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg2 = class extends bar { - method() { - console.log("foo"); - } -}; - -foo = class extends aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg2 { - method() { - console.log("foo"); - } -}; - -module.exports = class A extends B { - method () { - console.log("foo"); - } -}; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -12,17 +12,14 @@ - } - }; - --aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg2 = class extends ( -- bar --) { -+aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg2 = class extends bar { - method() { - console.log("foo"); - } - }; - --foo = class extends ( -- aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg2 --) { -+foo = class extends aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff -+ .gggggggg2 { - method() { - console.log("foo"); - } -``` - -# Output - -```js -aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg2 = class extends ( - aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg1 -) { - method() { - console.log("foo"); - } -}; - -foo = class extends bar { - method() { - console.log("foo"); - } -}; - -aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg2 = class extends bar { - method() { - console.log("foo"); - } -}; - -foo = class extends aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff - .gggggggg2 { - method() { - console.log("foo"); - } -}; - -module.exports = class A extends B { - method() { - console.log("foo"); - } -}; -``` - - -# Lines exceeding max width of 80 characters -``` - 1: aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg2 = class extends ( - 15: aaaaaaaa.bbbbbbbb.cccccccc.dddddddd.eeeeeeee.ffffffff.gggggggg2 = class extends bar { -``` - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/binary-expr.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/binary-expr.js.snap index 5cc701f7c1d..77737960ee3 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/binary-expr.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/binary-expr.js.snap @@ -19,7 +19,7 @@ var a = b || /** @type {string} */ -var a = b || /** @type {string} */ (c); +var a = + b || /** @type {string} */ -+ (c); ++ c; ``` # Output @@ -27,7 +27,7 @@ var a = b || /** @type {string} */ ```js var a = b || /** @type {string} */ - (c); + c; ``` 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 8a9e05cc080..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,29 +73,20 @@ const style2 =/** ```diff --- Prettier +++ Rome -@@ -1,21 +1,24 @@ - // test to make sure comments are attached correctly --let inlineComment = /* some comment */ someReallyLongFunctionCall( -- withLots, -- ofArguments, -+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( + 1 + /** @type {string} */ -+ (value), /** @type {!Foo} */ -+ ({}), ++ value, /** @type {!Foo} */ ++ {}, +); function returnValue() { @@ -104,48 +95,84 @@ const style2 =/** } // Only numberOrString is typecast -@@ -26,9 +29,9 @@ +-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)); // The numberOrString.map CallExpression is typecast - var newArray = /** @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 = /** @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 = 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); +-test(/** @type {!Array} */ (arrOrString).length + 1); ++test(/** @type {number} */ num + 1); ++test(/** @type {!Array} */ arrOrString.length + 1); ++test(/** @type {!Array} */ arrOrString.length + 1); + + const data = functionCall( + arg1, + arg2, +- /** @type {{height: number, width: number}} */ (arg3), ++ /** @type {{height: number, width: number}} */ arg3, + ); + + const style = /** @type {{ +@@ -47,16 +51,16 @@ + marginLeft: number, + marginRight: number, + marginBottom: number, +-}} */ ({ ++}} */ { + width, + height, + ...margins, +-}); ++}; - test(/** @type {number} */ (num) + 1); - test(/** @type {!Array} */ (arrOrString).length + 1); -@@ -57,6 +60,6 @@ + const style2 = /** * @type {{ * width: number, * }} - */ ({ -+*/ ({ ++*/ { width, - }); +-}); ++}; ``` # Output ```js // test to make sure comments are attached correctly -let inlineComment = /* some comment */ ( - someReallyLongFunctionCall(withLots, ofArguments) +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} */ - (value), /** @type {!Foo} */ - ({}), + value, /** @type {!Foo} */ + {}, ); function returnValue() { @@ -153,25 +180,25 @@ function returnValue() { } // Only numberOrString 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)); // 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); -test(/** @type {!Array} */ (arrOrString).length + 1); +test(/** @type {number} */ num + 1); +test(/** @type {!Array} */ arrOrString.length + 1); +test(/** @type {!Array} */ arrOrString.length + 1); const data = functionCall( arg1, arg2, - /** @type {{height: number, width: number}} */ (arg3), + /** @type {{height: number, width: number}} */ arg3, ); const style = /** @type {{ @@ -181,19 +208,19 @@ const style = /** @type {{ marginLeft: number, marginRight: number, marginBottom: number, -}} */ ({ +}} */ { width, height, ...margins, -}); +}; const style2 = /** * @type {{ * width: number, * }} -*/ ({ +*/ { width, -}); +}; ``` 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/comment-placement.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/comment-placement.js.snap index 053c9cf6526..d33f9a5c481 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/comment-placement.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/comment-placement.js.snap @@ -33,37 +33,47 @@ const foo5 = ```diff --- Prettier +++ Rome -@@ -8,6 +8,8 @@ +@@ -1,13 +1,15 @@ +-const foo1 = /** @type {string} */ (value); ++const foo1 = /** @type {string} */ value; + + const foo2 = + /** @type {string} */ +- (value); ++ value; + + const foo3 = /** @type {string} */ - (value); +- (value); ++ value; -const foo4 = /** @type {string} */ (value); +const foo4 = -+ /** @type {string} */ (value); ++ /** @type {string} */ value; -const foo5 = /** @type {string} */ (value); +const foo5 = -+ /** @type {string} */ (value); ++ /** @type {string} */ value; ``` # Output ```js -const foo1 = /** @type {string} */ (value); +const foo1 = /** @type {string} */ value; const foo2 = /** @type {string} */ - (value); + value; const foo3 = /** @type {string} */ - (value); + value; const foo4 = - /** @type {string} */ (value); + /** @type {string} */ value; const foo5 = - /** @type {string} */ (value); + /** @type {string} */ value; ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/extra-spaces-and-asterisks.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/extra-spaces-and-asterisks.js.snap new file mode 100644 index 00000000000..3b3d0662a35 --- /dev/null +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/extra-spaces-and-asterisks.js.snap @@ -0,0 +1,57 @@ +--- +source: crates/rome_js_formatter/tests/prettier_tests.rs +--- + +# Input + +```js +const foo1 = /** @type {!Foo} */(bar); +const foo2 = /** @type {!Foo} **/(bar); +const foo3 = /** @type {!Foo} * */(bar); +const foo4 = /** @type {!Foo} ***/(bar); +const foo5 = /** @type {!Foo} * * */(bar); +const foo6 = /** @type {!Foo} *****/(bar); +const foo7 = /** @type {!Foo} * * * * */(bar); +const foo8 = /** @type {!Foo} ** * * */(bar); +``` + + +# Prettier differences + +```diff +--- Prettier ++++ Rome +@@ -1,8 +1,8 @@ +-const foo1 = /** @type {!Foo} */ (bar); +-const foo2 = /** @type {!Foo} **/ (bar); +-const foo3 = /** @type {!Foo} * */ (bar); +-const foo4 = /** @type {!Foo} ***/ (bar); +-const foo5 = /** @type {!Foo} * * */ (bar); +-const foo6 = /** @type {!Foo} *****/ (bar); +-const foo7 = /** @type {!Foo} * * * * */ (bar); +-const foo8 = /** @type {!Foo} ** * * */ (bar); ++const foo1 = /** @type {!Foo} */ bar; ++const foo2 = /** @type {!Foo} **/ bar; ++const foo3 = /** @type {!Foo} * */ bar; ++const foo4 = /** @type {!Foo} ***/ bar; ++const foo5 = /** @type {!Foo} * * */ bar; ++const foo6 = /** @type {!Foo} *****/ bar; ++const foo7 = /** @type {!Foo} * * * * */ bar; ++const foo8 = /** @type {!Foo} ** * * */ bar; +``` + +# Output + +```js +const foo1 = /** @type {!Foo} */ bar; +const foo2 = /** @type {!Foo} **/ bar; +const foo3 = /** @type {!Foo} * */ bar; +const foo4 = /** @type {!Foo} ***/ bar; +const foo5 = /** @type {!Foo} * * */ bar; +const foo6 = /** @type {!Foo} *****/ bar; +const foo7 = /** @type {!Foo} * * * * */ bar; +const foo8 = /** @type {!Foo} ** * * */ bar; +``` + + + 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 dacdadb8445..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,37 +26,41 @@ const helpers = /** @type {Helpers} */ (( ```diff --- Prettier +++ Rome -@@ -8,6 +8,8 @@ +@@ -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 () { ++ return something; ++})(); // TODO: @param is misplaced https://github.com/prettier/prettier/issues/5850 - const helpers = /** @type {Helpers} */ ( -- /** @param {Partial} helpers */ +-const helpers = /** @type {Helpers} */ ( ++const helpers = /** @type {Helpers} */ + /** @param {Partial} helpers */ - ((helpers = {}) => helpers)() -+ ( -+ /** @param {Partial} helpers */ -+ (helpers = {}) => helpers -+ )() - ); +-); ++ ((helpers = {}) => helpers)(); ``` # Output ```js -const helpers1 = /** @type {Helpers} */ (((helpers = {}) => helpers)()); +const helpers1 = /** @type {Helpers} */ ((helpers = {}) => helpers)(); -const helpers2 = /** @type {Helpers} */ ( - (function () { - return something; - })() -); +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 - )() -); +const helpers = /** @type {Helpers} */ + /** @param {Partial} 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 7b3e239a056..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 @@ -29,45 +29,67 @@ const test = /** @type (function (*): ?|undefined) */ (foo); ```diff --- Prettier +++ Rome -@@ -1,11 +1,11 @@ - /** @type {Object} */ (myObject.property).someProp = true; +@@ -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; - 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(NewThing.onTemplateChange, rationaleField, typeField) ++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(NewThing.onTemplateChange, rationaleField, typeField) ++const test = /** @type (function (*): ?|undefined) */ goog.partial( ++ NewThing.onTemplateChange, ++ rationaleField, ++ typeField, + ); + +-const model = /** @type {?{getIndex: Function}} */ (model); ++const model = /** @type {?{getIndex: Function}} */ model; + +-const foo = /** @type {string} */ (bar); ++const foo = /** @type {string} */ bar; + +-const test = /** @type (function (*): ?|undefined) */ (foo); ++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(NewThing.onTemplateChange, rationaleField, typeField) +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( + NewThing.onTemplateChange, + rationaleField, + typeField, ); -const model = /** @type {?{getIndex: Function}} */ (model); +const model = /** @type {?{getIndex: Function}} */ model; -const foo = /** @type {string} */ (bar); +const foo = /** @type {string} */ bar; -const test = /** @type (function (*): ?|undefined) */ (foo); +const test = /** @type (function (*): ?|undefined) */ foo; ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-8045.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-8045.js.snap index 487efb63da6..3b9bf50f337 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-8045.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-8045.js.snap @@ -38,7 +38,7 @@ function jsdocCastInReturn() { -const myLongVariableName = - /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ (fooBarBaz); +const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ -+ (fooBarBaz); ++ fooBarBaz; function jsdocCastInReturn() { - return /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ ( @@ -49,8 +49,9 @@ function jsdocCastInReturn() { -const myLongVariableName = - /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ +- (fooBarBaz); +const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ - (fooBarBaz); ++ fooBarBaz; function jsdocCastInReturn() { - return ( @@ -62,8 +63,9 @@ function jsdocCastInReturn() { -const myLongVariableName = - /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ +- (fooBarBaz); +const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ - (fooBarBaz); ++ fooBarBaz; function jsdocCastInReturn() { - return ( @@ -78,21 +80,21 @@ function jsdocCastInReturn() { ```js const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ - (fooBarBaz); + fooBarBaz; function jsdocCastInReturn() { return /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ fooBarBaz; } const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ - (fooBarBaz); + fooBarBaz; function jsdocCastInReturn() { return /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ fooBarBaz; } const myLongVariableName = /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ - (fooBarBaz); + fooBarBaz; function jsdocCastInReturn() { return /** @type {ThisIsAVeryLongTypeThatShouldTriggerLineWrapping} */ fooBarBaz; diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-9358.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-9358.js.snap new file mode 100644 index 00000000000..f32b0548005 --- /dev/null +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/issue-9358.js.snap @@ -0,0 +1,50 @@ +--- +source: crates/rome_js_formatter/tests/prettier_tests.rs +--- + +# Input + +```js +const fooooba1 = /** @type {Array.} */ (fooobaarbazzItems || foo); +const fooooba2 = /** @type {Array.} */ (fooobaarbazzItems + foo); +const fooooba3 = /** @type {Array.} */ (fooobaarbazzItems || foo) ? foo : bar; +``` + + +# Prettier differences + +```diff +--- Prettier ++++ Rome +@@ -1,9 +1,7 @@ +-const fooooba1 = /** @type {Array.} */ ( +- fooobaarbazzItems || foo +-); +-const fooooba2 = /** @type {Array.} */ ( +- fooobaarbazzItems + foo +-); ++const fooooba1 = /** @type {Array.} */ ++ (fooobaarbazzItems || foo); ++const fooooba2 = /** @type {Array.} */ ++ (fooobaarbazzItems + foo); + const fooooba3 = /** @type {Array.} */ ( + fooobaarbazzItems || foo + ) +``` + +# Output + +```js +const fooooba1 = /** @type {Array.} */ + (fooobaarbazzItems || foo); +const fooooba2 = /** @type {Array.} */ + (fooobaarbazzItems + foo); +const fooooba3 = /** @type {Array.} */ ( + fooobaarbazzItems || foo +) + ? foo + : bar; +``` + + + 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 22a50104e31..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 18c3e731af0..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 @@ -21,32 +21,34 @@ const BarImpl = /** @type {BarConstructor} */ ( ```diff --- Prettier +++ Rome -@@ -2,9 +2,7 @@ +@@ -1,10 +1,7 @@ +-foo = /** @type {!Foo} */ (/** @type {!Baz} */ (baz).bar); ++foo = /** @type {!Foo} */ /** @type {!Baz} */ baz.bar; - const BarImpl = /** @type {BarConstructor} */ ( +-const BarImpl = /** @type {BarConstructor} */ ( ++const BarImpl = /** @type {BarConstructor} */ /** @type {unknown} */ - ( - function Bar() { - throw new Error("Internal error: Illegal constructor"); - } - ) +-); + function Bar() { + throw new Error("Internal error: Illegal constructor"); -+ } - ); ++ }; ``` # Output ```js -foo = /** @type {!Foo} */ (/** @type {!Baz} */ (baz).bar); +foo = /** @type {!Foo} */ /** @type {!Baz} */ baz.bar; -const BarImpl = /** @type {BarConstructor} */ ( +const BarImpl = /** @type {BarConstructor} */ /** @type {unknown} */ function Bar() { throw new Error("Internal error: Illegal constructor"); - } -); + }; ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/non-casts.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/non-casts.js.snap deleted file mode 100644 index 2c717d227d1..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/non-casts.js.snap +++ /dev/null @@ -1,81 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -/* @type { } */ -z(x => { - (foo)((bar)(2+(3))) - return (1); -}) - -/** @type { } */ -z(x => { - (foo)((bar)(2+(3))) - return (1); -}) - -/** @type {number} */ -let q = z(x => { - return (1); -}) - -const w1 = /** @typefoo Foo */ (value); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,12 +1,12 @@ - /* @type { } */ - z((x) => { -- foo(bar(2 + 3)); -+ (foo)((bar)(2 + (3))); - return 1; - }); - - /** @type { } */ - z((x) => { -- foo(bar(2 + 3)); -+ (foo)((bar)(2 + (3))); - return 1; - }); - -@@ -15,4 +15,4 @@ - return 1; - }); - --const w1 = /** @typefoo Foo */ value; -+const w1 = /** @typefoo Foo */ (value); -``` - -# Output - -```js -/* @type { } */ -z((x) => { - (foo)((bar)(2 + (3))); - return 1; -}); - -/** @type { } */ -z((x) => { - (foo)((bar)(2 + (3))); - return 1; -}); - -/** @type {number} */ -let q = z((x) => { - return 1; -}); - -const w1 = /** @typefoo Foo */ (value); -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/object-with-comment.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/object-with-comment.js.snap index 95b305712d1..15bafcfd80f 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/object-with-comment.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/object-with-comment.js.snap @@ -24,34 +24,38 @@ const objectWithComment2 = /** @type MyType */ ( /* comment */ { ```diff --- Prettier +++ Rome -@@ -6,7 +6,8 @@ - ); +@@ -1,12 +1,9 @@ +-const objectWithComment = /** @type MyType */ ( ++const objectWithComment = /** @type MyType */ + /* comment */ + { + foo: bar, +- } +-); ++ }; - const objectWithComment2 = /** @type MyType */ ( +-const objectWithComment2 = /** @type MyType */ ( - /* comment */ { -+ /* comment */ -+ { - foo: bar, - } - ); +- foo: bar, +- } +-); ++const objectWithComment2 = /** @type MyType */ /* comment */ { ++ foo: bar, ++}; ``` # Output ```js -const objectWithComment = /** @type MyType */ ( +const objectWithComment = /** @type MyType */ /* comment */ { foo: bar, - } -); + }; -const objectWithComment2 = /** @type MyType */ ( - /* comment */ - { - foo: bar, - } -); +const objectWithComment2 = /** @type MyType */ /* comment */ { + foo: bar, +}; ``` 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-closure-typecast/superclass.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/superclass.js.snap new file mode 100644 index 00000000000..db1c0a6b5a5 --- /dev/null +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/superclass.js.snap @@ -0,0 +1,29 @@ +--- +source: crates/rome_js_formatter/tests/prettier_tests.rs +--- + +# Input + +```js +class Foo extends /** @type {string} */ (Bar) {} +``` + + +# Prettier differences + +```diff +--- Prettier ++++ Rome +@@ -1 +1 @@ +-class Foo extends /** @type {string} */ (Bar) {} ++class Foo extends /** @type {string} */ Bar {} +``` + +# Output + +```js +class Foo extends /** @type {string} */ Bar {} +``` + + + diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/ways-to-specify-type.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/ways-to-specify-type.js.snap new file mode 100644 index 00000000000..a348cb594f0 --- /dev/null +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments-closure-typecast/ways-to-specify-type.js.snap @@ -0,0 +1,75 @@ +--- +source: crates/rome_js_formatter/tests/prettier_tests.rs +--- + +# Input + +```js +const curlyBraces = /** @type {string} */ (foo); +const curlyBraces2 = /**@type {string} */ (foo); +const noWhitespace = /** @type{string} */ (foo); +const noWhitespace2 = /**@type{string} */ (foo); +const noBraces = /** @type string */ (foo); +const parens = /** @type (string | number) */ (foo); + +// Prettier just searches for "@type" and doesn't check the syntax of types. +const v1 = /** @type {} */ (value); +const v2 = /** @type {}} */ (value); +const v3 = /** @type } */ (value); +const v4 = /** @type { */ (value); +const v5 = /** @type {{} */ (value); +``` + + +# Prettier differences + +```diff +--- Prettier ++++ Rome +@@ -1,13 +1,13 @@ +-const curlyBraces = /** @type {string} */ (foo); +-const curlyBraces2 = /**@type {string} */ (foo); +-const noWhitespace = /** @type{string} */ (foo); +-const noWhitespace2 = /**@type{string} */ (foo); +-const noBraces = /** @type string */ (foo); +-const parens = /** @type (string | number) */ (foo); ++const curlyBraces = /** @type {string} */ foo; ++const curlyBraces2 = /**@type {string} */ foo; ++const noWhitespace = /** @type{string} */ foo; ++const noWhitespace2 = /**@type{string} */ foo; ++const noBraces = /** @type string */ foo; ++const parens = /** @type (string | number) */ foo; + + // Prettier just searches for "@type" and doesn't check the syntax of types. +-const v1 = /** @type {} */ (value); +-const v2 = /** @type {}} */ (value); +-const v3 = /** @type } */ (value); +-const v4 = /** @type { */ (value); +-const v5 = /** @type {{} */ (value); ++const v1 = /** @type {} */ value; ++const v2 = /** @type {}} */ value; ++const v3 = /** @type } */ value; ++const v4 = /** @type { */ value; ++const v5 = /** @type {{} */ value; +``` + +# Output + +```js +const curlyBraces = /** @type {string} */ foo; +const curlyBraces2 = /**@type {string} */ foo; +const noWhitespace = /** @type{string} */ foo; +const noWhitespace2 = /**@type{string} */ foo; +const noBraces = /** @type string */ foo; +const parens = /** @type (string | number) */ foo; + +// Prettier just searches for "@type" and doesn't check the syntax of types. +const v1 = /** @type {} */ value; +const v2 = /** @type {}} */ value; +const v3 = /** @type } */ value; +const v4 = /** @type { */ value; +const v5 = /** @type {{} */ value; +``` + + + 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 63ab78fdadb..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,24 +84,7 @@ foo({} ```diff --- Prettier +++ Rome -@@ -9,13 +9,14 @@ - }); - - // Missing one level of indentation because of the comment --const rootEpic = (actions, store) => -+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 +65,5 @@ +@@ -64,5 +64,5 @@ // The closing paren is printed on the same line as the comment foo( {}, @@ -124,14 +107,13 @@ throw new ProcessSystemError({ }); // Missing one level of indentation because of the comment -const rootEpic = (actions, store) => ( +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( @@ -187,6 +169,6 @@ foo( # Lines exceeding max width of 80 characters ``` - 32: 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 943160ca7bc..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,99 +134,7 @@ function inlineComment() { ```diff --- Prettier +++ Rome -@@ -18,72 +18,84 @@ - - function logical() { - return ( -- // Reason for 42 -- 42 && 84 -+ ( -+ // Reason for 42 -+ 42 -+ ) && 84 - ); - } - - function binary() { - return ( -- // Reason for 42 -- 42 * 84 -+ ( -+ // Reason for 42 -+ 42 -+ ) * 84 - ); - } - - function binaryInBinaryLeft() { - return ( -- // Reason for 42 -- 42 * 84 + 2 -+ ( -+ // Reason for 42 -+ 42 -+ ) * 84 + 2 - ); - } - - function binaryInBinaryRight() { - return ( -- // Reason for 42 -- 42 + 84 * 2 -+ ( -+ // Reason for 42 -+ 42 -+ ) + 84 * 2 - ); - } - - function conditional() { - return ( - // Reason for 42 -- 42 ? 1 : 2 -- ); -+ 42 -+ ) -+ ? 1 -+ : 2; - } - - function binaryInConditional() { - return ( - // Reason for 42 -- 42 * 3 ? 1 : 2 -- ); -+ 42 -+ ) * 3 -+ ? 1 -+ : 2; - } - - function call() { - return ( - // Reason for a -- a() -- ); -+ a -+ )(); - } - - function memberInside() { - return ( - // Reason for a.b -- a.b.c -- ); -+ a.b -+ ).c; - } - - function memberOutside() { - return ( - // Reason for a -- a.b.c -- ); -+ a -+ ).b.c; +@@ -80,10 +80,12 @@ } function memberInAndOutWithCalls() { @@ -236,12 +144,14 @@ function inlineComment() { - .c.d(); + return ( + // Reason for a -+ aFunction.b() -+ ).c.d(); ++ aFunction ++ .b() ++ .c.d() ++ ); } function excessiveEverything() { -@@ -103,18 +115,22 @@ +@@ -103,8 +105,8 @@ function sequenceExpressionInside() { return ( @@ -252,21 +162,13 @@ function inlineComment() { ); } - function taggedTemplate() { - return ( - // Reason for a -- a`b` -- ); -+ a -+ )`b`; +@@ -116,5 +118,7 @@ } function inlineComment() { - return /* hi */ 42 || 42; + return ( -+ ( -+ /* hi */ 42 -+ ) || 42 ++ /* hi */ 42 || 42 + ); } ``` @@ -294,84 +196,74 @@ function numericLiteralNoParen() { function logical() { return ( - ( - // Reason for 42 - 42 - ) && 84 + // Reason for 42 + 42 && 84 ); } function binary() { return ( - ( - // Reason for 42 - 42 - ) * 84 + // Reason for 42 + 42 * 84 ); } function binaryInBinaryLeft() { return ( - ( - // Reason for 42 - 42 - ) * 84 + 2 + // Reason for 42 + 42 * 84 + 2 ); } function binaryInBinaryRight() { return ( - ( - // Reason for 42 - 42 - ) + 84 * 2 + // Reason for 42 + 42 + 84 * 2 ); } function conditional() { return ( // Reason for 42 - 42 - ) - ? 1 - : 2; + 42 ? 1 : 2 + ); } function binaryInConditional() { return ( // Reason for 42 - 42 - ) * 3 - ? 1 - : 2; + 42 * 3 ? 1 : 2 + ); } function call() { return ( // Reason for a - a - )(); + a() + ); } function memberInside() { return ( // Reason for a.b - a.b - ).c; + a.b.c + ); } function memberOutside() { return ( // Reason for a - a - ).b.c; + a.b.c + ); } function memberInAndOutWithCalls() { return ( // Reason for a - aFunction.b() - ).c.d(); + aFunction + .b() + .c.d() + ); } function excessiveEverything() { @@ -399,15 +291,13 @@ function sequenceExpressionInside() { function taggedTemplate() { return ( // Reason for a - a - )`b`; + a`b` + ); } function inlineComment() { return ( - ( - /* hi */ 42 - ) || 42 + /* hi */ 42 || 42 ); } ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/comments/trailing-jsdocs.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/comments/trailing-jsdocs.js.snap index 6e13ff0d010..1fbf2ca99b0 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/comments/trailing-jsdocs.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/comments/trailing-jsdocs.js.snap @@ -35,14 +35,10 @@ const CONNECTION_STATUS = exports.CONNECTION_STATUS = { ```diff --- Prettier +++ Rome -@@ -1,27 +1,22 @@ --const CONNECTION_STATUS = (exports.CONNECTION_STATUS = { -+const CONNECTION_STATUS = exports.CONNECTION_STATUS = { - CLOSED: Object.freeze({ kind: "CLOSED" }), - CONNECTED: Object.freeze({ kind: "CONNECTED" }), +@@ -4,24 +4,19 @@ CONNECTING: Object.freeze({ kind: "CONNECTING" }), NOT_CONNECTED: Object.freeze({ kind: "NOT_CONNECTED" }), --}); + }); - -/* A comment */ -/** @@ -64,7 +60,6 @@ const CONNECTION_STATUS = exports.CONNECTION_STATUS = { -/** - * A single unit of data exchanged between the peers of a `ReactiveSocket`. - */ -+}; +/* A comment */ /** +* A type that can be written to a buffer. +*/ /** @@ -86,12 +81,12 @@ const CONNECTION_STATUS = exports.CONNECTION_STATUS = { # Output ```js -const CONNECTION_STATUS = exports.CONNECTION_STATUS = { +const CONNECTION_STATUS = (exports.CONNECTION_STATUS = { CLOSED: Object.freeze({ kind: "CLOSED" }), CONNECTED: Object.freeze({ kind: "CONNECTED" }), CONNECTING: Object.freeze({ kind: "CONNECTING" }), NOT_CONNECTED: Object.freeze({ kind: "NOT_CONNECTED" }), -}; +}); /* A comment */ /** * A type that can be written to a buffer. */ /** diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/conditional/comments.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/conditional/comments.js.snap index 5e26a4b9e1f..973a812cc58 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/conditional/comments.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/conditional/comments.js.snap @@ -122,35 +122,31 @@ c */? foo : bar : bar; 4 === util.inspect.length - ? // node <= 0.8.x - function (v, colors) { -+ ? (function (v, colors) { ++ ? function (v, colors) { + // node <= 0.8.x return util.inspect(v, void 0, void 0, colors); -- } + } - : // node > 0.8.x - function (v, colors) { -+ }) -+ : (function (v, colors) { ++ : function (v, colors) { + // node > 0.8.x return util.inspect(v, { colors: colors }); -- }; -+ }); + }; var inspect = 4 === util.inspect.length - ? // node <= 0.8.x - function (v, colors) { -+ ? (function (v, colors) { ++ ? function (v, colors) { + // node <= 0.8.x return util.inspect(v, void 0, void 0, colors); -- } + } - : // node > 0.8.x - function (v, colors) { -+ }) -+ : (function (v, colors) { ++ : function (v, colors) { + // node > 0.8.x return util.inspect(v, { colors: colors }); -- }; -+ }); + }; const extractTextPluginOptions = shouldUseRelativeAssetPaths - ? // Making sure that the publicPath goes back to to build folder. @@ -234,25 +230,25 @@ c */? foo : bar : bar; ```js var inspect = 4 === util.inspect.length - ? (function (v, colors) { + ? function (v, colors) { // node <= 0.8.x return util.inspect(v, void 0, void 0, colors); - }) - : (function (v, colors) { + } + : function (v, colors) { // node > 0.8.x return util.inspect(v, { colors: colors }); - }); + }; var inspect = 4 === util.inspect.length - ? (function (v, colors) { + ? function (v, colors) { // node <= 0.8.x return util.inspect(v, void 0, void 0, colors); - }) - : (function (v, colors) { + } + : function (v, colors) { // node > 0.8.x return util.inspect(v, { colors: colors }); - }); + }; const extractTextPluginOptions = shouldUseRelativeAssetPaths // Making sure that the publicPath goes back to to build folder. diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/export-default/binary_and_template.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/export-default/binary_and_template.js.snap new file mode 100644 index 00000000000..4322f88d229 --- /dev/null +++ b/crates/rome_js_formatter/tests/specs/prettier/js/export-default/binary_and_template.js.snap @@ -0,0 +1,29 @@ +--- +source: crates/rome_js_formatter/tests/prettier_tests.rs +--- + +# Input + +```js +export default (function() {} + foo)``; +``` + + +# Prettier differences + +```diff +--- Prettier ++++ Rome +@@ -1 +1 @@ +-export default (function () {} + foo)``; ++export default ((function () {}) + foo)``; +``` + +# Output + +```js +export default ((function () {}) + foo)``; +``` + + + diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/classes/new.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/export-default/body.js.snap similarity index 52% rename from crates/rome_js_formatter/tests/specs/prettier/js/classes/new.js.snap rename to crates/rome_js_formatter/tests/specs/prettier/js/export-default/body.js.snap index 927e0c08a70..36114ba0da4 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/classes/new.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/export-default/body.js.snap @@ -5,8 +5,7 @@ source: crates/rome_js_formatter/tests/prettier_tests.rs # Input ```js -new class {}; -new Ctor(class {}); +export default (class {}[1] = 1); ``` @@ -15,17 +14,15 @@ new Ctor(class {}); ```diff --- Prettier +++ Rome -@@ -1,2 +1,2 @@ --new (class {})(); -+new class {}(); - new Ctor(class {}); +@@ -1 +1 @@ +-export default (class {}[1] = 1); ++export default ((class {})[1] = 1); ``` # Output ```js -new class {}(); -new Ctor(class {}); +export default ((class {})[1] = 1); ``` 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 new file mode 100644 index 00000000000..41e86369725 --- /dev/null +++ b/crates/rome_js_formatter/tests/specs/prettier/js/export-default/class_instance.js.snap @@ -0,0 +1,29 @@ +--- +source: crates/rome_js_formatter/tests/prettier_tests.rs +--- + +# Input + +```js +export default (class {}.getInstance()); +``` + + +# Prettier differences + +```diff +--- Prettier ++++ Rome +@@ -1 +1 @@ +-export default (class {}.getInstance()); ++export default (class {}).getInstance(); +``` + +# Output + +```js +export default (class {}).getInstance(); +``` + + + diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/first-argument-expansion/test.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/first-argument-expansion/test.js.snap index 1f20b479f3d..6592cec2c55 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/first-argument-expansion/test.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/first-argument-expansion/test.js.snap @@ -196,15 +196,7 @@ func((args) => { compose( (a) => { -@@ -86,19 +74,20 @@ - ); - - somthing.reduce(function (item, thing) { -- return (thing.blah = item); -+ return thing.blah = item; - }, {}); - - somthing.reduce(function (item, thing) { +@@ -93,12 +81,13 @@ return thing.push(item); }, []); @@ -338,7 +330,7 @@ compose( ); somthing.reduce(function (item, thing) { - return thing.blah = item; + return (thing.blah = item); }, {}); somthing.reduce(function (item, thing) { diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/function/function_expression.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/function/function_expression.js.snap index 330bba0ef63..52bfc70eec5 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/function/function_expression.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/function/function_expression.js.snap @@ -24,36 +24,36 @@ new function() {}; ```diff --- Prettier +++ Rome -@@ -1,5 +1,5 @@ +@@ -1,4 +1,4 @@ -(function () {}.length); --typeof function () {}; +(function () {}).length; -+typeof (function () {}); + typeof function () {}; export default (function () {})(); (function () {})()``; - (function () {})``; -@@ -8,4 +8,4 @@ +@@ -6,6 +6,6 @@ + new (function () {})(); + (function () {}); a = function f() {} || b; - (function () {} && a); +-(function () {} && a); ++((function () {}) && a); a + function () {}; --new (function () {})(); -+new function () {}(); + new (function () {})(); ``` # Output ```js (function () {}).length; -typeof (function () {}); +typeof function () {}; export default (function () {})(); (function () {})()``; (function () {})``; new (function () {})(); (function () {}); a = function f() {} || b; -(function () {} && a); +((function () {}) && a); a + function () {}; -new function () {}(); +new (function () {})(); ``` 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 d8fe3511b6f..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 @@ -27,7 +27,8 @@ method().then(x => x) + .then((x) => x)["abc"]((x) => x)[abc]((x) => x); -({}.a().b()); - ({}.a().b()); +-({}.a().b()); ++({}).a().b(); +({}).a().b(); ``` @@ -37,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 deleted file mode 100644 index 3239dad828f..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/new-expression/new_expression.js.snap +++ /dev/null @@ -1,50 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -new (memoize.Cache || MapCache) -new (typeof this == "function" ? this : Dict()) -new (createObj()).prop(a()); -new (x()``.y)(); -new e[f().x].y(); -new e[f()].y(); -new (a().b)(); -new (a().b().c)(); -new (a``()); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,6 +1,6 @@ - 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(); -``` - -# Output - -```js -new (memoize.Cache || MapCache)(); -new (typeof this == "function" ? this : Dict())(); -new (createObj()).prop(a()); -new (x()``.y)(); -new e[f().x].y(); -new e[f()].y(); -new (a().b)(); -new (a().b().c)(); -new (a``())(); -``` - - - 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 887934fec43..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 @@ -93,12 +93,7 @@ aReallyLongLine012345678901234567890123456789012345678901234567890123456789 * ```diff --- Prettier +++ Rome -@@ -13,13 +13,13 @@ - x; - ("h" + "i").repeat(10); - x; --1, 2; -+(1, 2); +@@ -17,9 +17,9 @@ x; (() => {})(); x; @@ -110,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); @@ -142,7 +134,7 @@ x; x; ("h" + "i").repeat(10); x; -(1, 2); +1, 2; x; (() => {})(); x; @@ -214,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/objects/expression.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/objects/expression.js.snap index 4b241334435..a702da396d3 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/objects/expression.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/objects/expression.js.snap @@ -37,79 +37,57 @@ const a3 = { ```diff --- Prettier +++ Rome -@@ -1,25 +1,29 @@ - () => ({}``); +@@ -1,10 +1,12 @@ +-() => ({}``); -({}``); -a = () => ({}.x); -+({})``; -+a = () => ({}).x; - ({} && a, b); +-({} && a, b); -({}::b, 0); -({}::b()``[""].c++ && 0 ? 0 : 0, 0); +-({}(), 0); ++() => ({})``; ++({})``; ++a = () => ({}).x; ++({}) && a, b; +({} +::b, 0) +({} +::b()``[''].c++ && 0 ? 0 : 0, 0) - ({}(), 0); ++({})(), 0; ({} = 0); --({} = 0), 1; -+(({} = 0), 1); + ({} = 0), 1; - const a1 = { - someKey: (shortName, shortName), - }; - - const a2 = { -- someKey: -- (longLongLongLongLongLongLongLongLongLongLongLongLongLongName, shortName), -+ someKey: ( -+ longLongLongLongLongLongLongLongLongLongLongLongLongLongName, shortName -+ ), - }; - - const a3 = { -- someKey: -- (longLongLongLongLongLongLongLongLongLongLongLongLongLongName, -+ someKey: ( -+ longLongLongLongLongLongLongLongLongLongLongLongLongLongName, - longLongLongLongLongLongLongLongLongLongLongLongLongLongName, -- longLongLongLongLongLongLongLongLongLongLongLongLongLongName), -+ longLongLongLongLongLongLongLongLongLongLongLongLongLongName -+ ), - }; ``` # Output ```js -() => ({}``); +() => ({})``; ({})``; a = () => ({}).x; -({} && a, b); +({}) && a, b; ({} ::b, 0) ({} ::b()``[''].c++ && 0 ? 0 : 0, 0) -({}(), 0); +({})(), 0; ({} = 0); -(({} = 0), 1); +({} = 0), 1; const a1 = { someKey: (shortName, shortName), }; const a2 = { - someKey: ( - longLongLongLongLongLongLongLongLongLongLongLongLongLongName, shortName - ), + someKey: + (longLongLongLongLongLongLongLongLongLongLongLongLongLongName, shortName), }; const a3 = { - someKey: ( - longLongLongLongLongLongLongLongLongLongLongLongLongLongName, + someKey: + (longLongLongLongLongLongLongLongLongLongLongLongLongLongName, longLongLongLongLongLongLongLongLongLongLongLongLongLongName, - longLongLongLongLongLongLongLongLongLongLongLongLongLongName - ), + longLongLongLongLongLongLongLongLongLongLongLongLongLongName), }; ``` 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 751436bb1ca..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,40 +121,78 @@ 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,8 +62,8 @@ - (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 f = () => ({}?.()); +-let g = () => ({}?.b); +-a = () => ({}?.() && a); +-a = () => ({}?.()() && a); +-a = () => ({}?.().b && a); +-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()); ++let f = () => ({})?.(); ++let g = () => ({})?.b; ++a = () => (({})?.() && a); ++a = () => (({})?.()() && a); ++a = () => (({})?.().b && a); ++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(); + + new (foo?.bar)(); + new (foo?.bar())(); ``` # Output @@ -161,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; @@ -187,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]; @@ -210,28 +259,26 @@ a?.[b ? c : d]; (function () {})?.(); (() => f)?.(); (() => f)?.x; -(a?.(x)).x; -( +a?.(x).x; +(aaaaaaaaaaaaaaaaaaaaaaaa && aaaaaaaaaaaaaaaaaaaaaaaa && - aaaaaaaaaaaaaaaaaaaaaaaa && - aaaaaaaaaaaaaaaaaaaaaaaa -)?.(); - -let f = () => ({}?.()); -let g = () => ({}?.b); -a = () => ({}?.() && a); -a = () => ({}?.()() && a); -a = () => ({}?.().b && a); -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()); + aaaaaaaaaaaaaaaaaaaaaaaa)?.(); + +let f = () => ({})?.(); +let g = () => ({})?.b; +a = () => (({})?.() && a); +a = () => (({})?.()() && a); +a = () => (({})?.().b && a); +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(); new (foo?.bar)(); new (foo?.bar())(); diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/optional-chaining/eval.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/optional-chaining/eval.js.snap deleted file mode 100644 index 546740139b4..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/optional-chaining/eval.js.snap +++ /dev/null @@ -1,81 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -// https://github.com/babel/babel/pull/11850 - -let foo; - -/* indirect eval calls */ -eval?.(foo); - -(eval)?.(foo); - -eval?.()(); - -eval?.().foo; - -/* direct eval calls */ - -eval()?.(); - -eval()?.foo; - -/* plain function calls */ - -foo.eval?.(foo); - -eval.foo?.(foo); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -5,7 +5,7 @@ - /* indirect eval calls */ - eval?.(foo); - --eval?.(foo); -+(eval)?.(foo); - - eval?.()(); - -``` - -# Output - -```js -// https://github.com/babel/babel/pull/11850 - -let foo; - -/* indirect eval calls */ -eval?.(foo); - -(eval)?.(foo); - -eval?.()(); - -eval?.().foo; - -/* direct eval calls */ - -eval()?.(); - -eval()?.foo; - -/* plain function calls */ - -foo.eval?.(foo); - -eval.foo?.(foo); -``` - - - 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/reserved-word/interfaces.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/reserved-word/interfaces.js.snap deleted file mode 100644 index 65c89b7fdf7..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/reserved-word/interfaces.js.snap +++ /dev/null @@ -1,81 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -foo.interface; -interface.foo; -new interface(); -({ interface: "foo" }); -(interface, "foo"); -void interface; -const interface = "foo"; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -2,6 +2,6 @@ - interface.foo; - new interface(); - ({ interface: "foo" }); --interface, "foo"; -+(interface, "foo"); - void interface; - const interface = "foo"; -``` - -# Output - -```js -foo.interface; -interface.foo; -new interface(); -({ interface: "foo" }); -(interface, "foo"); -void interface; -const interface = "foo"; -``` - - -# Errors -``` -error[SyntaxError]: Illegal use of reserved keyword `interface` as an identifier in strict mode - ┌─ interfaces.js:2:1 - │ -2 │ interface.foo; - │ ^^^^^^^^^ - -error[SyntaxError]: Illegal use of reserved keyword `interface` as an identifier in strict mode - ┌─ interfaces.js:3:5 - │ -3 │ new interface(); - │ ^^^^^^^^^ - -error[SyntaxError]: Illegal use of reserved keyword `interface` as an identifier in strict mode - ┌─ interfaces.js:5:2 - │ -5 │ (interface, "foo"); - │ ^^^^^^^^^ - -error[SyntaxError]: Illegal use of reserved keyword `interface` as an identifier in strict mode - ┌─ interfaces.js:6:6 - │ -6 │ void interface; - │ ^^^^^^^^^ - -error[SyntaxError]: Illegal use of reserved keyword `interface` as an identifier in strict mode - ┌─ interfaces.js:7:7 - │ -7 │ const interface = "foo"; - │ ^^^^^^^^^ - - -``` - - 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 deleted file mode 100644 index 66a0a0c925d..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/sequence-break/break.js.snap +++ /dev/null @@ -1,169 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs -assertion_line: 271 -info: - test_file: js/sequence-break/break.js ---- - -# Input - -```js -const f = (argument1, argument2, argument3) => - (doSomethingWithArgument(argument1), doSomethingWithArgument(argument2),argument1); -(function(){ - return aLongIdentifierName, aLongIdentifierName, aLongIdentifierName, aLongIdentifierName; -}); -aLongIdentifierName, aLongIdentifierName, aLongIdentifierName, aLongIdentifierName; -a.then(() => (aLongIdentifierName, aLongIdentifierName, aLongIdentifierName, aLongIdentifierName)); -for (aLongIdentifierName = 0, aLongIdentifierName = 0, aLongIdentifierName = 0, aLongIdentifierName = 0; test; update) {} -(a = b ? c : function() { return 0; }), - (a = b ? c : function() { return 0; }), - (a = b ? c : function() { return 0; }), - (a = b ? c : function() { return 0; }), - (a = b ? c : function() { return 0; }); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -31,28 +31,38 @@ - test; - update - ) {} --(a = b -- ? c -- : function () { -- return 0; -- }), -- (a = b -+( -+ a = b - ? c - : function () { - return 0; -- }), -- (a = b -- ? c -- : function () { -- return 0; -- }), -- (a = b -- ? c -- : function () { -- return 0; -- }), -- (a = b -- ? c -- : function () { -- return 0; -- }); -+ } -+), -+ ( -+ a = b -+ ? c -+ : function () { -+ return 0; -+ } -+ ), -+ ( -+ a = b -+ ? c -+ : function () { -+ return 0; -+ } -+ ), -+ ( -+ a = b -+ ? c -+ : function () { -+ return 0; -+ } -+ ), -+ ( -+ a = b -+ ? c -+ : function () { -+ return 0; -+ } -+ ); -``` - -# Output - -```js -const f = (argument1, argument2, argument3) => ( - doSomethingWithArgument(argument1), - doSomethingWithArgument(argument2), - argument1 -); -(function () { - return ( - aLongIdentifierName, - aLongIdentifierName, - aLongIdentifierName, - aLongIdentifierName - ); -}); -aLongIdentifierName, - aLongIdentifierName, - aLongIdentifierName, - aLongIdentifierName; -a.then( - () => ( - aLongIdentifierName, - aLongIdentifierName, - aLongIdentifierName, - aLongIdentifierName - ), -); -for ( - aLongIdentifierName = 0, - aLongIdentifierName = 0, - aLongIdentifierName = 0, - aLongIdentifierName = 0; - test; - update -) {} -( - a = b - ? c - : function () { - return 0; - } -), - ( - a = b - ? c - : function () { - return 0; - } - ), - ( - a = b - ? c - : function () { - return 0; - } - ), - ( - a = b - ? c - : function () { - return 0; - } - ), - ( - a = b - ? c - : function () { - return 0; - } - ); -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/template-literals/sequence-expressions.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/template-literals/sequence-expressions.js.snap index 2cae76d8ff6..2399e8cce65 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/template-literals/sequence-expressions.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/template-literals/sequence-expressions.js.snap @@ -1,5 +1,8 @@ --- source: crates/rome_js_formatter/tests/prettier_tests.rs +assertion_line: 271 +info: + test_file: js/template-literals/sequence-expressions.js --- # Input @@ -14,21 +17,19 @@ source: crates/rome_js_formatter/tests/prettier_tests.rs ```diff --- Prettier +++ Rome -@@ -1,3 +1,3 @@ +@@ -1,3 +1,2 @@ -`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${ - (1, 2) -}`; -+`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${( -+ 1, 2 -+)}`; ++`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${(1, ++2)}`; ``` # Output ```js -`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${( - 1, 2 -)}`; +`111111111 222222222 333333333 444444444 555555555 666666666 777777777 ${(1, +2)}`; ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/template/parenthesis.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/template/parenthesis.js.snap index 285363bbf2a..16e50fa7e37 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/template/parenthesis.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/template/parenthesis.js.snap @@ -76,12 +76,8 @@ function* d() { // "ConditionalExpression" (b ? c : d)``; -@@ -31,16 +31,16 @@ - b.c``; - - // "NewExpression" --new B()``; -+(new B())``; +@@ -34,7 +34,7 @@ + new B()``; // "ObjectExpression" -({}``); @@ -89,13 +85,6 @@ function* d() { // "SequenceExpression" (b, c)``; - - // "TaggedTemplateExpression" --````; -+(``)``; - - // "UnaryExpression" - (void b)``; ``` # Output @@ -134,7 +123,7 @@ b()``; b.c``; // "NewExpression" -(new B())``; +new B()``; // "ObjectExpression" ({})``; @@ -143,7 +132,7 @@ b.c``; (b, c)``; // "TaggedTemplateExpression" -(``)``; +````; // "UnaryExpression" (void b)``; 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 b39432f6c2b..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,22 @@ +@@ -1,17 +1,21 @@ const funnelSnapshotCard = - (report === MY_OVERVIEW && !ReportGK.xar_metrics_active_capitol_v2) || - (report === COMPANY_OVERVIEW && @@ -46,8 +46,9 @@ room = room.map((row, rowIndex) => ( - colIndex === width - ? 1 - : 0, +- ), +room = room.map( -+ (row, rowIndex) => ( ++ (row, rowIndex) => + row.map( + (col, colIndex) => + ( @@ -58,8 +59,7 @@ room = room.map((row, rowIndex) => ( + ) + ? 1 + : 0, -+ ) - ), ++ ), ); ``` @@ -74,7 +74,7 @@ const funnelSnapshotCard = : null; room = room.map( - (row, rowIndex) => ( + (row, rowIndex) => row.map( (col, colIndex) => ( @@ -85,8 +85,7 @@ room = room.map( ) ? 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 e177f7dbeb0..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,81 +305,55 @@ fn?.[ ```diff --- Prettier +++ Rome -@@ -210,54 +210,56 @@ +@@ -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 -- ? 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 = ( - glimseGlyphsHazardNoopsTieTie === 0 && @@ -396,7 +370,7 @@ fn?.[ foo = ( coooooooooooooooooooooooooooooooooooooooooooooooooooond -@@ -280,8 +282,9 @@ +@@ -280,8 +270,9 @@ const decorated = (arg, ignoreRequestError) => { return ( @@ -625,48 +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 - ? 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 = ( glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI @@ -767,9 +729,9 @@ fn?.[ # Lines exceeding max width of 80 characters ``` - 221: (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI - 236: (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI - 251: (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI - 257: 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-in-condition.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/nested-in-condition.js.snap index daa430e9252..b0eaa4c389d 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/nested-in-condition.js.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/js/ternaries/nested-in-condition.js.snap @@ -49,12 +49,10 @@ const value = (bifornCringerMoshedPerplexSawder ```diff --- Prettier +++ Rome -@@ -20,21 +20,8 @@ - - ( +@@ -22,19 +22,6 @@ bifornCringerMoshedPerplexSawder -- ? askTrovenaBeenaDependsRowans -- : glimseGlyphsHazardNoopsTieTie + ? askTrovenaBeenaDependsRowans + : glimseGlyphsHazardNoopsTieTie -) ? ( - - @@ -71,8 +69,6 @@ const value = (bifornCringerMoshedPerplexSawder - - -); -+ ? (askTrovenaBeenaDependsRowans) -+ : (glimseGlyphsHazardNoopsTieTie) +) + ? + : ; @@ -103,8 +99,8 @@ const value = ( ( bifornCringerMoshedPerplexSawder - ? (askTrovenaBeenaDependsRowans) - : (glimseGlyphsHazardNoopsTieTie) + ? askTrovenaBeenaDependsRowans + : glimseGlyphsHazardNoopsTieTie ) ? : ; 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 7ce85bd68d6..87d1fc70318 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,8 +298,30 @@ async function bar2() { ```diff --- Prettier +++ Rome -@@ -14,33 +14,33 @@ - ); +@@ -1,283 +1,218 @@ + !x; +-!(x /* foo */); +-!(/* foo */ x); +-!( +- /* foo */ +- x +-); +-!( +- x +- /* foo */ +-); +-!( +- x // foo +-); ++!x /* foo */; ++! /* foo */ x; ++! ++/* foo */ ++x; ++!x ++/* foo */ ++; ++!x; // foo !(x + y); -!((x + y) /* foo */); @@ -342,32 +364,216 @@ async function bar2() { ); ![1, 2, 3]; -@@ -144,18 +144,18 @@ - ); +-!([1, 2, 3] /* foo */); +-!(/* foo */ [1, 2, 3]); +-!( +- /* foo */ +- [1, 2, 3] +-); +-!( +- [1, 2, 3] +- /* foo */ +-); +-!( +- [1, 2, 3] // foo +-); ++![1, 2, 3] /* foo */; ++! /* foo */ [1, 2, 3]; ++! ++/* foo */ ++[1, 2, 3]; ++![1, 2, 3] ++/* foo */ ++; ++![1, 2, 3]; // foo + + !{ a: 1, b: 2 }; +-!({ a: 1, b: 2 } /* foo */); +-!(/* foo */ { a: 1, b: 2 }); +-!( +- /* foo */ +- { a: 1, b: 2 } +-); +-!( +- { a: 1, b: 2 } +- /* foo */ +-); +-!( +- { a: 1, b: 2 } // foo +-); ++!{ a: 1, b: 2 } /* foo */; ++! /* foo */ { a: 1, b: 2 }; ++! ++/* foo */ ++{ a: 1, b: 2 }; ++!{ a: 1, b: 2 } ++/* foo */ ++; ++!{ a: 1, b: 2 }; // foo + + !function () { + return x; + }; +-!( +- function () { +- return x; +- } /* foo */ +-); +-!( +- /* foo */ function () { +- return x; +- } +-); +-!( +- /* foo */ +- function () { +- return x; +- } +-); +-!( +- function () { +- return x; +- } +- /* foo */ +-); +-!( +- function () { +- return x; +- } // foo +-); ++!function () { ++ return x; ++} /* foo */; ++! ++/* foo */ function () { ++ return x; ++}; ++! ++/* foo */ ++function () { ++ return x; ++}; ++!function () { ++ return x; ++} ++/* foo */ ++; ++!function () { ++ return x; ++}; // foo + + !+3; +-!(+3 /* foo */); +-!(/* foo */ +3); +-!( +- /* foo */ +- +3 +-); +-!( +- +3 +- /* foo */ +-); +-!( +- +3 // foo +-); ++!+3 /* foo */; ++! /* foo */ +3; ++! ++/* foo */ +++3; ++!+3 ++/* foo */ ++; ++!+3; // foo + +-!+( +- /* foo */ +- 3 +-); +-!(/* foo */ +(3 /* foo */)); +-!(+(3 /* foo */) /* foo */); +-!( +- /* foo */ +- +( +- /* foo */ +- 3 +- ) +-); +-!( +- +( +- 3 +- /* foo */ +- ) +- /* foo */ +-); +-!( +- +(3 /* foo */) // foo +-); ++!+ ++/* foo */ ++3; ++! /* foo */ +3 /* foo */; ++!+3 /* foo */ /* foo */; ++! ++/* foo */ +++ ++/* foo */ ++3; ++!+3 ++/* foo */ ++/* foo */ ++; ++!+3 /* foo */ ++// foo ++; !(x = y); -!((x = y) /* foo */); -!(/* foo */ (x = y)); -+!(x = y /* foo */); -+!(/* foo */ x = y); - !( - /* foo */ +-!( +- /* foo */ - (x = y) -+ x = y - ); - !( +-); +-!( - (x = y) -+ x = y - /* foo */ - ); - !( +- /* foo */ +-); +-!( - (x = y) // foo -+ x = y // foo - ); +-); ++!(x = y) /* foo */; ++! /* foo */ (x = y); ++! ++/* foo */ ++(x = y); ++!(x = y) ++/* foo */ ++; ++!(x = y); // foo !x.y; -@@ -174,19 +174,15 @@ - ); +-!(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 */); @@ -394,92 +600,151 @@ async function bar2() { +!(x ? y : z); // foo !x(); - !(x() /* foo */); -@@ -219,14 +215,14 @@ - ); +-!(x() /* foo */); +-!(/* foo */ x()); +-!( +- /* foo */ +- x() +-); +-!( +- x() +- /* foo */ +-); +-!( +- x() // foo +-); ++!x() /* foo */; ++! /* foo */ x(); ++! ++/* foo */ ++x(); ++!x() ++/* foo */ ++; ++!x(); // foo + + !new x(); +-!(new x() /* foo */); +-!(/* foo */ new x()); +-!( +- /* foo */ +- new x() +-); +-!( +- new x() +- /* foo */ +-); +-!( +- new x() // foo +-); ++!new x() /* foo */; ++! /* foo */ new x(); ++! ++/* foo */ ++new x(); ++!new x() ++/* foo */ ++; ++!new x(); // foo !(x, y); -!((x, y) /* foo */); -!(/* foo */ (x, y)); -+!(x, y /* foo */); -+!(/* foo */ x, y); - !( - /* foo */ +-!( +- /* foo */ - (x, y) -+ x, y - ); - !( +-); +-!( - (x, y) -+ x, y - /* foo */ - ); - !( -@@ -234,50 +230,51 @@ - ); +- /* foo */ +-); +-!( +- x.y // foo +-); ++!(x, y) /* foo */; ++! /* foo */ (x, y); ++! ++/* foo */ ++(x, y); ++!(x, y) ++/* foo */ ++; ++!x.y; // foo !(() => 3); -!((() => 3) /* foo */); -!(/* foo */ (() => 3)); -+!(() => 3 /* foo */); -+!(/* foo */ () => 3); - !( - /* foo */ +-!( +- /* foo */ - (() => 3) -+ () => 3 - ); - !( +-); +-!( - (() => 3) -+ () => 3 - /* foo */ - ); - !( +- /* foo */ +-); +-!( - (() => 3) // foo -+ () => -+ 3 // foo - ); +-); ++!(() => 3) /* foo */; ++! /* foo */ (() => 3); ++! ++/* foo */ ++(() => 3); ++!(() => 3) ++/* foo */ ++; ++!(() => 3); // foo function* bar() { !(yield x); - !((yield x) /* foo */); - !(/* foo */ (yield x)); -+ !(yield x /* foo */); -+ !(/* foo */ yield x); - !( - /* foo */ +- !( +- /* foo */ - (yield x) -+ yield x - ); - !( +- ); +- !( - (yield x) -+ yield x - /* foo */ - ); - !( +- /* foo */ +- ); +- !( - (yield x) // foo -+ yield x // foo - ); +- ); ++ !(yield x) /* foo */; ++ ! /* foo */ (yield x); ++ ! ++ /* foo */ ++ (yield x); ++ !(yield x) ++ /* foo */ ++ ; ++ !(yield x); // foo } async function bar2() { !(await x); - !((await x) /* foo */); - !(/* foo */ (await x)); -+ !(await x /* foo */); -+ !(/* foo */ await x); - !( - /* foo */ +- !( +- /* foo */ - (await x) -+ await x - ); - !( +- ); +- !( - (await x) -+ await x - /* foo */ - ); - !( +- /* foo */ +- ); +- !( - (await x) // foo -+ await x // foo - ); +- ); ++ !(await x) /* foo */; ++ ! /* foo */ (await x); ++ ! ++ /* foo */ ++ (await x); ++ !(await x) ++ /* foo */ ++ ; ++ !(await x); // foo } ``` @@ -487,19 +752,15 @@ async function bar2() { ```js !x; -!(x /* foo */); -!(/* foo */ x); -!( - /* foo */ - x -); -!( - x - /* foo */ -); -!( - x // foo -); +!x /* foo */; +! /* foo */ x; +! +/* foo */ +x; +!x +/* foo */ +; +!x; // foo !(x + y); !(x + y /* foo */); @@ -532,134 +793,101 @@ async function bar2() { ); ![1, 2, 3]; -!([1, 2, 3] /* foo */); -!(/* foo */ [1, 2, 3]); -!( - /* foo */ - [1, 2, 3] -); -!( - [1, 2, 3] - /* foo */ -); -!( - [1, 2, 3] // foo -); +![1, 2, 3] /* foo */; +! /* foo */ [1, 2, 3]; +! +/* foo */ +[1, 2, 3]; +![1, 2, 3] +/* foo */ +; +![1, 2, 3]; // foo !{ a: 1, b: 2 }; -!({ a: 1, b: 2 } /* foo */); -!(/* foo */ { a: 1, b: 2 }); -!( - /* foo */ - { a: 1, b: 2 } -); -!( - { a: 1, b: 2 } - /* foo */ -); -!( - { a: 1, b: 2 } // foo -); +!{ a: 1, b: 2 } /* foo */; +! /* foo */ { a: 1, b: 2 }; +! +/* foo */ +{ a: 1, b: 2 }; +!{ a: 1, b: 2 } +/* foo */ +; +!{ a: 1, b: 2 }; // foo !function () { return x; }; -!( - function () { - return x; - } /* foo */ -); -!( - /* foo */ function () { - return x; - } -); -!( - /* foo */ - function () { - return x; - } -); -!( - function () { - return x; - } - /* foo */ -); -!( - function () { - return x; - } // foo -); +!function () { + return x; +} /* foo */; +! +/* foo */ function () { + return x; +}; +! +/* foo */ +function () { + return x; +}; +!function () { + return x; +} +/* foo */ +; +!function () { + return x; +}; // foo !+3; -!(+3 /* foo */); -!(/* foo */ +3); -!( - /* foo */ - +3 -); -!( - +3 - /* foo */ -); -!( - +3 // foo -); +!+3 /* foo */; +! /* foo */ +3; +! +/* foo */ ++3; +!+3 +/* foo */ +; +!+3; // foo -!+( - /* foo */ - 3 -); -!(/* foo */ +(3 /* foo */)); -!(+(3 /* foo */) /* foo */); -!( - /* foo */ - +( - /* foo */ - 3 - ) -); -!( - +( - 3 - /* foo */ - ) - /* foo */ -); -!( - +(3 /* foo */) // foo -); +!+ +/* foo */ +3; +! /* foo */ +3 /* foo */; +!+3 /* foo */ /* foo */; +! +/* foo */ ++ +/* foo */ +3; +!+3 +/* foo */ +/* foo */ +; +!+3 /* foo */ +// 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; -!(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 */; @@ -673,98 +901,73 @@ async function bar2() { !(x ? y : z); // foo !x(); -!(x() /* foo */); -!(/* foo */ x()); -!( - /* foo */ - x() -); -!( - x() - /* foo */ -); -!( - x() // foo -); +!x() /* foo */; +! /* foo */ x(); +! +/* foo */ +x(); +!x() +/* foo */ +; +!x(); // foo !new x(); -!(new x() /* foo */); -!(/* foo */ new x()); -!( - /* foo */ - new x() -); -!( - new x() - /* foo */ -); -!( - new x() // foo -); +!new x() /* foo */; +! /* foo */ new x(); +! +/* foo */ +new x(); +!new x() +/* foo */ +; +!new x(); // 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 !(() => 3); -!(() => 3 /* foo */); -!(/* foo */ () => 3); -!( - /* foo */ - () => 3 -); -!( - () => 3 - /* foo */ -); -!( - () => - 3 // foo -); +!(() => 3) /* foo */; +! /* foo */ (() => 3); +! +/* foo */ +(() => 3); +!(() => 3) +/* foo */ +; +!(() => 3); // foo function* bar() { !(yield x); - !(yield x /* foo */); - !(/* foo */ yield x); - !( - /* foo */ - yield x - ); - !( - yield x - /* foo */ - ); - !( - yield x // foo - ); + !(yield x) /* foo */; + ! /* foo */ (yield x); + ! + /* foo */ + (yield x); + !(yield x) + /* foo */ + ; + !(yield x); // foo } async function bar2() { !(await x); - !(await x /* foo */); - !(/* foo */ await x); - !( - /* foo */ - await x - ); - !( - await x - /* foo */ - ); - !( - await x // foo - ); + !(await x) /* foo */; + ! /* foo */ (await x); + ! + /* foo */ + (await x); + !(await x) + /* foo */ + ; + !(await x); // foo } ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/unary-expression/urnary_expression.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/unary-expression/urnary_expression.js.snap deleted file mode 100644 index c2db87c5591..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/unary-expression/urnary_expression.js.snap +++ /dev/null @@ -1,51 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs -assertion_line: 271 -info: - test_file: js/unary-expression/urnary_expression.js ---- - -# Input - -```js -!!x -x++ -x--; --+1; -x + + + + 1; -x + (+ (+ (+ 1))) -x * +y; -+x * y; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -3,6 +3,6 @@ - x--; - -+1; - x + +(+(+1)); --x + +(+(+1)); -+x + (+(+(+1))); - x * +y; - +x * y; -``` - -# Output - -```js -!!x; -x++; -x--; --+1; -x + +(+(+1)); -x + (+(+(+1))); -x * +y; -+x * y; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/yield/arrow.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/yield/arrow.js.snap deleted file mode 100644 index aab6ca5c893..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/yield/arrow.js.snap +++ /dev/null @@ -1,43 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -function *f() { - (yield a => a); - (yield async a => a); - (yield async (a) => a); -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,5 +1,5 @@ - function* f() { -- yield (a) => a; -- yield async (a) => a; -- yield async (a) => a; -+ (yield (a) => a); -+ (yield async (a) => a); -+ (yield async (a) => a); - } -``` - -# Output - -```js -function* f() { - (yield (a) => a); - (yield async (a) => a); - (yield async (a) => a); -} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/js/yield/conditional.js.snap b/crates/rome_js_formatter/tests/specs/prettier/js/yield/conditional.js.snap deleted file mode 100644 index 7847d10391e..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/js/yield/conditional.js.snap +++ /dev/null @@ -1,71 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -function* f1() { - a = (yield) ? 1 : 1; - a = yield 1 ? 1 : 1; - a = (yield 1) ? 1 : 1; - a = 1 ? yield : yield; - a = 1 ? yield 1 : yield 1; -} - -function* f2() { - a = yield* 1 ? 1 : 1; - a = (yield* 1) ? 1 : 1; - a = 1 ? yield* 1 : yield* 1; -} - -async function f3() { - a = await 1 ? 1 : 1; - a = (await 1) ? 1 : 1; - a = 1 ? await 1 : await 1; -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -13,7 +13,7 @@ - } - - async function f3() { -- a = (await 1) ? 1 : 1; -+ a = await 1 ? 1 : 1; - a = (await 1) ? 1 : 1; - a = 1 ? await 1 : await 1; - } -``` - -# Output - -```js -function* f1() { - a = (yield) ? 1 : 1; - a = yield 1 ? 1 : 1; - a = (yield 1) ? 1 : 1; - a = 1 ? yield : yield; - a = 1 ? yield 1 : yield 1; -} - -function* f2() { - a = yield* 1 ? 1 : 1; - a = (yield* 1) ? 1 : 1; - a = 1 ? yield* 1 : yield* 1; -} - -async function f3() { - a = await 1 ? 1 : 1; - a = (await 1) ? 1 : 1; - a = 1 ? await 1 : await 1; -} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/argument-expansion/argument_expansion.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/argument-expansion/argument_expansion.ts.snap deleted file mode 100644 index 74317f1e2a4..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/argument-expansion/argument_expansion.ts.snap +++ /dev/null @@ -1,137 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -const bar1 = [1,2,3].reduce((carry, value) => { - return [...carry, value]; -}, ([] as unknown) as number[]); - -const bar2 = [1,2,3].reduce((carry, value) => { - return [...carry, value]; -}, >[]); - -const bar3 = [1,2,3].reduce((carry, value) => { - return [...carry, value]; -}, ([1, 2, 3] as unknown) as number[]); - -const bar4 = [1,2,3].reduce((carry, value) => { - return [...carry, value]; -}, >[1, 2, 3]); - -const bar5 = [1,2,3].reduce((carry, value) => { - return {...carry, [value]: true}; -}, ({} as unknown) as {[key: number]: boolean}); - -const bar6 = [1,2,3].reduce((carry, value) => { - return {...carry, [value]: true}; -}, <{[key: number]: boolean}>{}); - -const bar7 = [1,2,3].reduce((carry, value) => { - return {...carry, [value]: true}; -}, ({1: true} as unknown) as {[key: number]: boolean}); - -const bar8 = [1,2,3].reduce((carry, value) => { - return {...carry, [value]: true}; -}, <{[key: number]: boolean}>{1: true}); -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,17 +1,14 @@ - const bar1 = [1, 2, 3].reduce((carry, value) => { - return [...carry, value]; --}, [] as unknown as number[]); -+}, ([] as unknown) as number[]); - - const bar2 = [1, 2, 3].reduce((carry, value) => { - return [...carry, value]; - }, >[]); - --const bar3 = [1, 2, 3].reduce( -- (carry, value) => { -- return [...carry, value]; -- }, -- [1, 2, 3] as unknown as number[], --); -+const bar3 = [1, 2, 3].reduce((carry, value) => { -+ return [...carry, value]; -+}, ([1, 2, 3] as unknown) as number[]); - - const bar4 = [1, 2, 3].reduce( - (carry, value) => { -@@ -22,18 +19,15 @@ - - const bar5 = [1, 2, 3].reduce((carry, value) => { - return { ...carry, [value]: true }; --}, {} as unknown as { [key: number]: boolean }); -+}, ({} as unknown) as { [key: number]: boolean }); - - const bar6 = [1, 2, 3].reduce((carry, value) => { - return { ...carry, [value]: true }; - }, <{ [key: number]: boolean }>{}); - --const bar7 = [1, 2, 3].reduce( -- (carry, value) => { -- return { ...carry, [value]: true }; -- }, -- { 1: true } as unknown as { [key: number]: boolean }, --); -+const bar7 = [1, 2, 3].reduce((carry, value) => { -+ return { ...carry, [value]: true }; -+}, ({ 1: true } as unknown) as { [key: number]: boolean }); - - const bar8 = [1, 2, 3].reduce( - (carry, value) => { -``` - -# Output - -```js -const bar1 = [1, 2, 3].reduce((carry, value) => { - return [...carry, value]; -}, ([] as unknown) as number[]); - -const bar2 = [1, 2, 3].reduce((carry, value) => { - return [...carry, value]; -}, >[]); - -const bar3 = [1, 2, 3].reduce((carry, value) => { - return [...carry, value]; -}, ([1, 2, 3] as unknown) as number[]); - -const bar4 = [1, 2, 3].reduce( - (carry, value) => { - return [...carry, value]; - }, - >[1, 2, 3], -); - -const bar5 = [1, 2, 3].reduce((carry, value) => { - return { ...carry, [value]: true }; -}, ({} as unknown) as { [key: number]: boolean }); - -const bar6 = [1, 2, 3].reduce((carry, value) => { - return { ...carry, [value]: true }; -}, <{ [key: number]: boolean }>{}); - -const bar7 = [1, 2, 3].reduce((carry, value) => { - return { ...carry, [value]: true }; -}, ({ 1: true } as unknown) as { [key: number]: boolean }); - -const bar8 = [1, 2, 3].reduce( - (carry, value) => { - return { ...carry, [value]: true }; - }, - <{ [key: number]: boolean }>{ 1: true }, -); -``` - - - 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/as.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/as/as.ts.snap index 2f1489b636c..012c2ea0a99 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/as/as.ts.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/as/as.ts.snap @@ -1,8 +1,5 @@ --- source: crates/rome_js_formatter/tests/prettier_tests.rs -assertion_line: 271 -info: - test_file: typescript/as/as.ts --- # Input @@ -56,31 +53,8 @@ const iter2 = createIterator(self.controller, child, self.tag as SyncFunctionCom ```diff --- Prettier +++ Rome -@@ -3,29 +3,31 @@ - (originalError - ? wrappedError(errMsg, originalError) - : Error(errMsg)) as InjectionError; --"current" in (props.pagination as Object); -+"current" in props.pagination as Object; - ("current" in props.pagination) as Object; - start + (yearSelectTotal as number); - (start + yearSelectTotal) as number; - scrollTop > (visibilityHeight as number); - (scrollTop > visibilityHeight) as number; --export default class Column extends (RcTable.Column as React.ComponentClass< -- ColumnProps, -- ColumnProps, -- ColumnProps, -- ColumnProps -->) {} -+export default class Column extends ( -+ RcTable.Column as React.ComponentClass< -+ ColumnProps, -+ ColumnProps, -+ ColumnProps, -+ ColumnProps -+ > -+) {} +@@ -17,15 +17,15 @@ + >) {} export const MobxTypedForm = class extends (Form as { new (): any }) {}; export abstract class MobxTypedForm1 extends (Form as { new (): any }) {} -({} as {}); @@ -98,7 +72,7 @@ const iter2 = createIterator(self.controller, child, self.tag as SyncFunctionCom const state = JSON.stringify({ next: window.location.href, nonce, -@@ -41,9 +43,9 @@ +@@ -41,9 +41,9 @@ thisIsAReallyReallyReallyReallyReallyLongIdentifier as SomeInterface; const value2 = thisIsAnIdentifier as thisIsAReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongInterface; @@ -121,20 +95,18 @@ this.isTabActionBar((e.target || e.srcElement) as HTMLElement); (originalError ? wrappedError(errMsg, originalError) : Error(errMsg)) as InjectionError; -"current" in props.pagination as Object; +"current" in (props.pagination as Object); ("current" in props.pagination) as Object; start + (yearSelectTotal as number); (start + yearSelectTotal) as number; scrollTop > (visibilityHeight as number); (scrollTop > visibilityHeight) as number; -export default class Column extends ( - RcTable.Column as React.ComponentClass< - ColumnProps, - ColumnProps, - ColumnProps, - ColumnProps - > -) {} +export default class Column extends (RcTable.Column as React.ComponentClass< + ColumnProps, + ColumnProps, + ColumnProps, + ColumnProps +>) {} export const MobxTypedForm = class extends (Form as { new (): any }) {}; export abstract class MobxTypedForm1 extends (Form as { new (): any }) {} ({}) as {}; @@ -190,7 +162,7 @@ const iter2 = createIterator( # Lines exceeding max width of 80 characters ``` - 45: thisIsAnIdentifier as thisIsAReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongInterface; - 55: thisIsAReallyReallyReallyReallyReallyReallyReallyReallyReallyLongIdentifier as [ + 43: thisIsAnIdentifier as thisIsAReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyReallyLongInterface; + 53: thisIsAReallyReallyReallyReallyReallyReallyReallyReallyReallyLongIdentifier as [ ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/as/assignment.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/as/assignment.ts.snap deleted file mode 100644 index ab2e6a4d1bf..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/as/assignment.ts.snap +++ /dev/null @@ -1,115 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -export const LOG_LEVEL = { - EMERGENCY: 0, - ALERT: 1, - CRITICAL: 2, - ERROR: 3, - WARNING: 4, - NOTICE: 5, - INFO: 6, - DEBUG: 7, -} as const; - -const TYPE_MAP = { - 'character device': 'special', - 'character special file': 'special', - directory: 'directory', - 'regular file': 'file', - socket: 'socket', - 'symbolic link': 'link', -} as Foo; - -this.previewPlayerHandle = (setInterval(async () => { - if (this.previewIsPlaying) { - await this.fetchNextPreviews(); - this.currentPreviewIndex++; - } -}, this.refreshDelay) as unknown) as number; - -this.intervalID = (setInterval(() => { - self.step(); -}, 30) as unknown) as number; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -18,13 +18,17 @@ - "symbolic link": "link", - } as Foo; - --this.previewPlayerHandle = setInterval(async () => { -- if (this.previewIsPlaying) { -- await this.fetchNextPreviews(); -- this.currentPreviewIndex++; -- } --}, this.refreshDelay) as unknown as number; -+this.previewPlayerHandle = ( -+ setInterval(async () => { -+ if (this.previewIsPlaying) { -+ await this.fetchNextPreviews(); -+ this.currentPreviewIndex++; -+ } -+ }, this.refreshDelay) as unknown -+) as number; - --this.intervalID = setInterval(() => { -- self.step(); --}, 30) as unknown as number; -+this.intervalID = ( -+ setInterval(() => { -+ self.step(); -+ }, 30) as unknown -+) as number; -``` - -# Output - -```js -export const LOG_LEVEL = { - EMERGENCY: 0, - ALERT: 1, - CRITICAL: 2, - ERROR: 3, - WARNING: 4, - NOTICE: 5, - INFO: 6, - DEBUG: 7, -} as const; - -const TYPE_MAP = { - "character device": "special", - "character special file": "special", - directory: "directory", - "regular file": "file", - socket: "socket", - "symbolic link": "link", -} as Foo; - -this.previewPlayerHandle = ( - setInterval(async () => { - if (this.previewIsPlaying) { - await this.fetchNextPreviews(); - this.currentPreviewIndex++; - } - }, this.refreshDelay) as unknown -) as number; - -this.intervalID = ( - setInterval(() => { - self.step(); - }, 30) as unknown -) as number; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/as/export_default_as.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/as/export_default_as.ts.snap new file mode 100644 index 00000000000..7fd5bfbdf72 --- /dev/null +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/as/export_default_as.ts.snap @@ -0,0 +1,29 @@ +--- +source: crates/rome_js_formatter/tests/prettier_tests.rs +--- + +# Input + +```js +export default (function log() {} as typeof console.log) +``` + + +# Prettier differences + +```diff +--- Prettier ++++ Rome +@@ -1 +1 @@ +-export default (function log() {} as typeof console.log); ++export default (function log() {}) as typeof console.log; +``` + +# Output + +```js +export default (function log() {}) as typeof console.log; +``` + + + 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 deleted file mode 100644 index 118cfb1c1b0..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/as/nested-await-and-as.ts.snap +++ /dev/null @@ -1,55 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -const getAccountCount = async () => - (await - ((await ( - await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME) - ).findItem("My bookmarks")) as TreeItem - ).getChildren() - ).length -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,8 +1,10 @@ - const getAccountCount = async () => - ( - await ( -- (await ( -- await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME) -- ).findItem("My bookmarks")) as TreeItem -+ ( -+ 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; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/as/ternary.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/as/ternary.ts.snap index 9fb6991ccae..190657cd459 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/as/ternary.ts.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/as/ternary.ts.snap @@ -53,43 +53,23 @@ bifornCringerMoshedPerplexSawder = ```diff --- Prettier +++ Rome -@@ -29,22 +29,23 @@ - } - - function foo() { -- void (( -- coooooooooooooooooooooooooooooooooooooooooooooooooooond -+ void ( -+ (coooooooooooooooooooooooooooooooooooooooooooooooooooond - ? baaaaaaaaaaaaaaaaaaaaar -- : baaaaaaaaaaaaaaaaaaaaaz -- ) as Fooooooooooo); -+ : baaaaaaaaaaaaaaaaaaaaaz) as Fooooooooooo -+ ); +@@ -37,14 +37,11 @@ } bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + - ((glimseGlyphsHazardNoopsTieTie === 0 -- ? averredBathersBoxroomBuggyNurl -- : anodyneCondosMalateOverateRetinol) as AnnularCooeedSplicesWalksWayWay); -+ askTrovenaBeenaDependsRowans + ( -+ (glimseGlyphsHazardNoopsTieTie === 0 -+ ? averredBathersBoxroomBuggyNurl -+ : anodyneCondosMalateOverateRetinol) as AnnularCooeedSplicesWalksWayWay -+ ); ++ askTrovenaBeenaDependsRowans + ((glimseGlyphsHazardNoopsTieTie === 0 + ? averredBathersBoxroomBuggyNurl + : anodyneCondosMalateOverateRetinol) as AnnularCooeedSplicesWalksWayWay); bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + - ((glimseGlyphsHazardNoopsTieTie === 0 && - kochabCooieGameOnOboleUnweave === Math.PI -- ? averredBathersBoxroomBuggyNurl -- : anodyneCondosMalateOverateRetinol) as AnnularCooeedSplicesWalksWayWay); -+ askTrovenaBeenaDependsRowans + ( -+ (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI -+ ? averredBathersBoxroomBuggyNurl -+ : anodyneCondosMalateOverateRetinol) as AnnularCooeedSplicesWalksWayWay -+ ); ++ askTrovenaBeenaDependsRowans + ((glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI + ? averredBathersBoxroomBuggyNurl + : anodyneCondosMalateOverateRetinol) as AnnularCooeedSplicesWalksWayWay); ``` # Output @@ -126,31 +106,27 @@ function foo() { } function foo() { - void ( - (coooooooooooooooooooooooooooooooooooooooooooooooooooond + void (( + coooooooooooooooooooooooooooooooooooooooooooooooooooond ? baaaaaaaaaaaaaaaaaaaaar - : baaaaaaaaaaaaaaaaaaaaaz) as Fooooooooooo - ); + : baaaaaaaaaaaaaaaaaaaaaz + ) as Fooooooooooo); } bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + ( - (glimseGlyphsHazardNoopsTieTie === 0 - ? averredBathersBoxroomBuggyNurl - : anodyneCondosMalateOverateRetinol) as AnnularCooeedSplicesWalksWayWay - ); + askTrovenaBeenaDependsRowans + ((glimseGlyphsHazardNoopsTieTie === 0 + ? averredBathersBoxroomBuggyNurl + : anodyneCondosMalateOverateRetinol) as AnnularCooeedSplicesWalksWayWay); bifornCringerMoshedPerplexSawder = - askTrovenaBeenaDependsRowans + ( - (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI - ? averredBathersBoxroomBuggyNurl - : anodyneCondosMalateOverateRetinol) as AnnularCooeedSplicesWalksWayWay - ); + askTrovenaBeenaDependsRowans + ((glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI + ? averredBathersBoxroomBuggyNurl + : anodyneCondosMalateOverateRetinol) as AnnularCooeedSplicesWalksWayWay); ``` # Lines exceeding max width of 80 characters ``` - 48: (glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI + 45: askTrovenaBeenaDependsRowans + ((glimseGlyphsHazardNoopsTieTie === 0 && kochabCooieGameOnOboleUnweave === Math.PI ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/cast/as-const.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/cast/as-const.ts.snap deleted file mode 100644 index 7d87ae5fab2..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/cast/as-const.ts.snap +++ /dev/null @@ -1,42 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -let x = '123' as const; - -// https://github.com/babel/babel/pull/11912 -x as boolean <= y; // (x as boolean) <= y; -x as boolean ?? y; // (x as boolean) ?? y; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,5 +1,5 @@ - let x = "123" as const; - - // https://github.com/babel/babel/pull/11912 --(x as boolean) <= y; // (x as boolean) <= y; --(x as boolean) ?? y; // (x as boolean) ?? y; -+x as boolean <= y; // (x as boolean) <= y; -+x as boolean ?? y; // (x as boolean) ?? y; -``` - -# Output - -```js -let x = "123" as const; - -// https://github.com/babel/babel/pull/11912 -x as boolean <= y; // (x as boolean) <= y; -x as boolean ?? y; // (x as boolean) ?? y; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/cast/generic-cast.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/cast/generic-cast.ts.snap index 17f96915769..851a77fc7c1 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/cast/generic-cast.ts.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/cast/generic-cast.ts.snap @@ -151,31 +151,37 @@ function x() { ]; const testArrayLiteral2 = < -@@ -112,8 +108,8 @@ +@@ -111,9 +107,9 @@ + const fitsObjLiteral = | undefined>{ a: "test" }; const fitsArrayLiteral = | undefined>["t1", "t2"]; - const breakAfterCast = | undefined>( +- const breakAfterCast = | undefined>( - (permissions)[receiverType] - ); -+ permissions -+ )[receiverType]; ++ const breakAfterCast = | undefined>(< ++ any ++ >permissions)[receiverType]; const stillTooLong = < PermissionsChecker | undefined | number | string | boolean -@@ -130,10 +126,8 @@ +@@ -129,11 +125,11 @@ + | never >(permissions)[receiverType]; - const stillTooLong3 = | undefined>( +- const stillTooLong3 = | undefined>( - (permissions.someMethodWithLongName(parameter1, parameter2))[ - receiverTypeLongName - ] - ); -+ permissions.someMethodWithLongName(parameter1, parameter2) -+ )[receiverTypeLongName]; ++ const stillTooLong3 = | undefined>(< ++ any ++ >permissions.someMethodWithLongName(parameter1, parameter2))[ ++ receiverTypeLongName ++ ]; const stillTooLong4 = < | PermissionsChecker -@@ -163,9 +157,7 @@ +@@ -163,9 +159,7 @@ >{ prop1: "myPropVal" }; const testArrayLiteral = | undefined>[ @@ -300,9 +306,9 @@ function x() { const fitsObjLiteral = | undefined>{ a: "test" }; const fitsArrayLiteral = | undefined>["t1", "t2"]; - const breakAfterCast = | undefined>( - permissions - )[receiverType]; + const breakAfterCast = | undefined>(< + any + >permissions)[receiverType]; const stillTooLong = < PermissionsChecker | undefined | number | string | boolean @@ -318,9 +324,11 @@ function x() { | never >(permissions)[receiverType]; - const stillTooLong3 = | undefined>( - permissions.someMethodWithLongName(parameter1, parameter2) - )[receiverTypeLongName]; + const stillTooLong3 = | undefined>(< + any + >permissions.someMethodWithLongName(parameter1, parameter2))[ + receiverTypeLongName + ]; const stillTooLong4 = < | PermissionsChecker diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/castOfAwait.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/castOfAwait.ts.snap deleted file mode 100644 index c2434dae12a..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/castOfAwait.ts.snap +++ /dev/null @@ -1,52 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -// @target: es6 -async function f() { - await 0; - typeof await 0; - void await 0; - await void typeof void await 0; - await await 0; -} -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,8 +1,8 @@ - // @target: es6 - async function f() { - await 0; -- typeof (await 0); -- void (await 0); -- await void (typeof (void (await 0))); -+ typeof await 0; -+ void await 0; -+ await void typeof void await 0; - await await 0; - } -``` - -# Output - -```js -// @target: es6 -async function f() { - await 0; - typeof await 0; - void await 0; - await void typeof void await 0; - await await 0; -} -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/castParentheses.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/castParentheses.ts.snap index adae8e026c9..9278d0afc75 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/castParentheses.ts.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/castParentheses.ts.snap @@ -24,22 +24,12 @@ var b = (new a).b ```diff --- Prettier +++ Rome -@@ -1,11 +1,11 @@ +@@ -1,4 +1,4 @@ -class a { +class a { static b: any; } --var b = a; -+var b = (a); - var b = (a).b; - var b = (a.b).c; - var b = (a.b()).c; --var b = new a(); --var b = new a.b(); -+var b = (new a()); -+var b = (new a.b()); - var b = (new a()).b; ``` # Output @@ -49,12 +39,12 @@ class a { static b: any; } -var b = (a); +var b = a; var b = (a).b; var b = (a.b).c; var b = (a.b()).c; -var b = (new a()); -var b = (new a.b()); +var b = new a(); +var b = new a.b(); var b = (new a()).b; ``` diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/castTest.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/castTest.ts.snap deleted file mode 100644 index 85c503059b5..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/castTest.ts.snap +++ /dev/null @@ -1,99 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js - -var x : any = 0; -var z = x; -var y = x + z; - -var a = 0; -var b = true; -var s = ""; - -var ar = null; - -var f = <(res : number) => void>null; - -declare class Point -{ - x: number; - y: number; - add(dx: number, dy: number): Point; - mult(p: Point): Point; - constructor(x: number, y: number); -} - -var p_cast = ({ - x: 0, - y: 0, - add: function(dx, dy) { - return new Point(this.x + dx, this.y + dy); - }, - mult: function(p) { return p; } -}) -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -18,7 +18,7 @@ - constructor(x: number, y: number); - } - --var p_cast = { -+var p_cast = ({ - x: 0, - y: 0, - add: function (dx, dy) { -@@ -27,4 +27,4 @@ - mult: function (p) { - return p; - }, --}; -+}); -``` - -# Output - -```js -var x: any = 0; -var z = x; -var y = x + z; - -var a = 0; -var b = true; -var s = ""; - -var ar = null; - -var f = <(res: number) => void>null; - -declare class Point { - x: number; - y: number; - add(dx: number, dy: number): Point; - mult(p: Point): Point; - constructor(x: number, y: number); -} - -var p_cast = ({ - x: 0, - y: 0, - add: function (dx, dy) { - return new Point(this.x + dx, this.y + dy); - }, - mult: function (p) { - return p; - }, -}); -``` - - - 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/conformance/types/functions/functionImplementations.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/functions/functionImplementations.ts.snap index 9cf980269af..eb26c047293 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/functions/functionImplementations.ts.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/functions/functionImplementations.ts.snap @@ -171,84 +171,8 @@ var f12: (x: number) => any = x => { // should be (x: number) => Base | AnotherC ```diff --- Prettier +++ Rome -@@ -1,7 +1,7 @@ - // @allowUnreachableCode: true - - // FunctionExpression with no return type annotation and no return statement returns void --var v: void = (function () {})(); -+var v: void = function () {}(); - - // FunctionExpression f with no return type annotation and directly references f in its body returns any - var a: any = function f() { -@@ -39,37 +39,37 @@ - var n = rec4(); - - // FunctionExpression with no return type annotation and returns a number --var n = (function () { -+var n = function () { - return 3; --})(); -+}(); - - // FunctionExpression with no return type annotation and returns null - var nu = null; --var nu = (function () { -+var nu = function () { - return null; --})(); -+}(); - - // FunctionExpression with no return type annotation and returns undefined - var un = undefined; --var un = (function () { -+var un = function () { - return undefined; --})(); -+}(); - - // FunctionExpression with no return type annotation and returns a type parameter type --var n = (function (x: T) { -+var n = function (x: T) { - return x; --})(4); -+}(4); - - // FunctionExpression with no return type annotation and returns a constrained type parameter type --var n = (function (x: T) { -+var n = function (x: T) { - return x; --})(4); -+}(4); - - // FunctionExpression with no return type annotation with multiple return statements with identical types --var n = (function () { -+var n = function () { - return 3; - return 5; --})(); -+}(); - - // Otherwise, the inferred return type is the first of the types of the return statement expressions - // in the function body that is a supertype of each of the others, -@@ -83,23 +83,22 @@ - private q; - } - var b: Base; --var b = (function () { -+var b = function () { - return new Base(); - return new Derived(); --})(); -+}(); - - // FunctionExpression with no return type annotation with multiple return statements with one a recursive call --var a = (function f() { -+var a = function f() { - return new Base(); - return new Derived(); - return f(); // ? --})(); -+}(); +@@ -96,10 +96,9 @@ + })(); // FunctionExpression with non -void return type annotation with a single throw statement -undefined === @@ -269,7 +193,7 @@ var f12: (x: number) => any = x => { // should be (x: number) => Base | AnotherC // @allowUnreachableCode: true // FunctionExpression with no return type annotation and no return statement returns void -var v: void = function () {}(); +var v: void = (function () {})(); // FunctionExpression f with no return type annotation and directly references f in its body returns any var a: any = function f() { @@ -307,37 +231,37 @@ var n = rec3(); var n = rec4(); // FunctionExpression with no return type annotation and returns a number -var n = function () { +var n = (function () { return 3; -}(); +})(); // FunctionExpression with no return type annotation and returns null var nu = null; -var nu = function () { +var nu = (function () { return null; -}(); +})(); // FunctionExpression with no return type annotation and returns undefined var un = undefined; -var un = function () { +var un = (function () { return undefined; -}(); +})(); // FunctionExpression with no return type annotation and returns a type parameter type -var n = function (x: T) { +var n = (function (x: T) { return x; -}(4); +})(4); // FunctionExpression with no return type annotation and returns a constrained type parameter type -var n = function (x: T) { +var n = (function (x: T) { return x; -}(4); +})(4); // FunctionExpression with no return type annotation with multiple return statements with identical types -var n = function () { +var n = (function () { return 3; return 5; -}(); +})(); // Otherwise, the inferred return type is the first of the types of the return statement expressions // in the function body that is a supertype of each of the others, @@ -351,17 +275,17 @@ class Derived extends Base { private q; } var b: Base; -var b = function () { +var b = (function () { return new Base(); return new Derived(); -}(); +})(); // FunctionExpression with no return type annotation with multiple return statements with one a recursive call -var a = function f() { +var a = (function f() { return new Base(); return new Derived(); return f(); // ? -}(); +})(); // FunctionExpression with non -void return type annotation with a single throw statement undefined === function (): number { diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/tuple/wideningTuples3.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/tuple/wideningTuples3.ts.snap deleted file mode 100644 index 8aca50d45bc..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/tuple/wideningTuples3.ts.snap +++ /dev/null @@ -1,38 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -//@noImplicitAny: true -var a: [any]; - -var b = a = [undefined, null]; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,4 +1,4 @@ - //@noImplicitAny: true - var a: [any]; - --var b = (a = [undefined, null]); -+var b = a = [undefined, null]; -``` - -# Output - -```js -//@noImplicitAny: true -var a: [any]; - -var b = a = [undefined, null]; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/tuple/wideningTuples4.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/tuple/wideningTuples4.ts.snap deleted file mode 100644 index de9f9892218..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/tuple/wideningTuples4.ts.snap +++ /dev/null @@ -1,38 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -var a: [any]; - -var b = a = [undefined, null]; -b = ["", ""]; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,4 +1,4 @@ - var a: [any]; - --var b = (a = [undefined, null]); -+var b = a = [undefined, null]; - b = ["", ""]; -``` - -# Output - -```js -var a: [any]; - -var b = a = [undefined, null]; -b = ["", ""]; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/tuple/wideningTuples7.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/tuple/wideningTuples7.ts.snap deleted file mode 100644 index de143678efd..00000000000 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/conformance/types/tuple/wideningTuples7.ts.snap +++ /dev/null @@ -1,41 +0,0 @@ ---- -source: crates/rome_js_formatter/tests/prettier_tests.rs ---- - -# Input - -```js -//@noImplicitAny: true -var foo = function bar() { - let intermediate: [string]; - return intermediate = [undefined]; -}; -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Rome -@@ -1,5 +1,5 @@ - //@noImplicitAny: true - var foo = function bar() { - let intermediate: [string]; -- return (intermediate = [undefined]); -+ return intermediate = [undefined]; - }; -``` - -# Output - -```js -//@noImplicitAny: true -var foo = function bar() { - let intermediate: [string]; - return intermediate = [undefined]; -}; -``` - - - diff --git a/crates/rome_js_formatter/tests/specs/prettier/typescript/export-default/function_as.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/export-default/function_as.ts.snap new file mode 100644 index 00000000000..8ea53add4b4 --- /dev/null +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/export-default/function_as.ts.snap @@ -0,0 +1,29 @@ +--- +source: crates/rome_js_formatter/tests/prettier_tests.rs +--- + +# Input + +```js +export default (function log(){} as typeof console.log); +``` + + +# Prettier differences + +```diff +--- Prettier ++++ Rome +@@ -1 +1 @@ +-export default (function log() {} as typeof console.log); ++export default (function log() {}) as typeof console.log; +``` + +# Output + +```js +export default (function log() {}) as typeof console.log; +``` + + + 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 97d0c17d9d2..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,8 +1,9 @@ --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; -@@ -10,8 +11,8 @@ + 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_formatter/tests/specs/prettier/typescript/type-arguments-bit-shift-left-like/1.ts.snap b/crates/rome_js_formatter/tests/specs/prettier/typescript/type-arguments-bit-shift-left-like/1.ts.snap index fa28764dcea..6d63a3a07df 100644 --- a/crates/rome_js_formatter/tests/specs/prettier/typescript/type-arguments-bit-shift-left-like/1.ts.snap +++ b/crates/rome_js_formatter/tests/specs/prettier/typescript/type-arguments-bit-shift-left-like/1.ts.snap @@ -16,13 +16,13 @@ f<<(x) +++ Rome @@ -1 +1 @@ -f << (x); -+f << (x); ++f << x; ``` # Output ```js -f << (x); +f << x; ``` diff --git a/crates/rome_js_formatter/tests/specs/ts/expression/type_assertion_expression.ts.snap b/crates/rome_js_formatter/tests/specs/ts/expression/type_assertion_expression.ts.snap index 0e11130d3f5..caef85f20a5 100644 --- a/crates/rome_js_formatter/tests/specs/ts/expression/type_assertion_expression.ts.snap +++ b/crates/rome_js_formatter/tests/specs/ts/expression/type_assertion_expression.ts.snap @@ -21,5 +21,5 @@ Quote properties: As needed ----- let x = "hello"; let y = x; -var d = ({ name: "foo", message: "bar" }); +var d = { name: "foo", message: "bar" }; diff --git a/crates/rome_js_formatter/tests/specs/ts/parenthesis.ts.snap b/crates/rome_js_formatter/tests/specs/ts/parenthesis.ts.snap index 4c00d4e5569..015dab289c4 100644 --- a/crates/rome_js_formatter/tests/specs/ts/parenthesis.ts.snap +++ b/crates/rome_js_formatter/tests/specs/ts/parenthesis.ts.snap @@ -16,6 +16,6 @@ Quote style: Double Quotes Quote properties: As needed ----- const a = (c && b) as boolean; -const a = (c && b) as boolean; +const a = ((c && b)) as boolean; const a = !(c && b) as boolean; diff --git a/crates/rome_js_syntax/src/expr_ext.rs b/crates/rome_js_syntax/src/expr_ext.rs index db9af1ad6c3..ae813315a70 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,112 @@ impl JsRegexLiteralExpression { Ok(String::from(&text_trimmed[1..end_slash_pos])) } } + +impl JsStaticMemberExpression { + /// Returns `true` if this is an optional member access + /// + /// ```javascript + /// a.b -> false, + /// a?.b -> true + /// a?.[b][c][d].e -> false + /// ``` + pub fn is_optional(&self) -> bool { + self.operator_token() + .map_or(false, |token| token.kind() == JsSyntaxKind::QUESTIONDOT) + } + + /// Returns true if this member has an optional token or any member expression on the left side. + /// + /// ```javascript + /// a.b -> false + /// a?.b-> true + /// a?.[b][c][d].e -> true + /// ``` + pub fn is_optional_chain(&self) -> bool { + is_optional_chain(self.clone().into()) + } +} + +impl JsComputedMemberExpression { + /// Returns `true` if this is an optional member access + /// + /// ```javascript + /// a[b] -> false, + /// a?.[b] -> true + /// a?.b.c.d[e] -> false + /// ``` + pub fn is_optional(&self) -> bool { + self.optional_chain_token().is_some() + } + + /// Returns true if this member has an optional token or any member expression on the left side. + /// + /// ```javascript + /// a[b] -> false + /// a?.[b]-> true + /// a?.b.c.d[e] -> true + /// ``` + pub fn is_optional_chain(&self) -> bool { + is_optional_chain(self.clone().into()) + } +} + +impl JsCallExpression { + /// Returns `true` if this is an optional member access + /// + /// ```javascript + /// a() -> false, + /// a?.() -> true + /// a?.b() -> false + /// ``` + pub fn is_optional(&self) -> bool { + self.optional_chain_token().is_some() + } + + /// Returns true if this member has an optional token or any member expression on the left side. + /// + /// ```javascript + /// a() -> false + /// a?.()-> true + /// a?.b.c.d() -> true + /// ``` + 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 +} diff --git a/crates/rome_rowan/src/syntax/token.rs b/crates/rome_rowan/src/syntax/token.rs index 6a0d15613a2..34ba241c8d6 100644 --- a/crates/rome_rowan/src/syntax/token.rs +++ b/crates/rome_rowan/src/syntax/token.rs @@ -322,26 +322,9 @@ impl fmt::Debug for SyntaxToken { self.text_trimmed() )?; - write!(f, "[")?; - let mut first_piece = true; - for piece in self.leading_trivia().pieces() { - if !first_piece { - write!(f, ", ")?; - } - first_piece = false; - write!(f, "{:?}", piece)?; - } - write!(f, "] [")?; - - let mut first_piece = true; - for piece in self.trailing_trivia().pieces() { - if !first_piece { - write!(f, ", ")?; - } - first_piece = false; - write!(f, "{:?}", piece)?; - } - write!(f, "]") + self.leading_trivia().fmt(f)?; + write!(f, " ")?; + self.trailing_trivia().fmt(f) } } diff --git a/crates/rome_rowan/src/syntax/trivia.rs b/crates/rome_rowan/src/syntax/trivia.rs index 8039e229836..d15f1a15e50 100644 --- a/crates/rome_rowan/src/syntax/trivia.rs +++ b/crates/rome_rowan/src/syntax/trivia.rs @@ -1,5 +1,6 @@ use crate::{cursor, Language, SyntaxToken}; use std::fmt; +use std::fmt::Formatter; use std::marker::PhantomData; use text_size::{TextRange, TextSize}; @@ -642,3 +643,20 @@ fn print_debug_str>(text: S, f: &mut fmt::Formatter<'_>) -> fmt::R write!(f, "") } } + +impl std::fmt::Debug for SyntaxTrivia { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "[")?; + let mut first_piece = true; + + for piece in self.pieces() { + if !first_piece { + write!(f, ", ")?; + } + first_piece = false; + write!(f, "{:?}", piece)?; + } + + write!(f, "]") + } +} diff --git a/editors/vscode/src/commands/syntaxTree.ts b/editors/vscode/src/commands/syntaxTree.ts index 061eecf9c27..17763a1cc2f 100644 --- a/editors/vscode/src/commands/syntaxTree.ts +++ b/editors/vscode/src/commands/syntaxTree.ts @@ -143,9 +143,9 @@ export function syntaxTree(session: Session): Command { return async () => { const document = await workspace.openTextDocument(provider.uri); provider.eventEmitter.fire(provider.uri); - void await window.showTextDocument(document, { + void (await window.showTextDocument(document, { viewColumn: ViewColumn.Two, preserveFocus: true, - }); + })); }; } diff --git a/website/playground/src/utils.ts b/website/playground/src/utils.ts index 8ac538daca2..932dbf16c32 100644 --- a/website/playground/src/utils.ts +++ b/website/playground/src/utils.ts @@ -58,13 +58,13 @@ export function usePlaygroundState( searchParams.get("lineWidth") ?? defaultRomeConfig.lineWidth, ), indentStyle: - ( - searchParams.get("indentStyle") as IndentStyle - ) ?? defaultRomeConfig.indentStyle, + (searchParams.get( + "indentStyle", + ) as IndentStyle) ?? defaultRomeConfig.indentStyle, quoteStyle: - ( - searchParams.get("quoteStyle") as QuoteStyle - ) ?? defaultRomeConfig.quoteStyle, + (searchParams.get( + "quoteStyle", + ) as QuoteStyle) ?? defaultRomeConfig.quoteStyle, indentWidth: parseInt( searchParams.get("indentWidth") ?? defaultRomeConfig.indentWidth, ), @@ -74,9 +74,9 @@ export function usePlaygroundState( ) === "true" || defaultRomeConfig.isTypeScript, isJsx: searchParams.get("jsx") === "true" || defaultRomeConfig.isJsx, sourceType: - ( - searchParams.get("sourceType") as SourceType - ) ?? defaultRomeConfig.sourceType, + (searchParams.get( + "sourceType", + ) as SourceType) ?? defaultRomeConfig.sourceType, cursorPosition: 0, }); const [playgroundState, setPlaygroundState] = useState(initState());