From 366aa746926f8b35f2e57aefca8dee994410e0c7 Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Mon, 22 Dec 2025 00:08:19 +0545 Subject: [PATCH 01/12] fix(lint): Added more test cases for safe literals in `noLeakedRender` This PR fixes the issues with false positive errors for safe literals --- .../src/lint/nursery/no_leaked_render.rs | 88 ++++++++++++------- .../specs/nursery/noLeakedRender/invalid.jsx | 12 +++ .../nursery/noLeakedRender/invalid.jsx.snap | 50 +++++++++++ .../specs/nursery/noLeakedRender/valid.jsx | 20 +++++ .../nursery/noLeakedRender/valid.jsx.snap | 20 +++++ 5 files changed, 156 insertions(+), 34 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs index 7acaaf49db20..6f56cd4b0b0c 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs @@ -2,10 +2,11 @@ use biome_analyze::{ Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, }; use biome_console::markup; +use biome_js_semantic::SemanticModel; use biome_js_syntax::{ - AnyJsExpression, JsConditionalExpression, JsLogicalExpression, JsLogicalOperator, JsSyntaxNode, - JsxExpressionChild, JsxTagExpression, binding_ext::AnyJsBindingDeclaration, - jsx_ext::AnyJsxElement, + AnyJsExpression, AnyJsLiteralExpression, JsConditionalExpression, JsLogicalExpression, + JsLogicalOperator, JsReferenceIdentifier, JsSyntaxNode, JsxExpressionChild, JsxTagExpression, + binding_ext::AnyJsBindingDeclaration, jsx_ext::AnyJsxElement, }; use biome_rowan::{AstNode, declare_node_union}; use biome_rule_options::no_leaked_render::NoLeakedRenderOptions; @@ -98,7 +99,7 @@ declare_lint_rule! { impl Rule for NoLeakedRender { type Query = Semantic; - type State = bool; + type State = (); type Signals = Option; type Options = NoLeakedRenderOptions; @@ -165,38 +166,43 @@ impl Rule for NoLeakedRender { if let AnyJsExpression::JsIdentifierExpression(ident) = &left { let name = ident.name().ok()?; - // Use the semantic model to resolve the variable binding and check - // if it's initialized with a boolean literal. This allows us to + // if it's initialized with safe expressions. This allows us to // handle cases like: // let isOpen = false; // This is safe + // let isError = errorCount > 0; // This is safe + // let isEqual = a === b; // This is safe + // let emptyStr = ''; // This is unsafe // return
{isOpen && }
; // This should pass - if let Some(binding) = model.binding(&name) - && binding - .tree() - .declaration() - .and_then(|declaration| { - if let AnyJsBindingDeclaration::JsVariableDeclarator(declarator) = - declaration - { - Some(declarator) - } else { - None - } - }) - .and_then(|declarator| declarator.initializer()) - .and_then(|initializer| initializer.expression().ok()) - .and_then(|expr| { - if let AnyJsExpression::AnyJsLiteralExpression(literal) = expr { - Some(literal) - } else { - None - } - }) - .and_then(|literal| literal.value_token().ok()) - .is_some_and(|token| matches!(token.text_trimmed(), "true" | "false")) - { - return None; + // return
{isError && }
; // This should pass + // return
{isEqual && }
; // This should pass + // return
{emptyStr && }
; // This should fail + + if let Some(variable) = find_variable(model, &name) { + match variable { + AnyJsExpression::AnyJsLiteralExpression(expr) => { + match expr { + AnyJsLiteralExpression::JsStringLiteralExpression(str) => { + if str.value_token().ok()?.text_trimmed().is_empty() { + return Some(()); + } + return None; + } + AnyJsLiteralExpression::JsNumberLiteralExpression(num) => { + let value = num.value_token().ok()?; + if value.text_trimmed() == "0" { + return Some(()); + } + return None; + } + _ => return None, + }; + } + AnyJsExpression::JsUnaryExpression(_) + | AnyJsExpression::JsBinaryExpression(_) + | AnyJsExpression::JsCallExpression(_) => return None, + _ => {} + } } } @@ -205,7 +211,7 @@ impl Rule for NoLeakedRender { return None; } - Some(true) + Some(()) } NoLeakedRenderQuery::JsConditionalExpression(expr) => { let alternate = expr.alternate().ok()?; @@ -216,7 +222,7 @@ impl Rule for NoLeakedRender { return None; } - Some(true) + Some(()) } } } @@ -276,3 +282,17 @@ fn is_inside_jsx_expression(node: &JsSyntaxNode) -> Option { || AnyJsxElement::can_cast(parent.kind()), ) } + +fn find_variable(model: &SemanticModel, name: &JsReferenceIdentifier) -> Option { + let Some(binding) = model.binding(name) else { + return None; + }; + + let declaration = binding.tree().declaration()?; + let AnyJsBindingDeclaration::JsVariableDeclarator(declarator) = declaration else { + return None; + }; + + let expr = declarator.initializer()?.expression().ok()?; + return Some(expr); +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx index e9d248104325..fa113ca2c3d2 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx @@ -67,3 +67,15 @@ const MyComponent5 = ({ data }) => { const MyComponent6 = ({ value }) => { return
{(((value))) && }
; }; + +const isNaN = NaN; +const isZero = 0; +const emptyStr = ''; +const MyComponent7 = () => { + return <> +
{isNan && 'NaN'}
+
{isZero && 'Zero'}
+
{emptyStr && "Empty String"}
+ +} + diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap index eba5ab0fde20..2bcea00349a6 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap @@ -74,6 +74,18 @@ const MyComponent6 = ({ value }) => { return
{(((value))) && }
; }; +const isNaN = NaN; +const isZero = 0; +const emptyStr = ''; +const MyComponent7 = () => { + return <> +
{isNan && 'NaN'}
+
{isZero && 'Zero'}
+
{emptyStr && "Empty String"}
+ +} + + ``` # Diagnostics @@ -338,3 +350,41 @@ invalid.jsx:68:15 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` + +``` +invalid.jsx:76:10 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 74 │ const MyComponent7 = () => { + 75 │ return <> + > 76 │
{isNan && 'NaN'}
+ │ ^^^^^^^^^^^^^^ + 77 │
{isZero && 'Zero'}
+ 78 │
{emptyStr && "Empty String"}
+ + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` + +``` +invalid.jsx:77:10 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 75 │ return <> + 76 │
{isNan && 'NaN'}
+ > 77 │
{isZero && 'Zero'}
+ │ ^^^^^^^^^^^^^^^^ + 78 │
{emptyStr && "Empty String"}
+ 79 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx index 8f8c6fbf5eb9..a79f80defe4e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx @@ -72,3 +72,23 @@ const isOpen2 = false; const Component15 = () => { return 0} />; }; + +const errorCount = 1; +const Component16 = () => { + return
{errorCount && 'Error'}
; +}; + +const isErr = errorCount > 0; +const Component17 = () => { + return
{isErr && 'Error'}
; +}; + +const someCondition = !!x; +const Component18 = () => { + return
{someCondition && 'hello'}
; +}; + +const isEqual = a == b; +const Component19 = () => { + return
{isEqual && 'Equal'}
; +}; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx.snap index 52b3aa64bf6e..bd429338edfc 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/valid.jsx.snap @@ -79,4 +79,24 @@ const Component15 = () => { return 0} />; }; +const errorCount = 1; +const Component16 = () => { + return
{errorCount && 'Error'}
; +}; + +const isErr = errorCount > 0; +const Component17 = () => { + return
{isErr && 'Error'}
; +}; + +const someCondition = !!x; +const Component18 = () => { + return
{someCondition && 'hello'}
; +}; + +const isEqual = a == b; +const Component19 = () => { + return
{isEqual && 'Equal'}
; +}; + ``` From 07c7b5ca9c6560e46e8c62ea426dd1e13e36ed27 Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Mon, 22 Dec 2025 00:23:18 +0545 Subject: [PATCH 02/12] chores(lint): added new changeset --- .changeset/curvy-worlds-invent.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .changeset/curvy-worlds-invent.md diff --git a/.changeset/curvy-worlds-invent.md b/.changeset/curvy-worlds-invent.md new file mode 100644 index 000000000000..4df02f3d18a6 --- /dev/null +++ b/.changeset/curvy-worlds-invent.md @@ -0,0 +1,31 @@ +--- +'@biomejs/biome': patch +--- + +Related to [#8306](https://github.com/biomejs/biome/issues/8491): Fixed the issue with false positive errors for safe boolean expressions. + +This new change will check for safe boolean expressions in variable declarations. + +For example + +Valid: + +```jsx +let isOne = 1; +let isPositiveNumber = number > 0; + +return
{isvalid && "One } { isPositiveNumber && "Is positive" }
+``` + +Invalid: + +```jsx +let emptyStr = ''; +let isZero = 0; + +return ( +
+ {emptyStr && 'Empty String'} {isZero && 'Number is zero'}{' '} +
+); +``` From fa57558c9af8b20f6378331560c22939ca373a28 Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Mon, 22 Dec 2025 00:28:30 +0545 Subject: [PATCH 03/12] chores: fixed the issue number --- .changeset/curvy-worlds-invent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/curvy-worlds-invent.md b/.changeset/curvy-worlds-invent.md index 4df02f3d18a6..979f840dbb56 100644 --- a/.changeset/curvy-worlds-invent.md +++ b/.changeset/curvy-worlds-invent.md @@ -2,7 +2,7 @@ '@biomejs/biome': patch --- -Related to [#8306](https://github.com/biomejs/biome/issues/8491): Fixed the issue with false positive errors for safe boolean expressions. +Related to [#8491](https://github.com/biomejs/biome/issues/8491): Fixed the issue with false positive errors for safe boolean expressions. This new change will check for safe boolean expressions in variable declarations. From 030d68aff49c4c73d1da55b92578473e90c7b448 Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Mon, 22 Dec 2025 00:29:14 +0545 Subject: [PATCH 04/12] chores: fixed the changeset --- .changeset/curvy-worlds-invent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/curvy-worlds-invent.md b/.changeset/curvy-worlds-invent.md index 979f840dbb56..31b23bd95962 100644 --- a/.changeset/curvy-worlds-invent.md +++ b/.changeset/curvy-worlds-invent.md @@ -14,7 +14,7 @@ Valid: let isOne = 1; let isPositiveNumber = number > 0; -return
{isvalid && "One } { isPositiveNumber && "Is positive" }
+return
{isOne && "One } { isPositiveNumber && "Is positive" }
``` Invalid: From 10b6a36e6edbe7be6466ff806ecc3be3e252b1d6 Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Mon, 22 Dec 2025 01:22:53 +0545 Subject: [PATCH 05/12] chores(lint): added comma to changeset --- .changeset/curvy-worlds-invent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/curvy-worlds-invent.md b/.changeset/curvy-worlds-invent.md index 31b23bd95962..080c36b3356b 100644 --- a/.changeset/curvy-worlds-invent.md +++ b/.changeset/curvy-worlds-invent.md @@ -6,7 +6,7 @@ Related to [#8491](https://github.com/biomejs/biome/issues/8491): Fixed the issu This new change will check for safe boolean expressions in variable declarations. -For example +For example, Valid: From 6341386ffd6b2c1148a0f88fb92346db5c1c1af2 Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Mon, 22 Dec 2025 01:27:22 +0545 Subject: [PATCH 06/12] chores(lint): fixed the diagnostics with string literals --- .../src/lint/nursery/no_leaked_render.rs | 2 +- .../nursery/noLeakedRender/invalid.jsx.snap | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs index 6f56cd4b0b0c..2d6f70151ff6 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs @@ -183,7 +183,7 @@ impl Rule for NoLeakedRender { AnyJsExpression::AnyJsLiteralExpression(expr) => { match expr { AnyJsLiteralExpression::JsStringLiteralExpression(str) => { - if str.value_token().ok()?.text_trimmed().is_empty() { + if str.inner_string_text().ok()?.text().is_empty() { return Some(()); } return None; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap index b7f56e260370..f6183b4d907e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap @@ -395,6 +395,8 @@ invalid.jsx:76:10 lint/nursery/noLeakedRender ━━━━━━━━━━━ i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + ``` @@ -414,5 +416,28 @@ invalid.jsx:77:10 lint/nursery/noLeakedRender ━━━━━━━━━━━ i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + +``` + +``` +invalid.jsx:78:9 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 76 │
{isNan && 'NaN'}
+ 77 │
{isZero && 'Zero'}
+ > 78 │
{emptyStr && "Empty String"}
+ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 79 │ + 80 │ } + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + ``` From b54dcbb32e0f6f56ebb93cf5f0989dd5747c19a0 Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Mon, 22 Dec 2025 14:18:07 +0545 Subject: [PATCH 07/12] fix(lint): fixed the issue with null literals --- crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs index 2d6f70151ff6..2f486cc00695 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs @@ -188,6 +188,9 @@ impl Rule for NoLeakedRender { } return None; } + AnyJsLiteralExpression::JsNullLiteralExpression(_) => { + return Some(()); + } AnyJsLiteralExpression::JsNumberLiteralExpression(num) => { let value = num.value_token().ok()?; if value.text_trimmed() == "0" { From 67c76f5e5083d718be16337a106d0b6412c9bc65 Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Mon, 22 Dec 2025 14:35:47 +0545 Subject: [PATCH 08/12] fix(lint): fixed clippy errors --- .../biome_js_analyze/src/lint/nursery/no_leaked_render.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs index 2f486cc00695..c4a791905bb4 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs @@ -287,15 +287,12 @@ fn is_inside_jsx_expression(node: &JsSyntaxNode) -> Option { } fn find_variable(model: &SemanticModel, name: &JsReferenceIdentifier) -> Option { - let Some(binding) = model.binding(name) else { - return None; - }; - + let binding = model.binding(name)?; let declaration = binding.tree().declaration()?; let AnyJsBindingDeclaration::JsVariableDeclarator(declarator) = declaration else { return None; }; let expr = declarator.initializer()?.expression().ok()?; - return Some(expr); + Some(expr) } From 4678d89f423c9f1d3748ef3486b0963d0d60428c Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 22 Dec 2025 09:22:02 +0000 Subject: [PATCH 09/12] Apply suggestion from @ematipico --- .changeset/curvy-worlds-invent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/curvy-worlds-invent.md b/.changeset/curvy-worlds-invent.md index 080c36b3356b..594c101a111a 100644 --- a/.changeset/curvy-worlds-invent.md +++ b/.changeset/curvy-worlds-invent.md @@ -14,7 +14,7 @@ Valid: let isOne = 1; let isPositiveNumber = number > 0; -return
{isOne && "One } { isPositiveNumber && "Is positive" }
+return
{isOne && "One" } { isPositiveNumber && "Is positive" }
``` Invalid: From 1c1b80a16c84d415550474b23be9c7cf3f2586ec Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Wed, 24 Dec 2025 15:19:36 +0545 Subject: [PATCH 10/12] fix(lint): fixed the issue with big integers and negative zero --- .../src/lint/nursery/no_leaked_render.rs | 118 ++++--- .../specs/nursery/noLeakedRender/invalid.jsx | 5 + .../nursery/noLeakedRender/invalid.jsx.snap | 288 ++++++++++++------ 3 files changed, 280 insertions(+), 131 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs index c4a791905bb4..c7ce7454863a 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs @@ -5,8 +5,8 @@ use biome_console::markup; use biome_js_semantic::SemanticModel; use biome_js_syntax::{ AnyJsExpression, AnyJsLiteralExpression, JsConditionalExpression, JsLogicalExpression, - JsLogicalOperator, JsReferenceIdentifier, JsSyntaxNode, JsxExpressionChild, JsxTagExpression, - binding_ext::AnyJsBindingDeclaration, jsx_ext::AnyJsxElement, + JsLogicalOperator, JsReferenceIdentifier, JsSyntaxNode, JsUnaryOperator, JsxExpressionChild, + JsxTagExpression, binding_ext::AnyJsBindingDeclaration, jsx_ext::AnyJsxElement, }; use biome_rowan::{AstNode, declare_node_union}; use biome_rule_options::no_leaked_render::NoLeakedRenderOptions; @@ -120,14 +120,7 @@ impl Rule for NoLeakedRender { } let left = exp.left().ok()?; - let is_left_hand_side_safe = matches!( - left, - AnyJsExpression::JsUnaryExpression(_) - | AnyJsExpression::JsCallExpression(_) - | AnyJsExpression::JsBinaryExpression(_) - ); - - if is_left_hand_side_safe { + if is_left_hand_side_safe(&left)? { return None; } @@ -150,13 +143,12 @@ impl Rule for NoLeakedRender { } // If we find expressions that coerce to boolean (unary, call, binary), // then the entire expression is considered safe - AnyJsExpression::JsUnaryExpression(_) - | AnyJsExpression::JsCallExpression(_) - | AnyJsExpression::JsBinaryExpression(_) => { - is_nested_left_hand_side_safe = true; - break; + expr => { + if is_left_hand_side_safe(&expr)? { + is_nested_left_hand_side_safe = true; + break; + } } - _ => {} } } @@ -173,33 +165,13 @@ impl Rule for NoLeakedRender { // let isError = errorCount > 0; // This is safe // let isEqual = a === b; // This is safe // let emptyStr = ''; // This is unsafe - // return
{isOpen && }
; // This should pass - // return
{isError && }
; // This should pass - // return
{isEqual && }
; // This should pass - // return
{emptyStr && }
; // This should fail if let Some(variable) = find_variable(model, &name) { match variable { AnyJsExpression::AnyJsLiteralExpression(expr) => { - match expr { - AnyJsLiteralExpression::JsStringLiteralExpression(str) => { - if str.inner_string_text().ok()?.text().is_empty() { - return Some(()); - } - return None; - } - AnyJsLiteralExpression::JsNullLiteralExpression(_) => { - return Some(()); - } - AnyJsLiteralExpression::JsNumberLiteralExpression(num) => { - let value = num.value_token().ok()?; - if value.text_trimmed() == "0" { - return Some(()); - } - return None; - } - _ => return None, - }; + if is_unsafe_literal(&expr)? { + return Some(()); + } } AnyJsExpression::JsUnaryExpression(_) | AnyJsExpression::JsBinaryExpression(_) @@ -209,9 +181,10 @@ impl Rule for NoLeakedRender { } } - let is_literal = matches!(left, AnyJsExpression::AnyJsLiteralExpression(_)); - if is_literal && left.to_trimmed_text().is_empty() { - return None; + if let AnyJsExpression::AnyJsLiteralExpression(literal) = left { + if is_unsafe_literal(&literal)? { + return Some(()); + } } Some(()) @@ -296,3 +269,64 @@ fn find_variable(model: &SemanticModel, name: &JsReferenceIdentifier) -> Option< let expr = declarator.initializer()?.expression().ok()?; Some(expr) } + +fn is_unsafe_literal(literal: &AnyJsLiteralExpression) -> Option { + match literal { + AnyJsLiteralExpression::JsStringLiteralExpression(str) => { + if str.inner_string_text().ok()?.text().is_empty() { + return Some(true); + } + return None; + } + AnyJsLiteralExpression::JsNullLiteralExpression(_) => { + return Some(true); + } + AnyJsLiteralExpression::JsNumberLiteralExpression(num) => { + let value = num.value_token().ok()?; + + if is_numeric_zero(value.text_trimmed()) { + return Some(true); + } + + return None; + } + + AnyJsLiteralExpression::JsBigintLiteralExpression(bnum) => { + let value = bnum.value_token().ok()?; + let literal = value.text_trimmed(); + let num = literal.strip_suffix("n").unwrap_or(literal); + + if is_numeric_zero(num) { + return Some(true); + } + return None; + } + _ => return None, + }; +} + +fn is_numeric_zero(num: &str) -> bool { + if let Ok(num) = num.parse::() { + return num == 0; + } + + if let Ok(num) = num.parse::() { + return num == 0.0; + } + + false +} + +fn is_left_hand_side_safe(expr: &AnyJsExpression) -> Option { + match expr { + AnyJsExpression::JsCallExpression(_) | AnyJsExpression::JsBinaryExpression(_) => Some(true), + AnyJsExpression::JsUnaryExpression(unary) => { + if unary.operator().ok()? == JsUnaryOperator::Minus { + Some(false) + } else { + Some(true) + } + } + _ => Some(false), + } +} diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx index fa113ca2c3d2..31e23a07b6c3 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx @@ -4,6 +4,11 @@ const Example1 = () => { return ( <> {0 && } + {0.0 && } + {-0 && } + {-0.0 && } + {0n && } + {-0n && } {'' && } {NaN && } diff --git a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap index f6183b4d907e..5b1a32b7f3fc 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noLeakedRender/invalid.jsx.snap @@ -10,6 +10,11 @@ const Example1 = () => { return ( <> {0 && } + {0.0 && } + {-0 && } + {-0.0 && } + {0n && } + {-0n && } {'' && } {NaN && } @@ -98,8 +103,8 @@ invalid.jsx:6:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━ 5 │ <> > 6 │ {0 && } │ ^^^^^^^^^^^^^^^^^^ - 7 │ {'' && } - 8 │ {NaN && } + 7 │ {0.0 && } + 8 │ {-0 && } i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -117,10 +122,10 @@ invalid.jsx:7:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━ 5 │ <> 6 │ {0 && } - > 7 │ {'' && } - │ ^^^^^^^^^^^^^^^^^^^ - 8 │ {NaN && } - 9 │ + > 7 │ {0.0 && } + │ ^^^^^^^^^^^^^^^^^^^^ + 8 │ {-0 && } + 9 │ {-0.0 && } i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -137,11 +142,116 @@ invalid.jsx:8:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. 6 │ {0 && } - 7 │ {'' && } - > 8 │ {NaN && } + 7 │ {0.0 && } + > 8 │ {-0 && } + │ ^^^^^^^^^^^^^^^^^^^ + 9 │ {-0.0 && } + 10 │ {0n && } + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + +``` + +``` +invalid.jsx:9:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 7 │ {0.0 && } + 8 │ {-0 && } + > 9 │ {-0.0 && } + │ ^^^^^^^^^^^^^^^^^^^^^ + 10 │ {0n && } + 11 │ {-0n && } + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + +``` + +``` +invalid.jsx:10:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 8 │ {-0 && } + 9 │ {-0.0 && } + > 10 │ {0n && } + │ ^^^^^^^^^^^^^^^^^^^ + 11 │ {-0n && } + 12 │ {'' && } + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + +``` + +``` +invalid.jsx:11:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 9 │ {-0.0 && } + 10 │ {0n && } + > 11 │ {-0n && } + │ ^^^^^^^^^^^^^^^^^^^^ + 12 │ {'' && } + 13 │ {NaN && } + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + +``` + +``` +invalid.jsx:12:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 10 │ {0n && } + 11 │ {-0n && } + > 12 │ {'' && } + │ ^^^^^^^^^^^^^^^^^^^ + 13 │ {NaN && } + 14 │ + + i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. + + i Make sure the condition is explicitly boolean.Use !!value, value > 0, or a ternary expression. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + +``` + +``` +invalid.jsx:13:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Potential leaked value that might cause unintended rendering. + + 11 │ {-0n && } + 12 │ {'' && } + > 13 │ {NaN && } │ ^^^^^^^^^^^^^^^^^^^^ - 9 │ - 10 │ ); + 14 │ + 15 │ ); i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -153,15 +263,15 @@ invalid.jsx:8:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━ ``` ``` -invalid.jsx:14:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:19:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 13 │ const Component1 = ({ count, title }) => { - > 14 │ return
{count && title}
; + 18 │ const Component1 = ({ count, title }) => { + > 19 │ return
{count && title}
; │ ^^^^^^^^^^^^^^ - 15 │ }; - 16 │ + 20 │ }; + 21 │ i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -173,15 +283,15 @@ invalid.jsx:14:15 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:18:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:23:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 17 │ const Component2 = ({ count }) => { - > 18 │ return
{count && There are {count} results}
; + 22 │ const Component2 = ({ count }) => { + > 23 │ return
{count && There are {count} results}
; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 19 │ }; - 20 │ + 24 │ }; + 25 │ i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -193,15 +303,15 @@ invalid.jsx:18:15 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:22:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:27:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 21 │ const Component3 = ({ elements }) => { - > 22 │ return
{elements.length && }
; + 26 │ const Component3 = ({ elements }) => { + > 27 │ return
{elements.length && }
; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 23 │ }; - 24 │ + 28 │ }; + 29 │ i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -213,16 +323,16 @@ invalid.jsx:22:15 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:27:9 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:32:9 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 25 │ const Component4 = ({ nestedCollection }) => { - 26 │ return ( - > 27 │
{nestedCollection.elements.length && }
+ 30 │ const Component4 = ({ nestedCollection }) => { + 31 │ return ( + > 32 │
{nestedCollection.elements.length && }
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 28 │ ); - 29 │ }; + 33 │ ); + 34 │ }; i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -234,15 +344,15 @@ invalid.jsx:27:9 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:32:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:37:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 31 │ const Component5 = ({ elements }) => { - > 32 │ return
{elements[0] && }
; + 36 │ const Component5 = ({ elements }) => { + > 37 │ return
{elements[0] && }
; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 33 │ }; - 34 │ + 38 │ }; + 39 │ i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -254,15 +364,15 @@ invalid.jsx:32:15 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:36:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:41:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 35 │ const Component6 = ({ numberA, numberB }) => { - > 36 │ return
{(numberA || numberB) && {numberA + numberB}}
; + 40 │ const Component6 = ({ numberA, numberB }) => { + > 41 │ return
{(numberA || numberB) && {numberA + numberB}}
; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 37 │ }; - 38 │ + 42 │ }; + 43 │ i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -274,21 +384,21 @@ invalid.jsx:36:15 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:42:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:47:5 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 40 │ return ( - 41 │ <> - > 42 │ {someCondition && ( + 45 │ return ( + 46 │ <> + > 47 │ {someCondition && ( │ ^^^^^^^^^^^^^^^^^^ - > 43 │
- > 44 │

hello

- > 45 │
- > 46 │ )} + > 48 │
+ > 49 │

hello

+ > 50 │
+ > 51 │ )} │ ^ - 47 │ - 48 │ ); + 52 │ + 53 │ ); i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -300,15 +410,15 @@ invalid.jsx:42:5 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:52:12 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:57:12 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 51 │ const MyComponent2 = () => { - > 52 │ return <>{someCondition && }; + 56 │ const MyComponent2 = () => { + > 57 │ return <>{someCondition && }; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 53 │ }; - 54 │ + 58 │ }; + 59 │ i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -320,15 +430,15 @@ invalid.jsx:52:12 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:56:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:61:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 55 │ const MyComponent3 = () => { - > 56 │ return
{maybeObject && (isFoo ? : )}
; + 60 │ const MyComponent3 = () => { + > 61 │ return
{maybeObject && (isFoo ? : )}
; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 57 │ }; - 58 │ + 62 │ }; + 63 │ i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -340,15 +450,15 @@ invalid.jsx:56:15 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:60:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:65:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 59 │ const MyComponent4 = ({ count, title }) => { - > 60 │ return
{(((((count))))) && ((title))}
; + 64 │ const MyComponent4 = ({ count, title }) => { + > 65 │ return
{(((((count))))) && ((title))}
; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 61 │ }; - 62 │ + 66 │ }; + 67 │ i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -360,15 +470,15 @@ invalid.jsx:60:15 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:68:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:73:15 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 67 │ const MyComponent6 = ({ value }) => { - > 68 │ return
{(((value))) && }
; + 72 │ const MyComponent6 = ({ value }) => { + > 73 │ return
{(((value))) && }
; │ ^^^^^^^^^^^^^^^^^^^^^^^ - 69 │ }; - 70 │ + 74 │ }; + 75 │ i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -380,16 +490,16 @@ invalid.jsx:68:15 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:76:10 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:81:10 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 74 │ const MyComponent7 = () => { - 75 │ return <> - > 76 │
{isNan && 'NaN'}
+ 79 │ const MyComponent7 = () => { + 80 │ return <> + > 81 │
{isNan && 'NaN'}
│ ^^^^^^^^^^^^^^ - 77 │
{isZero && 'Zero'}
- 78 │
{emptyStr && "Empty String"}
+ 82 │
{isZero && 'Zero'}
+ 83 │
{emptyStr && "Empty String"}
i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -401,16 +511,16 @@ invalid.jsx:76:10 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:77:10 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:82:10 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 75 │ return <> - 76 │
{isNan && 'NaN'}
- > 77 │
{isZero && 'Zero'}
+ 80 │ return <> + 81 │
{isNan && 'NaN'}
+ > 82 │
{isZero && 'Zero'}
│ ^^^^^^^^^^^^^^^^ - 78 │
{emptyStr && "Empty String"}
- 79 │ + 83 │
{emptyStr && "Empty String"}
+ 84 │ i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. @@ -422,16 +532,16 @@ invalid.jsx:77:10 lint/nursery/noLeakedRender ━━━━━━━━━━━ ``` ``` -invalid.jsx:78:9 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.jsx:83:9 lint/nursery/noLeakedRender ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ i Potential leaked value that might cause unintended rendering. - 76 │
{isNan && 'NaN'}
- 77 │
{isZero && 'Zero'}
- > 78 │
{emptyStr && "Empty String"}
+ 81 │
{isNan && 'NaN'}
+ 82 │
{isZero && 'Zero'}
+ > 83 │
{emptyStr && "Empty String"}
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^ - 79 │ - 80 │ } + 84 │ + 85 │ } i JavaScript's && operator returns the left value when it's falsy (e.g., 0, NaN, ''). React will render that value, causing unexpected UI output. From ee5f1a4c3c2245f819353c0a514cc6bc6a8cdfa8 Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Wed, 24 Dec 2025 16:36:47 +0545 Subject: [PATCH 11/12] fix(lint): fixed the clippy errors and fixed changeset --- .changeset/curvy-worlds-invent.md | 2 +- .../src/lint/nursery/no_leaked_render.rs | 32 ++++++++----------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/.changeset/curvy-worlds-invent.md b/.changeset/curvy-worlds-invent.md index 594c101a111a..fe4bf03ef532 100644 --- a/.changeset/curvy-worlds-invent.md +++ b/.changeset/curvy-worlds-invent.md @@ -2,7 +2,7 @@ '@biomejs/biome': patch --- -Related to [#8491](https://github.com/biomejs/biome/issues/8491): Fixed the issue with false positive errors for safe boolean expressions. +Fixed [#8491](https://github.com/biomejs/biome/issues/8491): Fixed the issue with false positive errors for safe boolean expressions. There are still pending fixes. Head to [#8491 (comment)](https://github.com/biomejs/biome/issues/8491#issuecomment-3669243551) for more details This new change will check for safe boolean expressions in variable declarations. diff --git a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs index c7ce7454863a..e621e63e5083 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs @@ -159,12 +159,9 @@ impl Rule for NoLeakedRender { if let AnyJsExpression::JsIdentifierExpression(ident) = &left { let name = ident.name().ok()?; // Use the semantic model to resolve the variable binding and check - // if it's initialized with safe expressions. This allows us to - // handle cases like: - // let isOpen = false; // This is safe - // let isError = errorCount > 0; // This is safe - // let isEqual = a === b; // This is safe - // let emptyStr = ''; // This is unsafe + // if it's initialized with safe expressions. + // Safe: let isOpen = false; let isError = errorCount > 0; let isEqual = a === b; + // Unsafe: let emptyStr = ''; if let Some(variable) = find_variable(model, &name) { match variable { @@ -180,11 +177,10 @@ impl Rule for NoLeakedRender { } } } - - if let AnyJsExpression::AnyJsLiteralExpression(literal) = left { - if is_unsafe_literal(&literal)? { - return Some(()); - } + if let AnyJsExpression::AnyJsLiteralExpression(literal) = left + && is_unsafe_literal(&literal)? + { + return Some(()); } Some(()) @@ -276,11 +272,9 @@ fn is_unsafe_literal(literal: &AnyJsLiteralExpression) -> Option { if str.inner_string_text().ok()?.text().is_empty() { return Some(true); } - return None; - } - AnyJsLiteralExpression::JsNullLiteralExpression(_) => { - return Some(true); + None } + AnyJsLiteralExpression::JsNullLiteralExpression(_) => Some(true), AnyJsLiteralExpression::JsNumberLiteralExpression(num) => { let value = num.value_token().ok()?; @@ -288,7 +282,7 @@ fn is_unsafe_literal(literal: &AnyJsLiteralExpression) -> Option { return Some(true); } - return None; + None } AnyJsLiteralExpression::JsBigintLiteralExpression(bnum) => { @@ -299,10 +293,10 @@ fn is_unsafe_literal(literal: &AnyJsLiteralExpression) -> Option { if is_numeric_zero(num) { return Some(true); } - return None; + None } - _ => return None, - }; + _ => None, + } } fn is_numeric_zero(num: &str) -> bool { From 8910f8f3f065c7cfe351c5178924173e1bca15d4 Mon Sep 17 00:00:00 2001 From: Dibash Thapa Date: Wed, 24 Dec 2025 18:37:58 +0545 Subject: [PATCH 12/12] chores: fixed changeset --- .changeset/curvy-worlds-invent.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.changeset/curvy-worlds-invent.md b/.changeset/curvy-worlds-invent.md index fe4bf03ef532..4ffae7f0177f 100644 --- a/.changeset/curvy-worlds-invent.md +++ b/.changeset/curvy-worlds-invent.md @@ -1,8 +1,7 @@ --- '@biomejs/biome': patch --- - -Fixed [#8491](https://github.com/biomejs/biome/issues/8491): Fixed the issue with false positive errors for safe boolean expressions. There are still pending fixes. Head to [#8491 (comment)](https://github.com/biomejs/biome/issues/8491#issuecomment-3669243551) for more details +Fixed [#8491](https://github.com/biomejs/biome/issues/8491): Resolved false positive errors for safe boolean expressions. There are still pending fixes. Head to [#8491 (comment)](https://github.com/biomejs/biome/issues/8491#issuecomment-3669243551) for more details This new change will check for safe boolean expressions in variable declarations.