Skip to content

Commit

Permalink
fix(linter): noUselessFragments deal html escapes under fragements wr…
Browse files Browse the repository at this point in the history
…ong (#4107)

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
  • Loading branch information
fireairforce and ematipico authored Oct 8, 2024
1 parent d7cc01f commit f8d9173
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 3 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. `&nbsp;`) inside expression escapes `{ ... }`.
The following code is no longer reported:

```jsx
function Component() {
return (
<div key={index}>{line || <>&nbsp;</>}</div>
)
}
```

Contributed by @fireairforce

### Parser

#### Bug Fixes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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()
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function MyComponent() {
return (
<div key={index}>{line || <>&nbsp;</>}</div>
)
}

function MyComponent2() {
return (
<div key={index}>{<>&nbsp;</>}</div>
)
}

function MyComponent3() {
return (
<div key={index}>{value ?? <>&nbsp;</>}</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
source: crates/biome_js_analyze/tests/spec_tests.rs
expression: issue_4059.jsx
---
# Input
```jsx
function MyComponent() {
return (
<div key={index}>{line || <>&nbsp;</>}</div>
)
}

function MyComponent2() {
return (
<div key={index}>{<>&nbsp;</>}</div>
)
}

function MyComponent3() {
return (
<div key={index}>{value ?? <>&nbsp;</>}</div>
)
}
```

0 comments on commit f8d9173

Please sign in to comment.