diff --git a/CHANGELOG.md b/CHANGELOG.md
index 42dd50991f6d..c27aaf256a9d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -143,6 +143,17 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b
- [noUselessFragments](https://biomejs.dev/linter/rules/no-useless-fragments/) don't create invaild JSX code when Fragments children contains JSX Expression and in a LogicalExpression. Contributed by @fireairforce
+- [noUselessFragments](https://biomejs.dev/linter/rules/no-useless-fragments/) Fragments containing HTML escapes (e.g. ) inside expression escapes `{ ... }` should not be considered useless.
+The following code is no longer reported:
+
+```jsx
+function Component() {
+ return (
+
{line || <> >}
+ )
+}
+```
+
### Parser
#### Bug fixes
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 a846f71944b5..e280e244de73 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
@@ -11,7 +11,7 @@ use biome_js_factory::make::{
use biome_js_syntax::{
AnyJsxChild, AnyJsxElementName, AnyJsxTag, JsLanguage, JsLogicalExpression,
JsParenthesizedExpression, JsSyntaxKind, JsxChildList, JsxElement, JsxExpressionAttributeValue,
- JsxFragment, JsxTagExpression, JsxText, T,
+ JsxExpressionChild, JsxFragment, JsxTagExpression, JsxText, T,
};
use biome_rowan::{declare_node_union, AstNode, AstNodeList, BatchMutation, BatchMutationExt};
@@ -124,6 +124,7 @@ impl Rule for NoUselessFragments {
let model = ctx.model();
let mut in_jsx_attr_expr = false;
let mut in_js_logical_expr = false;
+ let mut in_jsx_expr = false;
match node {
NoUselessFragmentsQuery::JsxFragment(fragment) => {
let parents_where_fragments_must_be_preserved = node.syntax().parent().map_or(
@@ -139,6 +140,9 @@ impl Rule for NoUselessFragments {
if JsLogicalExpression::can_cast(parent.kind()) {
in_js_logical_expr = true;
}
+ if JsxExpressionChild::can_cast(parent.kind()) {
+ in_jsx_expr = true;
+ }
match JsParenthesizedExpression::try_cast(parent) {
Ok(parenthesized_expression) => {
parenthesized_expression.syntax().parent()
@@ -190,7 +194,17 @@ impl Rule for NoUselessFragments {
}
}
JsSyntaxKind::JSX_TEXT => {
- if !child.syntax().text().to_string().trim().is_empty() {
+ let child_text =
+ child.syntax().text().to_string().trim().to_string();
+
+ if (in_jsx_expr || in_js_logical_expr)
+ && contains_html_entity(&child_text)
+ {
+ children_where_fragments_must_preserved = true;
+ break;
+ }
+
+ if !child_text.is_empty() {
significant_children += 1;
if first_significant_child.is_none() {
first_significant_child = Some(child);
@@ -401,3 +415,7 @@ impl Rule for NoUselessFragments {
}))
}
}
+
+fn contains_html_entity(s: &str) -> bool {
+ s.contains("&") && s.contains(";")
+}
diff --git a/crates/biome_js_analyze/tests/specs/complexity/noUselessFragments/issue_4059.jsx b/crates/biome_js_analyze/tests/specs/complexity/noUselessFragments/issue_4059.jsx
new file mode 100644
index 000000000000..2bd7ec6335c5
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/complexity/noUselessFragments/issue_4059.jsx
@@ -0,0 +1,17 @@
+function MyComponent() {
+ return (
+ {line || <> >}
+ )
+}
+
+function MyComponent2() {
+ return (
+ {<> >}
+ )
+}
+
+function MyComponent3() {
+ return (
+ {value ?? <> >}
+ )
+}
\ No newline at end of file
diff --git a/crates/biome_js_analyze/tests/specs/complexity/noUselessFragments/issue_4059.jsx.snap b/crates/biome_js_analyze/tests/specs/complexity/noUselessFragments/issue_4059.jsx.snap
new file mode 100644
index 000000000000..4dc837242e07
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/complexity/noUselessFragments/issue_4059.jsx.snap
@@ -0,0 +1,24 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: issue_4059.jsx
+---
+# Input
+```jsx
+function MyComponent() {
+ return (
+ {line || <> >}
+ )
+}
+
+function MyComponent2() {
+ return (
+ {<> >}
+ )
+}
+
+function MyComponent3() {
+ return (
+ {value ?? <> >}
+ )
+}
+```