Skip to content

Commit 62e5aab

Browse files
authored
fix(js_formatter): correctly handle comments placement for jsx spread expression (biomejs#2358)
1 parent d9f7047 commit 62e5aab

10 files changed

Lines changed: 232 additions & 233 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b
184184

185185
### Formatter
186186

187+
#### Bug fixes
188+
189+
- Fix [#2291](https://github.com/biomejs/biome/issues/2291) by correctly handle comment placement for JSX spread attributes and JSX spread children. Contributed by @ah-yu
190+
187191
### JavaScript APIs
188192

189193
### Linter

crates/biome_js_formatter/src/jsx/attribute/spread_attribute.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::prelude::*;
22

3+
use crate::utils::format_node_without_comments::FormatAnyJsExpressionWithoutComments;
34
use biome_formatter::write;
45
use biome_js_syntax::{JsxSpreadAttribute, JsxSpreadAttributeFields};
56

@@ -16,8 +17,34 @@ impl FormatNodeRule<JsxSpreadAttribute> for FormatJsxSpreadAttribute {
1617
} = node.as_fields();
1718

1819
let argument = argument?;
19-
let format_inner =
20-
format_with(|f| write!(f, [dotdotdot_token.format(), argument.format(),]));
20+
let format_inner = format_with(|f| {
21+
if f.comments().is_suppressed(argument.syntax()) {
22+
write!(
23+
f,
24+
[
25+
dotdotdot_token.format(),
26+
argument.format(),
27+
line_suffix_boundary()
28+
]
29+
)
30+
} else {
31+
write!(
32+
f,
33+
[
34+
format_leading_comments(argument.syntax()),
35+
dotdotdot_token.format()
36+
]
37+
)?;
38+
FormatAnyJsExpressionWithoutComments.fmt(&argument, f)?;
39+
write!(
40+
f,
41+
[
42+
format_dangling_comments(argument.syntax()).with_soft_block_indent(),
43+
format_trailing_comments(argument.syntax()),
44+
]
45+
)
46+
}
47+
});
2148

2249
write!(f, [l_curly_token.format()])?;
2350

crates/biome_js_formatter/src/jsx/auxiliary/spread_child.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::prelude::*;
22
use biome_formatter::write;
33

4+
use crate::utils::format_node_without_comments::FormatAnyJsExpressionWithoutComments;
45
use biome_js_syntax::{JsxSpreadChild, JsxSpreadChildFields};
56

67
#[derive(Debug, Clone, Default)]
@@ -18,14 +19,33 @@ impl FormatNodeRule<JsxSpreadChild> for FormatJsxSpreadChild {
1819
let expression = expression?;
1920

2021
let format_inner = format_with(|f| {
21-
write!(
22-
f,
23-
[
24-
dotdotdot_token.format(),
25-
expression.format(),
26-
line_suffix_boundary()
27-
]
28-
)
22+
if f.comments().is_suppressed(expression.syntax()) {
23+
write!(
24+
f,
25+
[
26+
dotdotdot_token.format(),
27+
expression.format(),
28+
line_suffix_boundary()
29+
]
30+
)
31+
} else {
32+
write!(
33+
f,
34+
[
35+
format_leading_comments(expression.syntax()),
36+
dotdotdot_token.format(),
37+
]
38+
)?;
39+
FormatAnyJsExpressionWithoutComments.fmt(&expression, f)?;
40+
write!(
41+
f,
42+
[
43+
format_dangling_comments(expression.syntax()).with_soft_block_indent(),
44+
format_trailing_comments(expression.syntax()),
45+
line_suffix_boundary()
46+
]
47+
)
48+
}
2949
});
3050

3151
write!(f, [l_curly_token.format()])?;
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use crate::js::bogus::bogus_expression::FormatJsBogusExpression;
2+
use crate::js::expressions::array_expression::FormatJsArrayExpression;
3+
use crate::js::expressions::arrow_function_expression::FormatJsArrowFunctionExpression;
4+
use crate::js::expressions::assignment_expression::FormatJsAssignmentExpression;
5+
use crate::js::expressions::await_expression::FormatJsAwaitExpression;
6+
use crate::js::expressions::bigint_literal_expression::FormatJsBigintLiteralExpression;
7+
use crate::js::expressions::binary_expression::FormatJsBinaryExpression;
8+
use crate::js::expressions::boolean_literal_expression::FormatJsBooleanLiteralExpression;
9+
use crate::js::expressions::call_expression::FormatJsCallExpression;
10+
use crate::js::expressions::class_expression::FormatJsClassExpression;
11+
use crate::js::expressions::computed_member_expression::FormatJsComputedMemberExpression;
12+
use crate::js::expressions::conditional_expression::FormatJsConditionalExpression;
13+
use crate::js::expressions::function_expression::FormatJsFunctionExpression;
14+
use crate::js::expressions::import_call_expression::FormatJsImportCallExpression;
15+
use crate::js::expressions::import_meta_expression::FormatJsImportMetaExpression;
16+
use crate::js::expressions::in_expression::FormatJsInExpression;
17+
use crate::js::expressions::instanceof_expression::FormatJsInstanceofExpression;
18+
use crate::js::expressions::logical_expression::FormatJsLogicalExpression;
19+
use crate::js::expressions::new_expression::FormatJsNewExpression;
20+
use crate::js::expressions::new_target_expression::FormatJsNewTargetExpression;
21+
use crate::js::expressions::null_literal_expression::FormatJsNullLiteralExpression;
22+
use crate::js::expressions::number_literal_expression::FormatJsNumberLiteralExpression;
23+
use crate::js::expressions::object_expression::FormatJsObjectExpression;
24+
use crate::js::expressions::parenthesized_expression::FormatJsParenthesizedExpression;
25+
use crate::js::expressions::post_update_expression::FormatJsPostUpdateExpression;
26+
use crate::js::expressions::pre_update_expression::FormatJsPreUpdateExpression;
27+
use crate::js::expressions::regex_literal_expression::FormatJsRegexLiteralExpression;
28+
use crate::js::expressions::sequence_expression::FormatJsSequenceExpression;
29+
use crate::js::expressions::static_member_expression::FormatJsStaticMemberExpression;
30+
use crate::js::expressions::string_literal_expression::FormatJsStringLiteralExpression;
31+
use crate::js::expressions::super_expression::FormatJsSuperExpression;
32+
use crate::js::expressions::template_expression::FormatJsTemplateExpression;
33+
use crate::js::expressions::this_expression::FormatJsThisExpression;
34+
use crate::js::expressions::unary_expression::FormatJsUnaryExpression;
35+
use crate::js::expressions::yield_expression::FormatJsYieldExpression;
36+
use crate::jsx::expressions::tag_expression::FormatJsxTagExpression;
37+
use crate::ts::expressions::as_expression::FormatTsAsExpression;
38+
use crate::ts::expressions::instantiation_expression::FormatTsInstantiationExpression;
39+
use crate::ts::expressions::non_null_assertion_expression::FormatTsNonNullAssertionExpression;
40+
use crate::ts::expressions::satisfies_expression::FormatTsSatisfiesExpression;
41+
use crate::ts::expressions::type_assertion_expression::FormatTsTypeAssertionExpression;
42+
use crate::{js::expressions::identifier_expression::FormatJsIdentifierExpression, prelude::*};
43+
use biome_js_syntax::{AnyJsExpression, AnyJsLiteralExpression};
44+
45+
#[derive(Debug, Clone, Default)]
46+
pub(crate) struct FormatAnyJsExpressionWithoutComments;
47+
48+
impl FormatRule<AnyJsExpression> for FormatAnyJsExpressionWithoutComments {
49+
type Context = JsFormatContext;
50+
fn fmt(&self, node: &AnyJsExpression, f: &mut JsFormatter) -> FormatResult<()> {
51+
match node {
52+
AnyJsExpression::AnyJsLiteralExpression(literal_expr) => match literal_expr {
53+
AnyJsLiteralExpression::JsBigintLiteralExpression(node) => {
54+
FormatJsBigintLiteralExpression.fmt_node(node, f)
55+
}
56+
AnyJsLiteralExpression::JsBooleanLiteralExpression(node) => {
57+
FormatJsBooleanLiteralExpression.fmt_node(node, f)
58+
}
59+
AnyJsLiteralExpression::JsNullLiteralExpression(node) => {
60+
FormatJsNullLiteralExpression.fmt_node(node, f)
61+
}
62+
AnyJsLiteralExpression::JsNumberLiteralExpression(node) => {
63+
FormatJsNumberLiteralExpression.fmt_node(node, f)
64+
}
65+
AnyJsLiteralExpression::JsRegexLiteralExpression(node) => {
66+
FormatJsRegexLiteralExpression.fmt_node(node, f)
67+
}
68+
AnyJsLiteralExpression::JsStringLiteralExpression(node) => {
69+
FormatJsStringLiteralExpression.fmt_node(node, f)
70+
}
71+
},
72+
AnyJsExpression::JsArrayExpression(node) => {
73+
FormatJsArrayExpression::default().fmt_node(node, f)
74+
}
75+
AnyJsExpression::JsArrowFunctionExpression(node) => {
76+
FormatJsArrowFunctionExpression::default().fmt_node(node, f)
77+
}
78+
AnyJsExpression::JsAssignmentExpression(node) => {
79+
FormatJsAssignmentExpression.fmt_node(node, f)
80+
}
81+
AnyJsExpression::JsAwaitExpression(node) => FormatJsAwaitExpression.fmt_node(node, f),
82+
AnyJsExpression::JsBinaryExpression(node) => FormatJsBinaryExpression.fmt_node(node, f),
83+
AnyJsExpression::JsBogusExpression(node) => FormatJsBogusExpression.fmt(node, f),
84+
AnyJsExpression::JsCallExpression(node) => FormatJsCallExpression.fmt_node(node, f),
85+
AnyJsExpression::JsClassExpression(node) => FormatJsClassExpression.fmt_node(node, f),
86+
AnyJsExpression::JsComputedMemberExpression(node) => {
87+
FormatJsComputedMemberExpression.fmt_node(node, f)
88+
}
89+
AnyJsExpression::JsConditionalExpression(node) => {
90+
FormatJsConditionalExpression::default().fmt_node(node, f)
91+
}
92+
AnyJsExpression::JsFunctionExpression(node) => {
93+
FormatJsFunctionExpression::default().fmt_node(node, f)
94+
}
95+
AnyJsExpression::JsIdentifierExpression(node) => {
96+
FormatJsIdentifierExpression.fmt_node(node, f)
97+
}
98+
AnyJsExpression::JsImportCallExpression(node) => {
99+
FormatJsImportCallExpression.fmt_node(node, f)
100+
}
101+
AnyJsExpression::JsImportMetaExpression(node) => {
102+
FormatJsImportMetaExpression.fmt_node(node, f)
103+
}
104+
AnyJsExpression::JsInExpression(node) => FormatJsInExpression.fmt_node(node, f),
105+
AnyJsExpression::JsInstanceofExpression(node) => {
106+
FormatJsInstanceofExpression.fmt_node(node, f)
107+
}
108+
AnyJsExpression::JsLogicalExpression(node) => {
109+
FormatJsLogicalExpression.fmt_node(node, f)
110+
}
111+
AnyJsExpression::JsNewExpression(node) => FormatJsNewExpression.fmt_node(node, f),
112+
AnyJsExpression::JsNewTargetExpression(node) => {
113+
FormatJsNewTargetExpression.fmt_node(node, f)
114+
}
115+
AnyJsExpression::JsObjectExpression(node) => FormatJsObjectExpression.fmt_node(node, f),
116+
AnyJsExpression::JsParenthesizedExpression(node) => {
117+
FormatJsParenthesizedExpression.fmt_node(node, f)
118+
}
119+
AnyJsExpression::JsPostUpdateExpression(node) => {
120+
FormatJsPostUpdateExpression.fmt_node(node, f)
121+
}
122+
AnyJsExpression::JsPreUpdateExpression(node) => {
123+
FormatJsPreUpdateExpression.fmt_node(node, f)
124+
}
125+
AnyJsExpression::JsSequenceExpression(node) => {
126+
FormatJsSequenceExpression.fmt_node(node, f)
127+
}
128+
AnyJsExpression::JsStaticMemberExpression(node) => {
129+
FormatJsStaticMemberExpression.fmt_node(node, f)
130+
}
131+
AnyJsExpression::JsSuperExpression(node) => FormatJsSuperExpression.fmt_node(node, f),
132+
AnyJsExpression::JsTemplateExpression(node) => {
133+
FormatJsTemplateExpression.fmt_node(node, f)
134+
}
135+
AnyJsExpression::JsThisExpression(node) => FormatJsThisExpression.fmt_node(node, f),
136+
AnyJsExpression::JsUnaryExpression(node) => FormatJsUnaryExpression.fmt_node(node, f),
137+
AnyJsExpression::JsYieldExpression(node) => FormatJsYieldExpression.fmt_node(node, f),
138+
AnyJsExpression::JsxTagExpression(node) => FormatJsxTagExpression.fmt_node(node, f),
139+
AnyJsExpression::TsAsExpression(node) => FormatTsAsExpression.fmt_node(node, f),
140+
AnyJsExpression::TsInstantiationExpression(node) => {
141+
FormatTsInstantiationExpression.fmt_node(node, f)
142+
}
143+
AnyJsExpression::TsNonNullAssertionExpression(node) => {
144+
FormatTsNonNullAssertionExpression.fmt_node(node, f)
145+
}
146+
AnyJsExpression::TsSatisfiesExpression(node) => {
147+
FormatTsSatisfiesExpression.fmt_node(node, f)
148+
}
149+
AnyJsExpression::TsTypeAssertionExpression(node) => {
150+
FormatTsTypeAssertionExpression.fmt_node(node, f)
151+
}
152+
}
153+
}
154+
}

crates/biome_js_formatter/src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub mod string_utils;
66

77
pub(crate) mod format_class;
88
pub(crate) mod format_modifiers;
9+
pub(crate) mod format_node_without_comments;
910
pub(crate) mod function_body;
1011
pub mod jsx;
1112
pub(crate) mod member_chain;

crates/biome_js_formatter/tests/specs/jsx/comments.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@
22
a>;
33

44
<a></ /* block1 */ // test
5-
a>;
5+
a>;
6+
7+
<div {.../* comment */a}></div>;
8+
9+
<div>{.../* comment */a}</div>;

crates/biome_js_formatter/tests/specs/jsx/comments.jsx.snap

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
source: crates/biome_formatter_test/src/snapshot_builder.rs
33
info: jsx/comments.jsx
44
---
5-
65
# Input
76

87
```jsx
@@ -11,6 +10,10 @@ a>;
1110
1211
<a></ /* block1 */ // test
1312
a>;
13+
14+
<div {.../* comment */a}></div>;
15+
16+
<div>{.../* comment */a}</div>;
1417
```
1518
1619
@@ -44,6 +47,8 @@ a>;
4447
/* block1 */ // test
4548
a
4649
>;
47-
```
4850
51+
<div {/* comment */ ...a}></div>;
4952
53+
<div>{/* comment */ ...a}</div>;
54+
```

0 commit comments

Comments
 (0)