diff --git a/CHANGELOG.md b/CHANGELOG.md
index aad4413ef214..d7fe0d4e1e1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -68,6 +68,22 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b
- Fix [#4575](https://github.com/biomejs/biome/issues/4575), don't wrap selector identation after css comments. Contributed by @fireairforce
+- Fix [#4553](https://github.com/biomejs/biome/issues/4553), `noUselessFragments` fix result has invalid syntax for JSX attribute, the follow code will fix:
+
+ ```jsx
+ Loading...>}>
+ {children}
+ ;
+ ```
+
+ it will fix as:
+
+ ```jsx
+ Loading...}>
+ {children}
+ ;
+ ```
+
### JavaScript APIs
### Linter
diff --git a/crates/biome_js_analyze/src/lint/complexity/no_useless_fragments.rs b/crates/biome_js_analyze/src/lint/complexity/no_useless_fragments.rs
index 827f53495f7e..da502a2b2816 100644
--- a/crates/biome_js_analyze/src/lint/complexity/no_useless_fragments.rs
+++ b/crates/biome_js_analyze/src/lint/complexity/no_useless_fragments.rs
@@ -5,11 +5,11 @@ use biome_analyze::context::RuleContext;
use biome_analyze::{declare_lint_rule, FixKind, Rule, RuleDiagnostic, RuleSource};
use biome_console::markup;
use biome_js_factory::make::{
- js_string_literal_expression, jsx_expression_child, jsx_string, jsx_string_literal,
- jsx_tag_expression, token, JsxExpressionChildBuilder,
+ js_string_literal_expression, jsx_expression_attribute_value, jsx_expression_child, jsx_string,
+ jsx_string_literal, jsx_tag_expression, token, JsxExpressionChildBuilder,
};
use biome_js_syntax::{
- AnyJsxChild, AnyJsxElementName, AnyJsxTag, JsLanguage, JsLogicalExpression,
+ AnyJsExpression, AnyJsxChild, AnyJsxElementName, AnyJsxTag, JsLanguage, JsLogicalExpression,
JsParenthesizedExpression, JsSyntaxKind, JsxChildList, JsxElement, JsxExpressionAttributeValue,
JsxExpressionChild, JsxFragment, JsxTagExpression, JsxText, T,
};
@@ -296,7 +296,7 @@ impl Rule for NoUselessFragments {
let node = ctx.query();
let mut mutation = ctx.root().begin();
- let in_jsx_attr = node.syntax().grand_parent().map_or(false, |parent| {
+ let is_in_jsx_attr = node.syntax().grand_parent().map_or(false, |parent| {
JsxExpressionAttributeValue::can_cast(parent.kind())
});
@@ -304,7 +304,6 @@ impl Rule for NoUselessFragments {
.syntax()
.parent()
.map_or(false, |parent| JsxChildList::can_cast(parent.kind()));
-
if is_in_list {
let new_child = match state {
NoUselessFragmentsState::Empty => None,
@@ -321,7 +320,6 @@ impl Rule for NoUselessFragments {
Some(grand_parent) => grand_parent.into_syntax(),
None => parent.into_syntax(),
};
-
let child = node
.children()
.iter()
@@ -337,7 +335,17 @@ impl Rule for NoUselessFragments {
if let Some(child) = child {
let new_node = match child {
AnyJsxChild::JsxElement(node) => {
- Some(jsx_tag_expression(AnyJsxTag::JsxElement(node)).into_syntax())
+ let jsx_tag_expr = jsx_tag_expression(AnyJsxTag::JsxElement(node));
+ if is_in_jsx_attr {
+ let jsx_expr_attr_value = jsx_expression_attribute_value(
+ token(T!['{']),
+ AnyJsExpression::JsxTagExpression(jsx_tag_expr.clone()),
+ token(T!['}']),
+ );
+ Some(jsx_expr_attr_value.into_syntax())
+ } else {
+ Some(jsx_tag_expr.into_syntax())
+ }
}
AnyJsxChild::JsxFragment(node) => {
Some(jsx_tag_expression(AnyJsxTag::JsxFragment(node)).into_syntax())
@@ -358,7 +366,7 @@ impl Rule for NoUselessFragments {
}
}
AnyJsxChild::JsxExpressionChild(child) => {
- if in_jsx_attr
+ if is_in_jsx_attr
|| !JsxTagExpression::can_cast(node.syntax().parent()?.kind())
{
child.expression().map(|expression| {
diff --git a/crates/biome_js_analyze/tests/quick_test.rs b/crates/biome_js_analyze/tests/quick_test.rs
index 901aa8a2885c..3f3b2f01f98f 100644
--- a/crates/biome_js_analyze/tests/quick_test.rs
+++ b/crates/biome_js_analyze/tests/quick_test.rs
@@ -14,7 +14,7 @@ use std::{ffi::OsStr, fs::read_to_string, path::Path, slice};
#[ignore]
#[test]
fn quick_test() {
- let input_file = Path::new("tests/specs/a11y/noAutofocus/invalid.jsx");
+ let input_file = Path::new("tests/specs/complexity/noUselessFragments/issue_4553.jsx");
let file_name = input_file.file_name().and_then(OsStr::to_str).unwrap();
let (group, rule) = parse_test_path(input_file);
diff --git a/crates/biome_js_analyze/tests/specs/complexity/noUselessFragments/issue_4553.jsx b/crates/biome_js_analyze/tests/specs/complexity/noUselessFragments/issue_4553.jsx
new file mode 100644
index 000000000000..2a20bb47d30c
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/complexity/noUselessFragments/issue_4553.jsx
@@ -0,0 +1,9 @@
+
+ Loading...
+ >
+ }
+>
+ {children}
+;
diff --git a/crates/biome_js_analyze/tests/specs/complexity/noUselessFragments/issue_4553.jsx.snap b/crates/biome_js_analyze/tests/specs/complexity/noUselessFragments/issue_4553.jsx.snap
new file mode 100644
index 000000000000..c262fe89ffb9
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/complexity/noUselessFragments/issue_4553.jsx.snap
@@ -0,0 +1,51 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: issue_4553.jsx
+snapshot_kind: text
+---
+# Input
+```jsx
+
+ Loading...
+ >
+ }
+>
+ {children}
+;
+
+```
+
+# Diagnostics
+```
+issue_4553.jsx:3:9 lint/complexity/noUselessFragments FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ ! Avoid using unnecessary Fragment.
+
+ 1 │ 3 │ <>
+ │ ^^
+ > 4 │ Loading...
+ > 5 │ >
+ │ ^^^
+ 6 │ }
+ 7 │ >
+
+ i A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
+
+ i Unsafe fix: Remove the Fragment
+
+ 1 1 │
+ 4 │ - ············Loading...
+ 5 │ - ········>
+ 6 │ - ····}
+ 2 │ + ····fallback={Loading...}
+ 7 3 │ >
+ 8 4 │ {children}
+
+
+```