diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e8ccc473fc9..36d0023ea4b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,19 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b Contributed by @Conaclos +- Fixes [#4059](https://github.com/biomejs/biome/issues/4059), the rule [noUselessFragments](https://biomejs.dev/linter/rules/no-useless-fragments/) now correctly handles fragments containing HTML escapes (e.g. ` `) inside expression escapes `{ ... }`. +The following code is no longer reported: + +```jsx +function Component() { + return ( +
{line || <> }
+ ) +} +``` + +Contributed by @fireairforce + ### 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..0043a119fe5f 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,9 +11,11 @@ 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, SyntaxNodeText, }; -use biome_rowan::{declare_node_union, AstNode, AstNodeList, BatchMutation, BatchMutationExt}; declare_lint_rule! { /// Disallow unnecessary fragments @@ -124,6 +126,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 +142,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 +196,19 @@ impl Rule for NoUselessFragments { } } JsSyntaxKind::JSX_TEXT => { - if !child.syntax().text().to_string().trim().is_empty() { + // We need to whitespaces and newlines from the original string. + // Since in the JSX newlines aren't trivia, we require to allocate a string to trim from those characters. + let original_text = child.text(); + let child_text = original_text.trim(); + + if (in_jsx_expr || in_js_logical_expr) + && contains_html_character_references(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 +419,9 @@ impl Rule for NoUselessFragments { })) } } + +fn contains_html_character_references(s: &str) -> bool { + let and = s.find('&'); + let semi = s.find(';'); + matches!((and, semi), (Some(and), Some(semi)) if and < semi) +} 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 ?? <> }
+ ) +} +```