From 31caa8970962328f92870fe6f014138e7c007488 Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:25:20 -0500 Subject: [PATCH 01/25] remove unneeded comment --- .../src/lint/nursery/no_parameters_only_used_in_recursion.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 51d46262fb32..fbbe1868d7f2 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -17,8 +17,6 @@ declare_lint_rule! { /// A parameter that is only passed to recursive calls is effectively unused /// and can be removed or replaced with a constant, simplifying the function. /// - /// This rule is inspired by Rust Clippy's `only_used_in_recursion` lint. - /// /// ## Examples /// /// ### Invalid From 1e573dd5bb2b6382b3bae3438b7f407b07effb5a Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:35:40 -0500 Subject: [PATCH 02/25] use AnyFunctionLike to detect functions --- .../no_parameters_only_used_in_recursion.rs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index fbbe1868d7f2..49bff8013a96 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -8,6 +8,7 @@ use biome_js_semantic::ReferencesExtensions; use biome_js_syntax::{ AnyJsExpression, JsAssignmentExpression, JsCallExpression, JsIdentifierBinding, JsVariableDeclarator, binding_ext::AnyJsParameterParentFunction, + function_ext::AnyFunctionLike, }; use biome_rowan::{AstNode, BatchMutationExt, TokenText}; @@ -292,7 +293,7 @@ fn get_arrow_function_name( // Stop searching if we hit a function boundary // (prevents extracting wrong name from outer scope) - if is_function_like(&ancestor) { + if AnyFunctionLike::cast_ref(&ancestor).is_some() { break; } } @@ -300,21 +301,6 @@ fn get_arrow_function_name( None } -/// Checks if a syntax node is a function-like boundary -/// (we should stop searching for names beyond these) -fn is_function_like(node: &biome_rowan::SyntaxNode) -> bool { - use biome_js_syntax::JsSyntaxKind::*; - matches!( - node.kind(), - JS_FUNCTION_DECLARATION - | JS_FUNCTION_EXPRESSION - | JS_ARROW_FUNCTION_EXPRESSION - | JS_METHOD_CLASS_MEMBER - | JS_METHOD_OBJECT_MEMBER - | JS_CONSTRUCTOR_CLASS_MEMBER - ) -} - fn is_function_signature(parent_function: &AnyJsParameterParentFunction) -> bool { matches!( parent_function, From 900780439b0e6b807a377230ec8a502f91cf6bb6 Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:46:09 -0500 Subject: [PATCH 03/25] use ancestors().skip(1) to simplify logic --- .../nursery/no_parameters_only_used_in_recursion.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 49bff8013a96..07fbe514c8bd 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -7,8 +7,7 @@ use biome_diagnostics::Severity; use biome_js_semantic::ReferencesExtensions; use biome_js_syntax::{ AnyJsExpression, JsAssignmentExpression, JsCallExpression, JsIdentifierBinding, - JsVariableDeclarator, binding_ext::AnyJsParameterParentFunction, - function_ext::AnyFunctionLike, + JsVariableDeclarator, binding_ext::AnyJsParameterParentFunction, function_ext::AnyFunctionLike, }; use biome_rowan::{AstNode, BatchMutationExt, TokenText}; @@ -261,12 +260,7 @@ fn get_arrow_function_name( let arrow_syntax = arrow_fn.syntax(); // Walk up the syntax tree to find a variable declarator or assignment - for ancestor in arrow_syntax.ancestors() { - // Skip the arrow function node itself - if ancestor == *arrow_syntax { - continue; - } - + for ancestor in arrow_syntax.ancestors().skip(1) { // Check for variable declarator: const foo = () => ... if let Some(declarator) = JsVariableDeclarator::cast_ref(&ancestor) { return declarator From dc460faf08e52104879f54122af8e586885ea24d Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:53:27 -0500 Subject: [PATCH 04/25] remove Option from TokenText for is_recursive_call --- .../no_parameters_only_used_in_recursion.rs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 07fbe514c8bd..d47453756503 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -313,20 +313,19 @@ fn is_function_signature(parent_function: &AnyJsParameterParentFunction) -> bool ) } -fn is_recursive_call(call: &JsCallExpression, function_name: Option<&TokenText>) -> bool { +fn is_recursive_call(call: &JsCallExpression, function_name: &TokenText) -> bool { let Ok(callee) = call.callee() else { return false; }; - let Some(name) = function_name else { - return false; - }; - let expr = callee.omit_parentheses(); // Simple identifier: foo() if let Some(ref_id) = expr.as_js_reference_identifier() { - return ref_id.name().ok().is_some_and(|n| n.text() == name.text()); + return ref_id + .name() + .ok() + .is_some_and(|n| n.text() == function_name.text()); } // Member expression: this.foo() or this?.foo() @@ -345,7 +344,7 @@ fn is_recursive_call(call: &JsCallExpression, function_name: Option<&TokenText>) let member_name_matches = member.member().ok().is_some_and(|m| { m.as_js_name() .and_then(|n| n.value_token().ok()) - .is_some_and(|t| t.text_trimmed() == name.text()) + .is_some_and(|t| t.text_trimmed() == function_name.text()) }); return member_name_matches; @@ -369,7 +368,7 @@ fn is_recursive_call(call: &JsCallExpression, function_name: Option<&TokenText>) && let Some(string_lit) = lit.as_js_string_literal_expression() && let Ok(text) = string_lit.inner_string_text() { - return text.text() == name.text(); + return text.text() == function_name.text(); } return false; @@ -507,8 +506,13 @@ fn is_recursive_call_with_param_usage( function_name: Option<&TokenText>, param_name: &str, ) -> bool { + // Early return if no function name (cannot be recursive) + let Some(name) = function_name else { + return false; + }; + // First check if this is a recursive call at all - if !is_recursive_call(call, function_name) { + if !is_recursive_call(call, name) { return false; } From 32d99f56aa56ec9c7309c535f322e0d474f29959 Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:58:05 -0500 Subject: [PATCH 05/25] import Reference from biome_js_semantic --- .../src/lint/nursery/no_parameters_only_used_in_recursion.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index d47453756503..6faa5cc8460a 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -4,7 +4,7 @@ use biome_analyze::{ }; use biome_console::markup; use biome_diagnostics::Severity; -use biome_js_semantic::ReferencesExtensions; +use biome_js_semantic::{Reference, ReferencesExtensions}; use biome_js_syntax::{ AnyJsExpression, JsAssignmentExpression, JsCallExpression, JsIdentifierBinding, JsVariableDeclarator, binding_ext::AnyJsParameterParentFunction, function_ext::AnyFunctionLike, @@ -378,7 +378,7 @@ fn is_recursive_call(call: &JsCallExpression, function_name: &TokenText) -> bool } fn is_reference_in_recursive_call( - reference: &biome_js_semantic::Reference, + reference: &Reference, function_name: Option<&TokenText>, parent_function: &AnyJsParameterParentFunction, param_name: &str, From 5aa577b994062fb1002872a7d283d506bfb439b9 Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:00:25 -0500 Subject: [PATCH 06/25] correct location of direct parameter reference comment --- .../src/lint/nursery/no_parameters_only_used_in_recursion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 6faa5cc8460a..0768ef5dc223 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -428,9 +428,9 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> bool { // Omit parentheses let current_expr = current_expr.omit_parentheses(); - // Direct parameter reference - found it! if let Some(ref_id) = current_expr.as_js_reference_identifier() { if ref_id.name().ok().is_some_and(|n| n.text() == param_name) { + // Found direct parameter reference return true; } continue; From 4c92b3b86b6225966c2226092faf72d9f853a1b7 Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:10:38 -0500 Subject: [PATCH 07/25] remove Option from TokenText for is_recursive_call_with_param_usage --- .../no_parameters_only_used_in_recursion.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 0768ef5dc223..411a7a0df9e2 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -383,6 +383,11 @@ fn is_reference_in_recursive_call( parent_function: &AnyJsParameterParentFunction, param_name: &str, ) -> bool { + // Early return if no function name (cannot be recursive) + let Some(name) = function_name else { + return false; + }; + let ref_node = reference.syntax(); // Walk up the tree to find if we're inside a call expression @@ -391,7 +396,7 @@ fn is_reference_in_recursive_call( // Check if this is a call expression if let Some(call_expr) = JsCallExpression::cast_ref(&node) { // Check if this call is recursive AND uses our parameter - if is_recursive_call_with_param_usage(&call_expr, function_name, param_name) { + if is_recursive_call_with_param_usage(&call_expr, name, param_name) { return true; } } @@ -503,16 +508,11 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> bool { /// Enhanced version that checks if any argument traces to parameters fn is_recursive_call_with_param_usage( call: &JsCallExpression, - function_name: Option<&TokenText>, + function_name: &TokenText, param_name: &str, ) -> bool { - // Early return if no function name (cannot be recursive) - let Some(name) = function_name else { - return false; - }; - // First check if this is a recursive call at all - if !is_recursive_call(call, name) { + if !is_recursive_call(call, function_name) { return false; } From 3e6056bae63959ed27c281acad5cb6e7a596fb2e Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:17:10 -0500 Subject: [PATCH 08/25] use try operator for function_name --- .../nursery/no_parameters_only_used_in_recursion.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 411a7a0df9e2..b0fbb6ecec3e 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -122,7 +122,7 @@ impl Rule for NoParametersOnlyUsedInRecursion { } // Get function name for recursion detection - let function_name = get_function_name(&parent_function); + let function_name = get_function_name(&parent_function)?; // Get all references to this parameter let all_refs: Vec<_> = binding.all_references(model).collect(); @@ -139,7 +139,7 @@ impl Rule for NoParametersOnlyUsedInRecursion { for reference in all_refs { if is_reference_in_recursive_call( &reference, - function_name.as_ref(), + &function_name, &parent_function, name_text, ) { @@ -379,15 +379,10 @@ fn is_recursive_call(call: &JsCallExpression, function_name: &TokenText) -> bool fn is_reference_in_recursive_call( reference: &Reference, - function_name: Option<&TokenText>, + function_name: &TokenText, parent_function: &AnyJsParameterParentFunction, param_name: &str, ) -> bool { - // Early return if no function name (cannot be recursive) - let Some(name) = function_name else { - return false; - }; - let ref_node = reference.syntax(); // Walk up the tree to find if we're inside a call expression @@ -396,7 +391,7 @@ fn is_reference_in_recursive_call( // Check if this is a call expression if let Some(call_expr) = JsCallExpression::cast_ref(&node) { // Check if this call is recursive AND uses our parameter - if is_recursive_call_with_param_usage(&call_expr, name, param_name) { + if is_recursive_call_with_param_usage(&call_expr, function_name, param_name) { return true; } } From c392c647a257b3f4ad8071827b106ea1a7bdf4dc Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:24:54 -0500 Subject: [PATCH 09/25] remove unneeded allocation by using allReferences --- .../nursery/no_parameters_only_used_in_recursion.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index b0fbb6ecec3e..44efdf919915 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -124,19 +124,11 @@ impl Rule for NoParametersOnlyUsedInRecursion { // Get function name for recursion detection let function_name = get_function_name(&parent_function)?; - // Get all references to this parameter - let all_refs: Vec<_> = binding.all_references(model).collect(); - - // If no references, let noUnusedFunctionParameters handle it - if all_refs.is_empty() { - return None; - } - // Classify references let mut refs_in_recursion = 0; let mut refs_elsewhere = 0; - for reference in all_refs { + for reference in binding.all_references(model) { if is_reference_in_recursive_call( &reference, &function_name, From a52af39409eb16f67c486badfe1b088f5138cb09 Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:33:55 -0500 Subject: [PATCH 10/25] replace Bool return types with Option(Bool) --- .../no_parameters_only_used_in_recursion.rs | 66 +++++++------------ 1 file changed, 25 insertions(+), 41 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 44efdf919915..77960a88b1a4 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -134,7 +134,9 @@ impl Rule for NoParametersOnlyUsedInRecursion { &function_name, &parent_function, name_text, - ) { + ) + .unwrap_or(false) + { refs_in_recursion += 1; } else { refs_elsewhere += 1; @@ -374,7 +376,7 @@ fn is_reference_in_recursive_call( function_name: &TokenText, parent_function: &AnyJsParameterParentFunction, param_name: &str, -) -> bool { +) -> Option { let ref_node = reference.syntax(); // Walk up the tree to find if we're inside a call expression @@ -383,8 +385,8 @@ fn is_reference_in_recursive_call( // Check if this is a call expression if let Some(call_expr) = JsCallExpression::cast_ref(&node) { // Check if this call is recursive AND uses our parameter - if is_recursive_call_with_param_usage(&call_expr, function_name, param_name) { - return true; + if is_recursive_call_with_param_usage(&call_expr, function_name, param_name)? { + return Some(true); } } @@ -396,7 +398,7 @@ fn is_reference_in_recursive_call( current = node.parent(); } - false + Some(false) } fn is_function_boundary( @@ -412,7 +414,7 @@ fn is_function_boundary( /// /// Uses an iterative approach with a worklist to avoid stack overflow /// on deeply nested expressions. -fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> bool { +fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> Option { // Worklist of expressions to examine let mut to_check = vec![expr.clone()]; @@ -423,7 +425,7 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> bool { if let Some(ref_id) = current_expr.as_js_reference_identifier() { if ref_id.name().ok().is_some_and(|n| n.text() == param_name) { // Found direct parameter reference - return true; + return Some(true); } continue; } @@ -431,48 +433,32 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> bool { // Binary operations: a + 1, a - b // Add both sides to worklist if let Some(bin_expr) = current_expr.as_js_binary_expression() { - if let Ok(left) = bin_expr.left() { - to_check.push(left); - } - if let Ok(right) = bin_expr.right() { - to_check.push(right); - } + to_check.push(bin_expr.left().ok()?); + to_check.push(bin_expr.right().ok()?); continue; } // Logical operations: a && b, a || b, a ?? b // Add both sides to worklist if let Some(logical_expr) = current_expr.as_js_logical_expression() { - if let Ok(left) = logical_expr.left() { - to_check.push(left); - } - if let Ok(right) = logical_expr.right() { - to_check.push(right); - } + to_check.push(logical_expr.left().ok()?); + to_check.push(logical_expr.right().ok()?); continue; } // Conditional expression: cond ? a : b // Add all three parts to worklist (test, consequent, alternate) if let Some(cond_expr) = current_expr.as_js_conditional_expression() { - if let Ok(test) = cond_expr.test() { - to_check.push(test); - } - if let Ok(consequent) = cond_expr.consequent() { - to_check.push(consequent); - } - if let Ok(alternate) = cond_expr.alternate() { - to_check.push(alternate); - } + to_check.push(cond_expr.test().ok()?); + to_check.push(cond_expr.consequent().ok()?); + to_check.push(cond_expr.alternate().ok()?); continue; } // Unary operations: -a, !flag // Add argument to worklist if let Some(unary_expr) = current_expr.as_js_unary_expression() { - if let Ok(arg) = unary_expr.argument() { - to_check.push(arg); - } + to_check.push(unary_expr.argument().ok()?); continue; } @@ -489,7 +475,7 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> bool { } // Didn't find the parameter anywhere - false + Some(false) } /// Enhanced version that checks if any argument traces to parameters @@ -497,19 +483,17 @@ fn is_recursive_call_with_param_usage( call: &JsCallExpression, function_name: &TokenText, param_name: &str, -) -> bool { +) -> Option { // First check if this is a recursive call at all if !is_recursive_call(call, function_name) { - return false; + return Some(false); } // Check if any argument uses the parameter - let Ok(arguments) = call.arguments() else { - return false; - }; + let arguments = call.arguments().ok()?; for arg in arguments.args() { - let Ok(arg_node) = arg else { continue }; + let arg_node = arg.ok()?; // Skip spread arguments (conservative) if arg_node.as_js_spread().is_some() { @@ -518,11 +502,11 @@ fn is_recursive_call_with_param_usage( // Check if argument expression uses the parameter if let Some(expr) = arg_node.as_any_js_expression() - && traces_to_parameter(expr, param_name) + && traces_to_parameter(expr, param_name)? { - return true; + return Some(true); } } - false + Some(false) } From ae360b4c3a5cd4cfa5b5b30df771aac7fc5ca13b Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:44:49 -0500 Subject: [PATCH 11/25] docstrings --- .../nursery/no_parameters_only_used_in_recursion.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 77960a88b1a4..de989d0e4091 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -289,6 +289,8 @@ fn get_arrow_function_name( None } +/// Returns true if the function is a TypeScript signature without an implementation body. +/// Matches interface method signatures, call signatures, function types, and declared functions. fn is_function_signature(parent_function: &AnyJsParameterParentFunction) -> bool { matches!( parent_function, @@ -307,6 +309,9 @@ fn is_function_signature(parent_function: &AnyJsParameterParentFunction) -> bool ) } +/// Checks if a call expression is a recursive call to the current function. +/// Handles direct calls (`foo()`), method calls (`this.foo()`), and computed members (`this["foo"]()`). +/// Uses a conservative approach to avoid false positives. fn is_recursive_call(call: &JsCallExpression, function_name: &TokenText) -> bool { let Ok(callee) = call.callee() else { return false; @@ -371,6 +376,9 @@ fn is_recursive_call(call: &JsCallExpression, function_name: &TokenText) -> bool false } +/// Checks if a parameter reference occurs within a recursive call expression. +/// Walks up the syntax tree from the reference to find a recursive call that uses the parameter, +/// stopping at the function boundary. fn is_reference_in_recursive_call( reference: &Reference, function_name: &TokenText, @@ -478,7 +486,9 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> Option Some(false) } -/// Enhanced version that checks if any argument traces to parameters +/// Checks if a recursive call uses a specific parameter in its arguments. +/// Examines each argument to see if it traces back to the parameter through transformations +/// like arithmetic operations, unary operations, or member access. fn is_recursive_call_with_param_usage( call: &JsCallExpression, function_name: &TokenText, From 3c16f5d04eb7de7a6c3098187477c30f793a31ea Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:19:53 -0500 Subject: [PATCH 12/25] use .kind() instead of cast_ref --- .../src/lint/nursery/no_parameters_only_used_in_recursion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index de989d0e4091..5f355753ea28 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -281,7 +281,7 @@ fn get_arrow_function_name( // Stop searching if we hit a function boundary // (prevents extracting wrong name from outer scope) - if AnyFunctionLike::cast_ref(&ancestor).is_some() { + if AnyFunctionLike::can_cast(ancestor.kind()) { break; } } From d837268089aa4905735115d5a47edd1835ef3ea5 Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:31:55 -0500 Subject: [PATCH 13/25] use Option(Bool) for is_recursive_call return type --- .../no_parameters_only_used_in_recursion.rs | 61 +++++++------------ 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 5f355753ea28..b80f81fdbbb9 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -312,68 +312,49 @@ fn is_function_signature(parent_function: &AnyJsParameterParentFunction) -> bool /// Checks if a call expression is a recursive call to the current function. /// Handles direct calls (`foo()`), method calls (`this.foo()`), and computed members (`this["foo"]()`). /// Uses a conservative approach to avoid false positives. -fn is_recursive_call(call: &JsCallExpression, function_name: &TokenText) -> bool { - let Ok(callee) = call.callee() else { - return false; - }; +fn is_recursive_call(call: &JsCallExpression, function_name: &TokenText) -> Option { + let callee = call.callee().ok()?; let expr = callee.omit_parentheses(); // Simple identifier: foo() if let Some(ref_id) = expr.as_js_reference_identifier() { - return ref_id - .name() - .ok() - .is_some_and(|n| n.text() == function_name.text()); + let name = ref_id.name().ok()?; + return Some(name.text() == function_name.text()); } // Member expression: this.foo() or this?.foo() if let Some(member) = expr.as_js_static_member_expression() { // Check if object is 'this' (for method calls) - let is_this_call = member - .object() - .ok() - .is_some_and(|obj| obj.as_js_this_expression().is_some()); - - if !is_this_call { - return false; + let object = member.object().ok()?; + if object.as_js_this_expression().is_none() { + return Some(false); } // Check if member name matches function name - let member_name_matches = member.member().ok().is_some_and(|m| { - m.as_js_name() - .and_then(|n| n.value_token().ok()) - .is_some_and(|t| t.text_trimmed() == function_name.text()) - }); - - return member_name_matches; + let member_node = member.member().ok()?; + let name = member_node.as_js_name()?; + let token = name.value_token().ok()?; + return Some(token.text_trimmed() == function_name.text()); } // Computed member expression: this["foo"]() or this?.["foo"]() if let Some(computed) = expr.as_js_computed_member_expression() { // Check if object is 'this' (for method calls) - let is_this_call = computed - .object() - .ok() - .is_some_and(|obj| obj.as_js_this_expression().is_some()); - - if !is_this_call { - return false; + let object = computed.object().ok()?; + if object.as_js_this_expression().is_none() { + return Some(false); } // Conservative approach: only handle string literal members - if let Ok(member_expr) = computed.member() - && let Some(lit) = member_expr.as_any_js_literal_expression() - && let Some(string_lit) = lit.as_js_string_literal_expression() - && let Ok(text) = string_lit.inner_string_text() - { - return text.text() == function_name.text(); - } - - return false; + let member_expr = computed.member().ok()?; + let lit = member_expr.as_any_js_literal_expression()?; + let string_lit = lit.as_js_string_literal_expression()?; + let text = string_lit.inner_string_text().ok()?; + return Some(text.text() == function_name.text()); } - false + Some(false) } /// Checks if a parameter reference occurs within a recursive call expression. @@ -495,7 +476,7 @@ fn is_recursive_call_with_param_usage( param_name: &str, ) -> Option { // First check if this is a recursive call at all - if !is_recursive_call(call, function_name) { + if !is_recursive_call(call, function_name)? { return Some(false); } From 0a5bcd77e5d1c7c44c70eeb459da5fc032fa29f4 Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 18:37:14 -0500 Subject: [PATCH 14/25] use text_timmed to compare to function name --- .../src/lint/nursery/no_parameters_only_used_in_recursion.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index b80f81fdbbb9..cddf335eb8c1 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -319,8 +319,8 @@ fn is_recursive_call(call: &JsCallExpression, function_name: &TokenText) -> Opti // Simple identifier: foo() if let Some(ref_id) = expr.as_js_reference_identifier() { - let name = ref_id.name().ok()?; - return Some(name.text() == function_name.text()); + let name = ref_id.value_token().ok()?; + return Some(name.text_trimmed() == function_name.text()); } // Member expression: this.foo() or this?.foo() @@ -350,6 +350,7 @@ fn is_recursive_call(call: &JsCallExpression, function_name: &TokenText) -> Opti let member_expr = computed.member().ok()?; let lit = member_expr.as_any_js_literal_expression()?; let string_lit = lit.as_js_string_literal_expression()?; + // Note: inner_string_text() already uses token_text_trimmed() internally let text = string_lit.inner_string_text().ok()?; return Some(text.text() == function_name.text()); } From 7f83211a5b59a03256ed8a34793a7e4892d1f082 Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:00:05 -0500 Subject: [PATCH 15/25] compare TokenText directly instead of strings --- .../lint/nursery/no_parameters_only_used_in_recursion.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index cddf335eb8c1..c1a496b6197a 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -320,7 +320,7 @@ fn is_recursive_call(call: &JsCallExpression, function_name: &TokenText) -> Opti // Simple identifier: foo() if let Some(ref_id) = expr.as_js_reference_identifier() { let name = ref_id.value_token().ok()?; - return Some(name.text_trimmed() == function_name.text()); + return Some(name.token_text_trimmed() == *function_name); } // Member expression: this.foo() or this?.foo() @@ -335,7 +335,7 @@ fn is_recursive_call(call: &JsCallExpression, function_name: &TokenText) -> Opti let member_node = member.member().ok()?; let name = member_node.as_js_name()?; let token = name.value_token().ok()?; - return Some(token.text_trimmed() == function_name.text()); + return Some(token.token_text_trimmed() == *function_name); } // Computed member expression: this["foo"]() or this?.["foo"]() @@ -350,9 +350,8 @@ fn is_recursive_call(call: &JsCallExpression, function_name: &TokenText) -> Opti let member_expr = computed.member().ok()?; let lit = member_expr.as_any_js_literal_expression()?; let string_lit = lit.as_js_string_literal_expression()?; - // Note: inner_string_text() already uses token_text_trimmed() internally let text = string_lit.inner_string_text().ok()?; - return Some(text.text() == function_name.text()); + return Some(text == *function_name); } Some(false) From db6c4a316dc91a2f2fad820b9c0f5a0a9cbe935a Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:03:27 -0500 Subject: [PATCH 16/25] check all ancestors for recursive calls --- .../src/lint/nursery/no_parameters_only_used_in_recursion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index c1a496b6197a..33c2ba0d6f91 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -374,7 +374,7 @@ fn is_reference_in_recursive_call( // Check if this is a call expression if let Some(call_expr) = JsCallExpression::cast_ref(&node) { // Check if this call is recursive AND uses our parameter - if is_recursive_call_with_param_usage(&call_expr, function_name, param_name)? { + if let Some(true) = is_recursive_call_with_param_usage(&call_expr, function_name, param_name) { return Some(true); } } From f74784b613456d90659e6ab2887aa5033590ee4a Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 00:09:58 +0000 Subject: [PATCH 17/25] [autofix.ci] apply automated fixes --- .../src/lint/nursery/no_parameters_only_used_in_recursion.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 33c2ba0d6f91..f44d9d0a2219 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -374,7 +374,9 @@ fn is_reference_in_recursive_call( // Check if this is a call expression if let Some(call_expr) = JsCallExpression::cast_ref(&node) { // Check if this call is recursive AND uses our parameter - if let Some(true) = is_recursive_call_with_param_usage(&call_expr, function_name, param_name) { + if let Some(true) = + is_recursive_call_with_param_usage(&call_expr, function_name, param_name) + { return Some(true); } } From 6feac1133e0e55b7da786334d58bf3629ed969ac Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:29:34 -0500 Subject: [PATCH 18/25] consistent conditions in woklist loop --- .../lint/nursery/no_parameters_only_used_in_recursion.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index f44d9d0a2219..e4cfcf6d4b1f 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -414,7 +414,7 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> Option let current_expr = current_expr.omit_parentheses(); if let Some(ref_id) = current_expr.as_js_reference_identifier() { - if ref_id.name().ok().is_some_and(|n| n.text() == param_name) { + if ref_id.name().ok()?.text() == param_name { // Found direct parameter reference return Some(true); } @@ -456,9 +456,9 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> Option // Static member access: obj.field // Add object to worklist if let Some(member_expr) = current_expr.as_js_static_member_expression() - && let Ok(obj) = member_expr.object() { - to_check.push(obj); + to_check.push(member_expr.object().ok()?); + continue; } // Any other expression - not safe to trace From 88e34cb94d323c29bff07380dbacc24203952b31 Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:32:43 -0500 Subject: [PATCH 19/25] lint; remove redudndant `continue` --- .../src/lint/nursery/no_parameters_only_used_in_recursion.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index e4cfcf6d4b1f..10127ede60a3 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -458,7 +458,6 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> Option if let Some(member_expr) = current_expr.as_js_static_member_expression() { to_check.push(member_expr.object().ok()?); - continue; } // Any other expression - not safe to trace From 962d38c4160831836199e2ec728366168493d029 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 00:39:25 +0000 Subject: [PATCH 20/25] [autofix.ci] apply automated fixes --- .../src/lint/nursery/no_parameters_only_used_in_recursion.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 10127ede60a3..6a6128339429 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -455,8 +455,7 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> Option // Static member access: obj.field // Add object to worklist - if let Some(member_expr) = current_expr.as_js_static_member_expression() - { + if let Some(member_expr) = current_expr.as_js_static_member_expression() { to_check.push(member_expr.object().ok()?); } From ec804863f4c75ab480adfbe0c86250d841bfdfde Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Tue, 11 Nov 2025 15:05:43 +0000 Subject: [PATCH 21/25] Update crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs --- .../src/lint/nursery/no_parameters_only_used_in_recursion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 6a6128339429..58177e1ddcda 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -135,7 +135,7 @@ impl Rule for NoParametersOnlyUsedInRecursion { &parent_function, name_text, ) - .unwrap_or(false) + .unwrap_or_default() { refs_in_recursion += 1; } else { From ed9cf16b8187bf3ca00efbcdf57ad67ef85948ab Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:34:25 -0500 Subject: [PATCH 22/25] Handle malformed subexpressions gracefully in noParametersOnlyUsedInRecursion Replace early-return error propagation with local error handling in traces_to_parameter and is_recursive_call_with_param_usage to skip malformed AST nodes instead of failing the entire analysis. --- .../no_parameters_only_used_in_recursion.rs | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index 58177e1ddcda..de3b430955a0 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -424,39 +424,57 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> Option // Binary operations: a + 1, a - b // Add both sides to worklist if let Some(bin_expr) = current_expr.as_js_binary_expression() { - to_check.push(bin_expr.left().ok()?); - to_check.push(bin_expr.right().ok()?); + if let Some(left) = bin_expr.left().ok() { + to_check.push(left); + } + if let Some(right) = bin_expr.right().ok() { + to_check.push(right); + } continue; } // Logical operations: a && b, a || b, a ?? b // Add both sides to worklist if let Some(logical_expr) = current_expr.as_js_logical_expression() { - to_check.push(logical_expr.left().ok()?); - to_check.push(logical_expr.right().ok()?); + if let Some(left) = logical_expr.left().ok() { + to_check.push(left); + } + if let Some(right) = logical_expr.right().ok() { + to_check.push(right); + } continue; } // Conditional expression: cond ? a : b // Add all three parts to worklist (test, consequent, alternate) if let Some(cond_expr) = current_expr.as_js_conditional_expression() { - to_check.push(cond_expr.test().ok()?); - to_check.push(cond_expr.consequent().ok()?); - to_check.push(cond_expr.alternate().ok()?); + if let Some(test) = cond_expr.test().ok() { + to_check.push(test); + } + if let Some(consequent) = cond_expr.consequent().ok() { + to_check.push(consequent); + } + if let Some(alternate) = cond_expr.alternate().ok() { + to_check.push(alternate); + } continue; } // Unary operations: -a, !flag // Add argument to worklist if let Some(unary_expr) = current_expr.as_js_unary_expression() { - to_check.push(unary_expr.argument().ok()?); + if let Some(argument) = unary_expr.argument().ok() { + to_check.push(argument); + } continue; } // Static member access: obj.field // Add object to worklist if let Some(member_expr) = current_expr.as_js_static_member_expression() { - to_check.push(member_expr.object().ok()?); + if let Some(object) = member_expr.object().ok() { + to_check.push(object); + } } // Any other expression - not safe to trace @@ -484,7 +502,9 @@ fn is_recursive_call_with_param_usage( let arguments = call.arguments().ok()?; for arg in arguments.args() { - let arg_node = arg.ok()?; + let Some(arg_node) = arg.ok() else { + continue; + }; // Skip spread arguments (conservative) if arg_node.as_js_spread().is_some() { From 14b9851446f51650559c1a7fb12990a371a8d282 Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:42:28 -0500 Subject: [PATCH 23/25] fix linting --- .../no_parameters_only_used_in_recursion.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs index de3b430955a0..dbeba6faaef6 100644 --- a/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs +++ b/crates/biome_js_analyze/src/lint/nursery/no_parameters_only_used_in_recursion.rs @@ -424,10 +424,10 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> Option // Binary operations: a + 1, a - b // Add both sides to worklist if let Some(bin_expr) = current_expr.as_js_binary_expression() { - if let Some(left) = bin_expr.left().ok() { + if let Ok(left) = bin_expr.left() { to_check.push(left); } - if let Some(right) = bin_expr.right().ok() { + if let Ok(right) = bin_expr.right() { to_check.push(right); } continue; @@ -436,10 +436,10 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> Option // Logical operations: a && b, a || b, a ?? b // Add both sides to worklist if let Some(logical_expr) = current_expr.as_js_logical_expression() { - if let Some(left) = logical_expr.left().ok() { + if let Ok(left) = logical_expr.left() { to_check.push(left); } - if let Some(right) = logical_expr.right().ok() { + if let Ok(right) = logical_expr.right() { to_check.push(right); } continue; @@ -448,13 +448,13 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> Option // Conditional expression: cond ? a : b // Add all three parts to worklist (test, consequent, alternate) if let Some(cond_expr) = current_expr.as_js_conditional_expression() { - if let Some(test) = cond_expr.test().ok() { + if let Ok(test) = cond_expr.test() { to_check.push(test); } - if let Some(consequent) = cond_expr.consequent().ok() { + if let Ok(consequent) = cond_expr.consequent() { to_check.push(consequent); } - if let Some(alternate) = cond_expr.alternate().ok() { + if let Ok(alternate) = cond_expr.alternate() { to_check.push(alternate); } continue; @@ -463,7 +463,7 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> Option // Unary operations: -a, !flag // Add argument to worklist if let Some(unary_expr) = current_expr.as_js_unary_expression() { - if let Some(argument) = unary_expr.argument().ok() { + if let Ok(argument) = unary_expr.argument() { to_check.push(argument); } continue; @@ -471,10 +471,10 @@ fn traces_to_parameter(expr: &AnyJsExpression, param_name: &str) -> Option // Static member access: obj.field // Add object to worklist - if let Some(member_expr) = current_expr.as_js_static_member_expression() { - if let Some(object) = member_expr.object().ok() { - to_check.push(object); - } + if let Some(member_expr) = current_expr.as_js_static_member_expression() + && let Ok(object) = member_expr.object() + { + to_check.push(object); } // Any other expression - not safe to trace From 02a75fd88c2ee1469c8a26937c6c48d1631fbe64 Mon Sep 17 00:00:00 2001 From: Matan Shavit <71092861+matanshavit@users.noreply.github.com> Date: Tue, 11 Nov 2025 11:33:48 -0500 Subject: [PATCH 24/25] add changeset for rule refactor --- .changeset/flat-results-happen.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/flat-results-happen.md diff --git a/.changeset/flat-results-happen.md b/.changeset/flat-results-happen.md new file mode 100644 index 000000000000..b8e26f571702 --- /dev/null +++ b/.changeset/flat-results-happen.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +refactor NoParametersOnlyUsedInRecursion and refine to accurately lint in valid subexpressions in malformed expressions From ba76f0660b5ff4fe5990b7b16a7f044737ad704d Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Tue, 11 Nov 2025 17:44:36 +0000 Subject: [PATCH 25/25] Delete .changeset/flat-results-happen.md --- .changeset/flat-results-happen.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/flat-results-happen.md diff --git a/.changeset/flat-results-happen.md b/.changeset/flat-results-happen.md deleted file mode 100644 index b8e26f571702..000000000000 --- a/.changeset/flat-results-happen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@biomejs/biome": patch ---- - -refactor NoParametersOnlyUsedInRecursion and refine to accurately lint in valid subexpressions in malformed expressions