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 ?? <> }
+ ) +} +```