feat(rome_js_formatter): Parenthesize assignments
MODIFY
crates/rome_js_formatter/src/js/assignments/array_assignment_pattern.rs
crates/rome_js_formatter/src/js/assignments/array_assignment_pattern.rs
+use crate::parentheses::{AssignmentNode, NeedsParentheses};
use crate::prelude::*;
use rome_formatter::write;
-use rome_js_syntax::JsArrayAssignmentPattern;
-use rome_js_syntax::JsArrayAssignmentPatternFields;
+use rome_js_syntax::{JsAnyAssignmentPattern, JsArrayAssignmentPattern};
+use rome_js_syntax::{JsArrayAssignmentPatternFields, JsSyntaxNode};
#[derive(Debug, Clone, Default)]
pub struct FormatJsArrayAssignmentPattern;
@@ -22,4 +23,32 @@ impl FormatNodeRule<JsArrayAssignmentPattern> for FormatJsArrayAssignmentPattern
]
)
}
+
+ fn needs_parentheses(&self, item: &JsArrayAssignmentPattern) -> bool {
+ item.needs_parentheses()
+ }
+}
+
+impl AssignmentNode for JsArrayAssignmentPattern {
+ #[inline]
+ fn resolve(&self) -> JsAnyAssignmentPattern {
+ self.clone().into()
+ }
+
+ #[inline]
+ fn into_resolved(self) -> JsAnyAssignmentPattern {
+ self.into()
+ }
+}
+
+impl NeedsParentheses for JsArrayAssignmentPattern {
+ #[inline]
+ fn needs_parentheses(&self) -> bool {
+ false
+ }
+
+ #[inline]
+ fn needs_parentheses_with_parent(&self, _: &JsSyntaxNode) -> bool {
+ false
+ }
}

MODIFY
crates/rome_js_formatter/src/js/assignments/computed_member_assignment.rs
crates/rome_js_formatter/src/js/assignments/computed_member_assignment.rs
use crate::prelude::*;
use crate::js::expressions::computed_member_expression::JsAnyComputedMemberLike;
-use rome_js_syntax::JsComputedMemberAssignment;
+use crate::parentheses::{AssignmentNode, NeedsParentheses};
+use rome_js_syntax::{
+ JsAnyAssignment, JsAnyAssignmentPattern, JsComputedMemberAssignment, JsSyntaxNode,
+};
#[derive(Debug, Clone, Default)]
pub struct FormatJsComputedMemberAssignment;
@@ -14,4 +17,32 @@ impl FormatNodeRule<JsComputedMemberAssignment> for FormatJsComputedMemberAssign
) -> FormatResult<()> {
JsAnyComputedMemberLike::from(node.clone()).fmt(f)
}
+
+ fn needs_parentheses(&self, item: &JsComputedMemberAssignment) -> bool {
+ item.needs_parentheses()
+ }
+}
+
+impl AssignmentNode for JsComputedMemberAssignment {
+ #[inline]
+ fn resolve(&self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self.clone()))
+ }
+
+ #[inline]
+ fn into_resolved(self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self))
+ }
+}
+
+impl NeedsParentheses for JsComputedMemberAssignment {
+ #[inline]
+ fn needs_parentheses(&self) -> bool {
+ false
+ }
+
+ #[inline]
+ fn needs_parentheses_with_parent(&self, _: &JsSyntaxNode) -> bool {
+ false
+ }
}

MODIFY
crates/rome_js_formatter/src/js/assignments/identifier_assignment.rs
crates/rome_js_formatter/src/js/assignments/identifier_assignment.rs
use crate::prelude::*;
use rome_formatter::write;
-use rome_js_syntax::JsIdentifierAssignment;
-use rome_js_syntax::JsIdentifierAssignmentFields;
+use crate::parentheses::{AssignmentNode, NeedsParentheses};
+use rome_js_syntax::{JsAnyAssignment, JsForOfStatement, JsIdentifierAssignmentFields};
+use rome_js_syntax::{JsAnyAssignmentPattern, JsIdentifierAssignment, JsSyntaxNode};
#[derive(Debug, Clone, Default)]
pub struct FormatJsIdentifierAssignment;
@@ -13,4 +14,52 @@ impl FormatNodeRule<JsIdentifierAssignment> for FormatJsIdentifierAssignment {
write![f, [name_token.format()]]
}
+
+ fn needs_parentheses(&self, item: &JsIdentifierAssignment) -> bool {
+ item.needs_parentheses()
+ }
+}
+
+impl AssignmentNode for JsIdentifierAssignment {
+ #[inline]
+ fn resolve(&self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self.clone()))
+ }
+
+ #[inline]
+ fn into_resolved(self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self))
+ }
+}
+
+impl NeedsParentheses for JsIdentifierAssignment {
+ #[inline]
+ fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool {
+ let is_async = self
+ .name_token()
+ .map_or(false, |name| name.text_trimmed() == "async");
+
+ if is_async && JsForOfStatement::can_cast(parent.kind()) {
+ let for_of = JsForOfStatement::unwrap_cast(parent.clone());
+
+ for_of.await_token().is_none()
+ } else {
+ false
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use crate::{assert_needs_parentheses, assert_not_needs_parentheses};
+ use rome_js_syntax::JsIdentifierAssignment;
+
+ #[test]
+ fn needs_parentheses() {
+ assert_needs_parentheses!("for ((async) of []) {}", JsIdentifierAssignment);
+
+ assert_not_needs_parentheses!("for await (async of []) {}", JsIdentifierAssignment);
+ assert_not_needs_parentheses!("for (test of []) {}", JsIdentifierAssignment);
+ }
}

MODIFY
crates/rome_js_formatter/src/js/assignments/object_assignment_pattern.rs
crates/rome_js_formatter/src/js/assignments/object_assignment_pattern.rs
+use crate::parentheses::{AssignmentNode, NeedsParentheses};
use crate::prelude::*;
use crate::utils::JsObjectPatternLike;
use rome_formatter::write;
-use rome_js_syntax::JsObjectAssignmentPattern;
+use rome_js_syntax::{JsAnyAssignmentPattern, JsObjectAssignmentPattern, JsSyntaxNode};
#[derive(Debug, Clone, Default)]
pub struct FormatJsObjectAssignmentPattern;
@@ -14,4 +15,32 @@ impl FormatNodeRule<JsObjectAssignmentPattern> for FormatJsObjectAssignmentPatte
) -> FormatResult<()> {
write!(f, [JsObjectPatternLike::from(node.clone())])
}
+
+ fn needs_parentheses(&self, item: &JsObjectAssignmentPattern) -> bool {
+ item.needs_parentheses()
+ }
+}
+
+impl AssignmentNode for JsObjectAssignmentPattern {
+ #[inline]
+ fn resolve(&self) -> JsAnyAssignmentPattern {
+ self.clone().into()
+ }
+
+ #[inline]
+ fn into_resolved(self) -> JsAnyAssignmentPattern {
+ self.into()
+ }
+}
+
+impl NeedsParentheses for JsObjectAssignmentPattern {
+ #[inline]
+ fn needs_parentheses(&self) -> bool {
+ false
+ }
+
+ #[inline]
+ fn needs_parentheses_with_parent(&self, _: &JsSyntaxNode) -> bool {
+ false
+ }
}

MODIFY
crates/rome_js_formatter/src/js/assignments/parenthesized_assignment.rs
crates/rome_js_formatter/src/js/assignments/parenthesized_assignment.rs
+use crate::parentheses::{AssignmentNode, NeedsParentheses};
use crate::prelude::*;
use rome_formatter::write;
-use rome_js_syntax::JsParenthesizedAssignment;
-use rome_js_syntax::JsParenthesizedAssignmentFields;
+use rome_js_syntax::{JsAnyAssignmentPattern, JsParenthesizedAssignment};
+use rome_js_syntax::{JsParenthesizedAssignmentFields, JsSyntaxNode};
#[derive(Debug, Clone, Default)]
pub struct FormatJsParenthesizedAssignment;
@@ -21,10 +22,42 @@ impl FormatNodeRule<JsParenthesizedAssignment> for FormatJsParenthesizedAssignme
write![
f,
[
- l_paren_token.format(),
+ format_removed(&l_paren_token?),
assignment.format(),
- r_paren_token.format(),
+ format_removed(&r_paren_token?),
]
]
}
+
+ fn needs_parentheses(&self, item: &JsParenthesizedAssignment) -> bool {
+ item.needs_parentheses()
+ }
+}
+
+impl AssignmentNode for JsParenthesizedAssignment {
+ #[inline]
+ fn resolve(&self) -> JsAnyAssignmentPattern {
+ let assignment = self.assignment().unwrap_or_else(|_| self.clone().into());
+
+ JsAnyAssignmentPattern::JsAnyAssignment(assignment)
+ }
+
+ #[inline]
+ fn into_resolved(self) -> JsAnyAssignmentPattern {
+ let assignment = self.assignment().unwrap_or_else(|_| self.into());
+
+ JsAnyAssignmentPattern::JsAnyAssignment(assignment)
+ }
+}
+
+impl NeedsParentheses for JsParenthesizedAssignment {
+ #[inline]
+ fn needs_parentheses(&self) -> bool {
+ false
+ }
+
+ #[inline]
+ fn needs_parentheses_with_parent(&self, _: &JsSyntaxNode) -> bool {
+ false
+ }
}

MODIFY
crates/rome_js_formatter/src/js/assignments/static_member_assignment.rs
crates/rome_js_formatter/src/js/assignments/static_member_assignment.rs
use crate::js::expressions::static_member_expression::JsAnyStaticMemberLike;
+use crate::parentheses::{AssignmentNode, NeedsParentheses};
use crate::prelude::*;
-use rome_js_syntax::JsStaticMemberAssignment;
+use rome_js_syntax::{
+ JsAnyAssignment, JsAnyAssignmentPattern, JsStaticMemberAssignment, JsSyntaxNode,
+};
#[derive(Debug, Clone, Default)]
pub struct FormatJsStaticMemberAssignment;
@@ -9,4 +12,32 @@ impl FormatNodeRule<JsStaticMemberAssignment> for FormatJsStaticMemberAssignment
fn fmt_fields(&self, node: &JsStaticMemberAssignment, f: &mut JsFormatter) -> FormatResult<()> {
JsAnyStaticMemberLike::from(node.clone()).fmt(f)
}
+
+ fn needs_parentheses(&self, item: &JsStaticMemberAssignment) -> bool {
+ item.needs_parentheses()
+ }
+}
+
+impl AssignmentNode for JsStaticMemberAssignment {
+ #[inline]
+ fn resolve(&self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self.clone()))
+ }
+
+ #[inline]
+ fn into_resolved(self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self))
+ }
+}
+
+impl NeedsParentheses for JsStaticMemberAssignment {
+ #[inline]
+ fn needs_parentheses(&self) -> bool {
+ false
+ }
+
+ #[inline]
+ fn needs_parentheses_with_parent(&self, _: &JsSyntaxNode) -> bool {
+ false
+ }
}

MODIFY
crates/rome_js_formatter/src/js/expressions/static_member_expression.rs
crates/rome_js_formatter/src/js/expressions/static_member_expression.rs
use crate::prelude::*;
use crate::js::expressions::computed_member_expression::JsAnyComputedMemberLike;
-use crate::parentheses::{resolve_parent, ExpressionNode, NeedsParentheses};
+use crate::parentheses::{resolve_parent, AssignmentNode, ExpressionNode, NeedsParentheses};
use rome_formatter::{format_args, write};
use rome_js_syntax::{
JsAnyAssignment, JsAnyAssignmentPattern, JsAnyExpression, JsAnyName, JsAssignmentExpression,
- JsInitializerClause, JsParenthesizedExpression, JsStaticMemberAssignment,
- JsStaticMemberExpression, JsSyntaxKind, JsSyntaxNode, JsSyntaxToken,
+ JsInitializerClause, JsParenthesizedAssignment, JsParenthesizedExpression,
+ JsStaticMemberAssignment, JsStaticMemberExpression, JsSyntaxKind, JsSyntaxNode, JsSyntaxToken,
};
use rome_rowan::{declare_node_union, AstNode, SyntaxResult};
@@ -126,14 +126,15 @@ impl JsAnyStaticMemberLike {
let first_non_static_member_ancestor = self.syntax().ancestors().find(|parent| {
!(JsAnyStaticMemberLike::can_cast(parent.kind())
|| JsAnyComputedMemberLike::can_cast(parent.kind())
- || JsParenthesizedExpression::can_cast(parent.kind()))
+ || JsParenthesizedExpression::can_cast(parent.kind())
+ || JsParenthesizedAssignment::can_cast(parent.kind()))
});
let layout = match first_non_static_member_ancestor.and_then(JsAnyExpression::cast) {
Some(JsAnyExpression::JsNewExpression(_)) => StaticMemberLikeLayout::NoBreak,
Some(JsAnyExpression::JsAssignmentExpression(assignment)) => {
if matches!(
- assignment.left()?,
+ assignment.left()?.resolve(),
JsAnyAssignmentPattern::JsAnyAssignment(
JsAnyAssignment::JsIdentifierAssignment(_)
)

MODIFY
crates/rome_js_formatter/src/js/unknown/unknown_assignment.rs
crates/rome_js_formatter/src/js/unknown/unknown_assignment.rs
use crate::prelude::*;
-use rome_js_syntax::JsUnknownAssignment;
+use crate::parentheses::{AssignmentNode, NeedsParentheses};
+use rome_js_syntax::{
+ JsAnyAssignment, JsAnyAssignmentPattern, JsSyntaxKind, JsSyntaxNode, JsUnknownAssignment,
+};
use rome_rowan::AstNode;
#[derive(Debug, Clone, Default)]
@@ -14,4 +17,28 @@ impl FormatNodeRule<JsUnknownAssignment> for FormatJsUnknownAssignment {
) -> FormatResult<()> {
format_unknown_node(node.syntax()).fmt(formatter)
}
+
+ fn needs_parentheses(&self, item: &JsUnknownAssignment) -> bool {
+ item.needs_parentheses()
+ }
+}
+
+impl AssignmentNode for JsUnknownAssignment {
+ #[inline]
+ fn resolve(&self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self.clone()))
+ }
+
+ #[inline]
+ fn into_resolved(self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self))
+ }
+}
+
+impl NeedsParentheses for JsUnknownAssignment {
+ fn needs_parentheses_with_parent(&self, _: &JsSyntaxNode) -> bool {
+ self.syntax().parent().map_or(false, |parent| {
+ parent.kind() == JsSyntaxKind::JS_PARENTHESIZED_ASSIGNMENT
+ })
+ }
}

MODIFY
crates/rome_js_formatter/src/parentheses.rs
crates/rome_js_formatter/src/parentheses.rs
use crate::utils::{JsAnyBinaryLikeExpression, JsAnyBinaryLikeLeftExpression};
use rome_js_syntax::{
- JsAnyExpression, JsAnyFunctionBody, JsAnyLiteralExpression, JsArrowFunctionExpression,
- JsAssignmentExpression, JsBinaryExpression, JsBinaryOperator, JsComputedMemberAssignment,
- JsComputedMemberExpression, JsConditionalExpression, JsLanguage, JsSequenceExpression,
- JsSyntaxKind, JsSyntaxNode,
+ JsAnyAssignment, JsAnyAssignmentPattern, JsAnyExpression, JsAnyFunctionBody,
+ JsAnyLiteralExpression, JsArrowFunctionExpression, JsAssignmentExpression, JsBinaryExpression,
+ JsBinaryOperator, JsComputedMemberAssignment, JsComputedMemberExpression,
+ JsConditionalExpression, JsLanguage, JsSequenceExpression, JsSyntaxKind, JsSyntaxNode,
};
use rome_rowan::AstNode;
@@ -790,6 +790,155 @@ pub(crate) fn is_binary_like_left_or_right(node: &JsSyntaxNode, parent: &JsSynta
JsAnyBinaryLikeExpression::can_cast(parent.kind())
}
+/// Trait implemented by all JavaScript assignments.
+pub trait AssignmentNode: NeedsParentheses {
+ /// Resolves an assignment to the first non parenthesized assignment.
+ fn resolve(&self) -> JsAnyAssignmentPattern;
+
+ /// Consumes `self` and returns the first assignment that isn't a parenthesized assignment.
+ fn into_resolved(self) -> JsAnyAssignmentPattern;
+
+ /// Resolves an assignment to the first non parenthesized assignment and returns its [JsSyntaxNode].
+ fn resolve_syntax(&self) -> JsSyntaxNode {
+ self.resolve().into_syntax()
+ }
+
+ /// Consumes `self` and returns the [JsSyntaxNode] of the first assignment that isn't a assignment expression.
+ fn into_resolved_syntax(self) -> JsSyntaxNode {
+ self.into_resolved().into_syntax()
+ }
+}
+
+impl AssignmentNode for JsAnyAssignment {
+ fn resolve(&self) -> JsAnyAssignmentPattern {
+ match self {
+ JsAnyAssignment::JsComputedMemberAssignment(assignment) => assignment.resolve(),
+ JsAnyAssignment::JsIdentifierAssignment(assignment) => assignment.resolve(),
+ JsAnyAssignment::JsParenthesizedAssignment(assignment) => assignment.resolve(),
+ JsAnyAssignment::JsStaticMemberAssignment(assignment) => assignment.resolve(),
+ JsAnyAssignment::JsUnknownAssignment(assignment) => assignment.resolve(),
+ JsAnyAssignment::TsAsAssignment(assignment) => assignment.resolve(),
+ JsAnyAssignment::TsNonNullAssertionAssignment(assignment) => assignment.resolve(),
+ JsAnyAssignment::TsTypeAssertionAssignment(assignment) => assignment.resolve(),
+ }
+ }
+
+ fn into_resolved(self) -> JsAnyAssignmentPattern {
+ match self {
+ JsAnyAssignment::JsComputedMemberAssignment(assignment) => assignment.into_resolved(),
+ JsAnyAssignment::JsIdentifierAssignment(assignment) => assignment.into_resolved(),
+ JsAnyAssignment::JsParenthesizedAssignment(assignment) => assignment.into_resolved(),
+ JsAnyAssignment::JsStaticMemberAssignment(assignment) => assignment.into_resolved(),
+ JsAnyAssignment::JsUnknownAssignment(assignment) => assignment.into_resolved(),
+ JsAnyAssignment::TsAsAssignment(assignment) => assignment.into_resolved(),
+ JsAnyAssignment::TsNonNullAssertionAssignment(assignment) => assignment.into_resolved(),
+ JsAnyAssignment::TsTypeAssertionAssignment(assignment) => assignment.into_resolved(),
+ }
+ }
+}
+
+impl NeedsParentheses for JsAnyAssignment {
+ fn needs_parentheses(&self) -> bool {
+ match self {
+ JsAnyAssignment::JsComputedMemberAssignment(assignment) => {
+ assignment.needs_parentheses()
+ }
+ JsAnyAssignment::JsIdentifierAssignment(assignment) => assignment.needs_parentheses(),
+ JsAnyAssignment::JsParenthesizedAssignment(assignment) => {
+ assignment.needs_parentheses()
+ }
+ JsAnyAssignment::JsStaticMemberAssignment(assignment) => assignment.needs_parentheses(),
+ JsAnyAssignment::JsUnknownAssignment(assignment) => assignment.needs_parentheses(),
+ JsAnyAssignment::TsAsAssignment(assignment) => assignment.needs_parentheses(),
+ JsAnyAssignment::TsNonNullAssertionAssignment(assignment) => {
+ assignment.needs_parentheses()
+ }
+ JsAnyAssignment::TsTypeAssertionAssignment(assignment) => {
+ assignment.needs_parentheses()
+ }
+ }
+ }
+
+ fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool {
+ match self {
+ JsAnyAssignment::JsComputedMemberAssignment(assignment) => {
+ assignment.needs_parentheses_with_parent(parent)
+ }
+ JsAnyAssignment::JsIdentifierAssignment(assignment) => {
+ assignment.needs_parentheses_with_parent(parent)
+ }
+ JsAnyAssignment::JsParenthesizedAssignment(assignment) => {
+ assignment.needs_parentheses_with_parent(parent)
+ }
+ JsAnyAssignment::JsStaticMemberAssignment(assignment) => {
+ assignment.needs_parentheses_with_parent(parent)
+ }
+ JsAnyAssignment::JsUnknownAssignment(assignment) => {
+ assignment.needs_parentheses_with_parent(parent)
+ }
+ JsAnyAssignment::TsAsAssignment(assignment) => {
+ assignment.needs_parentheses_with_parent(parent)
+ }
+ JsAnyAssignment::TsNonNullAssertionAssignment(assignment) => {
+ assignment.needs_parentheses_with_parent(parent)
+ }
+ JsAnyAssignment::TsTypeAssertionAssignment(assignment) => {
+ assignment.needs_parentheses_with_parent(parent)
+ }
+ }
+ }
+}
+
+impl AssignmentNode for JsAnyAssignmentPattern {
+ fn resolve(&self) -> JsAnyAssignmentPattern {
+ match self {
+ JsAnyAssignmentPattern::JsAnyAssignment(assignment) => assignment.resolve(),
+ JsAnyAssignmentPattern::JsArrayAssignmentPattern(assignment) => assignment.resolve(),
+ JsAnyAssignmentPattern::JsObjectAssignmentPattern(assignment) => assignment.resolve(),
+ }
+ }
+
+ fn into_resolved(self) -> JsAnyAssignmentPattern {
+ match self {
+ JsAnyAssignmentPattern::JsAnyAssignment(assignment) => assignment.into_resolved(),
+ JsAnyAssignmentPattern::JsArrayAssignmentPattern(assignment) => {
+ assignment.into_resolved()
+ }
+ JsAnyAssignmentPattern::JsObjectAssignmentPattern(assignment) => {
+ assignment.into_resolved()
+ }
+ }
+ }
+}
+
+impl NeedsParentheses for JsAnyAssignmentPattern {
+ fn needs_parentheses(&self) -> bool {
+ match self {
+ JsAnyAssignmentPattern::JsAnyAssignment(assignment) => assignment.needs_parentheses(),
+ JsAnyAssignmentPattern::JsArrayAssignmentPattern(assignment) => {
+ assignment.needs_parentheses()
+ }
+ JsAnyAssignmentPattern::JsObjectAssignmentPattern(assignment) => {
+ assignment.needs_parentheses()
+ }
+ }
+ }
+
+ fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool {
+ match self {
+ JsAnyAssignmentPattern::JsAnyAssignment(assignment) => {
+ assignment.needs_parentheses_with_parent(parent)
+ }
+ JsAnyAssignmentPattern::JsArrayAssignmentPattern(assignment) => {
+ assignment.needs_parentheses_with_parent(parent)
+ }
+ JsAnyAssignmentPattern::JsObjectAssignmentPattern(assignment) => {
+ assignment.needs_parentheses_with_parent(parent)
+ }
+ }
+ }
+}
+
fn debug_assert_is_expression(node: &JsSyntaxNode) {
debug_assert!(
JsAnyExpression::can_cast(node.kind()),

MODIFY
crates/rome_js_formatter/src/ts/assignments/as_assignment.rs
crates/rome_js_formatter/src/ts/assignments/as_assignment.rs
use crate::prelude::*;
+use crate::parentheses::{AssignmentNode, NeedsParentheses};
use rome_formatter::write;
-use rome_js_syntax::TsAsAssignment;
-use rome_js_syntax::TsAsAssignmentFields;
+use rome_js_syntax::{JsAnyAssignment, JsAnyAssignmentPattern, TsAsAssignment};
+use rome_js_syntax::{JsSyntaxKind, JsSyntaxNode, TsAsAssignmentFields};
#[derive(Debug, Clone, Default)]
pub struct FormatTsAsAssignment;
@@ -26,4 +27,51 @@ impl FormatNodeRule<TsAsAssignment> for FormatTsAsAssignment {
]
]
}
+
+ fn needs_parentheses(&self, item: &TsAsAssignment) -> bool {
+ item.needs_parentheses()
+ }
+}
+
+impl AssignmentNode for TsAsAssignment {
+ #[inline]
+ fn resolve(&self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self.clone()))
+ }
+
+ #[inline]
+ fn into_resolved(self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self))
+ }
+}
+
+impl NeedsParentheses for TsAsAssignment {
+ fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool {
+ matches!(
+ parent.kind(),
+ JsSyntaxKind::JS_ASSIGNMENT_EXPRESSION
+ | JsSyntaxKind::TS_NON_NULL_ASSERTION_ASSIGNMENT
+ | JsSyntaxKind::TS_TYPE_ASSERTION_ASSIGNMENT
+ | JsSyntaxKind::JS_PRE_UPDATE_EXPRESSION
+ | JsSyntaxKind::JS_POST_UPDATE_EXPRESSION
+ | JsSyntaxKind::JS_OBJECT_ASSIGNMENT_PATTERN_PROPERTY
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use crate::assert_needs_parentheses;
+ use rome_js_syntax::TsAsAssignment;
+
+ #[test]
+ fn needs_parentheses() {
+ assert_needs_parentheses!("a as number = 'test'", TsAsAssignment);
+ assert_needs_parentheses!("(a as number)! = 'test'", TsAsAssignment);
+ assert_needs_parentheses!("(<number>(a as number)) = 'test'", TsAsAssignment);
+ assert_needs_parentheses!("++(a as number)", TsAsAssignment);
+ assert_needs_parentheses!("(a as number)--", TsAsAssignment);
+ assert_needs_parentheses!("({ a: a as number } = { a: 5 })", TsAsAssignment);
+ }
}

MODIFY
crates/rome_js_formatter/src/ts/assignments/non_null_assertion_assignment.rs
crates/rome_js_formatter/src/ts/assignments/non_null_assertion_assignment.rs
use crate::prelude::*;
+use crate::parentheses::{AssignmentNode, NeedsParentheses};
use rome_formatter::write;
-use rome_js_syntax::TsNonNullAssertionAssignment;
use rome_js_syntax::TsNonNullAssertionAssignmentFields;
+use rome_js_syntax::{
+ JsAnyAssignment, JsAnyAssignmentPattern, JsSyntaxNode, TsNonNullAssertionAssignment,
+};
#[derive(Debug, Clone, Default)]
pub struct FormatTsNonNullAssertionAssignment;
@@ -19,4 +22,32 @@ impl FormatNodeRule<TsNonNullAssertionAssignment> for FormatTsNonNullAssertionAs
} = node.as_fields();
write![f, [assignment.format(), excl_token.format()]]
}
+
+ fn needs_parentheses(&self, item: &TsNonNullAssertionAssignment) -> bool {
+ item.needs_parentheses()
+ }
+}
+
+impl AssignmentNode for TsNonNullAssertionAssignment {
+ #[inline]
+ fn resolve(&self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self.clone()))
+ }
+
+ #[inline]
+ fn into_resolved(self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self))
+ }
+}
+
+impl NeedsParentheses for TsNonNullAssertionAssignment {
+ #[inline]
+ fn needs_parentheses(&self) -> bool {
+ false
+ }
+
+ #[inline]
+ fn needs_parentheses_with_parent(&self, _parent: &JsSyntaxNode) -> bool {
+ false
+ }
}

MODIFY
crates/rome_js_formatter/src/ts/assignments/type_assertion_assignment.rs
crates/rome_js_formatter/src/ts/assignments/type_assertion_assignment.rs
use crate::prelude::*;
use rome_formatter::write;
-use rome_js_syntax::TsTypeAssertionAssignmentFields;
+use rome_js_syntax::{
+ JsAnyAssignment, JsAnyAssignmentPattern, JsSyntaxKind, JsSyntaxNode,
+ TsTypeAssertionAssignmentFields,
+};
+use crate::parentheses::{AssignmentNode, NeedsParentheses};
use rome_js_syntax::TsTypeAssertionAssignment;
#[derive(Debug, Clone, Default)]
@@ -29,4 +33,51 @@ impl FormatNodeRule<TsTypeAssertionAssignment> for FormatTsTypeAssertionAssignme
]
]
}
+
+ fn needs_parentheses(&self, item: &TsTypeAssertionAssignment) -> bool {
+ item.needs_parentheses()
+ }
+}
+
+impl AssignmentNode for TsTypeAssertionAssignment {
+ #[inline]
+ fn resolve(&self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self.clone()))
+ }
+
+ #[inline]
+ fn into_resolved(self) -> JsAnyAssignmentPattern {
+ JsAnyAssignmentPattern::JsAnyAssignment(JsAnyAssignment::from(self))
+ }
+}
+
+impl NeedsParentheses for TsTypeAssertionAssignment {
+ fn needs_parentheses_with_parent(&self, parent: &JsSyntaxNode) -> bool {
+ matches!(
+ parent.kind(),
+ JsSyntaxKind::JS_ASSIGNMENT_EXPRESSION
+ | JsSyntaxKind::TS_TYPE_ASSERTION_ASSIGNMENT
+ | JsSyntaxKind::TS_NON_NULL_ASSERTION_ASSIGNMENT
+ | JsSyntaxKind::JS_PRE_UPDATE_EXPRESSION
+ | JsSyntaxKind::JS_POST_UPDATE_EXPRESSION
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use crate::{assert_needs_parentheses, assert_not_needs_parentheses};
+ use rome_js_syntax::TsTypeAssertionAssignment;
+
+ #[test]
+ fn needs_parentheses() {
+ assert_needs_parentheses!("(<number>a) = 'test'", TsTypeAssertionAssignment);
+ assert_needs_parentheses!("(<number>a)! = 'test'", TsTypeAssertionAssignment);
+ assert_needs_parentheses!("(<number>(<any>a)) = 'test'", TsTypeAssertionAssignment[0]);
+ assert_needs_parentheses!("(<number>(<any>a)) = 'test'", TsTypeAssertionAssignment[1]);
+ assert_needs_parentheses!("++(<number>a)", TsTypeAssertionAssignment);
+ assert_needs_parentheses!("(<number>a)--", TsTypeAssertionAssignment);
+ assert_not_needs_parentheses!("({ a: <number>a } = { a: 5 })", TsTypeAssertionAssignment);
+ }
}

MODIFY
crates/rome_js_formatter/tests/specs/js/module/assignment/assignment.js.snap
crates/rome_js_formatter/tests/specs/js/module/assignment/assignment.js.snap
@@ -185,7 +185,7 @@ a ??= b;
a **= b;
a.b = c.#d;
a[b] = c[d];
-(a) = b;
+a = b;
[a, b = "b", ...c] = d;
[
fooooooooooooooooooooooooooooooooooooooooooooooooo,

DELETE
crates/rome_js_formatter/tests/specs/prettier/js/for-of/async-identifier.js.snap
----
-source: crates/rome_js_formatter/tests/prettier_tests.rs
----
-
-# Input
-
-```js
-for ((async) of []);
-for ((foo) of async);
-for ((foo) of []) async;
-
-async function f() {
- for await (async of []);
- for await ((async) of []);
- for await ((foo) of async);
- for await ((foo) of []) async;
-}
-```
-
-
-# Prettier differences
-
-```diff
---- Prettier
-+++ Rome
-@@ -1,10 +1,10 @@
- for ((async) of []);
--for (foo of async);
--for (foo of []) async;
-+for ((foo) of async);
-+for ((foo) of []) async;
-
- async function f() {
- for await (async of []);
-- for await (async of []);
-- for await (foo of async);
-- for await (foo of []) async;
-+ for await ((async) of []);
-+ for await ((foo) of async);
-+ for await ((foo) of []) async;
- }
-```
-
-# Output
-
-```js
-for ((async) of []);
-for ((foo) of async);
-for ((foo) of []) async;
-
-async function f() {
- for await (async of []);
- for await ((async) of []);
- for await ((foo) of async);
- for await ((foo) of []) async;
-}
-```
-
-
-

DELETE
crates/rome_js_formatter/tests/specs/prettier/typescript/assignment/parenthesized.ts.snap
----
-source: crates/rome_js_formatter/tests/prettier_tests.rs
----
-
-# Input
-
-```js
-// https://github.com/babel/babel/pull/12933/files
-(<number>x) = null;
-(x!) = null;
-(a as any) = null;
-(a as number) = 42;
-((a as any) as string) = null;
-```
-
-
-# Prettier differences
-
-```diff
---- Prettier
-+++ Rome
-@@ -1,6 +1,6 @@
- // https://github.com/babel/babel/pull/12933/files
- (<number>x) = null;
--x! = null;
-+(x!) = null;
- (a as any) = null;
- (a as number) = 42;
--(a as any as string) = null;
-+((a as any) as string) = null;
-```
-
-# Output
-
-```js
-// https://github.com/babel/babel/pull/12933/files
-(<number>x) = null;
-(x!) = null;
-(a as any) = null;
-(a as number) = 42;
-((a as any) as string) = null;
-```
-
-
-

MODIFY
crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/decrementAndIncrementOperators.ts.snap
crates/rome_js_formatter/tests/specs/prettier/typescript/compiler/decrementAndIncrementOperators.ts.snap
@@ -66,21 +66,6 @@ x[x++]++;
(1 + 2)++;
(1 + 2)--;
-@@ -28,10 +28,10 @@
- ++x;
- --x;
-
--x++;
----x;
-+(x)++;
-+--(x);
-
--x++;
--x--;
-+((x))++;
-+((x))--;
-
- x[x++]++;
```
# Output
@@ -116,11 +101,11 @@ x--;
++x;
--x;
-(x)++;
---(x);
+x++;
+--x;
-((x))++;
-((x))--;
+x++;
+x--;
x[x++]++;
```

feat(rome_js_formatter): Parenthesize assignments (#3083)