diff --git a/.changeset/cute-moons-obey.md b/.changeset/cute-moons-obey.md new file mode 100644 index 000000000000..3df1bd13b88f --- /dev/null +++ b/.changeset/cute-moons-obey.md @@ -0,0 +1,21 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#8004](https://github.com/biomejs/biome/issues/8004): [`noParametersOnlyUsedInRecursion`](https://biomejs.dev/linter/rules/no-parameters-only-used-in-recursion/) now correctly detects recursion by comparing function bindings instead of just names. + +Previously, the rule incorrectly flagged parameters when a method had the same name as an outer function but called the outer function (not itself): + +```js +function notRecursive(arg) { + return arg; +} + +const obj = { + notRecursive(arg) { + return notRecursive(arg); // This calls the outer function, not the method itself + }, +}; +``` + +Biome now properly distinguishes between these cases and will not report false positives. 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..244f441b3f3a 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 @@ -126,6 +126,9 @@ impl Rule for NoParametersOnlyUsedInRecursion { // Get function name for recursion detection let function_name = get_function_name(&parent_function); + // Get function binding for semantic comparison + let parent_function_binding = get_function_binding(&parent_function, model); + // Get all references to this parameter let all_refs: Vec<_> = binding.all_references(model).collect(); @@ -144,6 +147,8 @@ impl Rule for NoParametersOnlyUsedInRecursion { function_name.as_ref(), &parent_function, name_text, + model, + parent_function_binding.as_ref(), ) { refs_in_recursion += 1; } else { @@ -250,6 +255,55 @@ fn get_function_name(parent_function: &AnyJsParameterParentFunction) -> Option Option { + match parent_function { + AnyJsParameterParentFunction::JsFunctionDeclaration(decl) => decl + .id() + .ok() + .and_then(|any_binding| any_binding.as_js_identifier_binding().cloned()) + .map(|id| model.as_binding(&id)), + AnyJsParameterParentFunction::JsFunctionExpression(expr) => expr + .id() + .and_then(|any_binding| any_binding.as_js_identifier_binding().cloned()) + .map(|id| model.as_binding(&id)), + AnyJsParameterParentFunction::JsArrowFunctionExpression(arrow) => { + // For arrow functions, find the binding from the surrounding context + let arrow_syntax = arrow.syntax(); + for ancestor in arrow_syntax.ancestors().skip(1) { + // Check for variable declarator: const foo = () => ... + if let Some(declarator) = JsVariableDeclarator::cast_ref(&ancestor) + && let Ok(id) = declarator.id() + && let Some(any_binding) = id.as_any_js_binding() + && let Some(js_id_binding) = any_binding.as_js_identifier_binding() + { + return Some(model.as_binding(js_id_binding)); + } + + // Check for assignment expression: foo = () => ... + if let Some(assignment) = JsAssignmentExpression::cast_ref(&ancestor) + && let Ok(left) = assignment.left() + && let Some(id_assignment) = left.as_any_js_assignment() + && let Some(js_id_assignment) = id_assignment.as_js_identifier_assignment() + { + // Resolve assignment target to its binding + return model.binding(js_id_assignment); + } + + if is_function_like(&ancestor) { + break; + } + } + None + } + // Methods are property names, not bindings - use name-based comparison + _ => None, + } +} + /// Extracts the name of an arrow function from its surrounding context. /// Handles cases like: /// - `const foo = () => ...` (variable declarator) @@ -262,12 +316,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 @@ -335,7 +384,12 @@ 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: Option<&TokenText>, + model: &biome_js_semantic::SemanticModel, + parent_function_binding: Option<&biome_js_semantic::Binding>, +) -> bool { let Ok(callee) = call.callee() else { return false; }; @@ -348,7 +402,32 @@ fn is_recursive_call(call: &JsCallExpression, function_name: Option<&TokenText>) // 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()); + let name_matches = ref_id.name().ok().is_some_and(|n| n.text() == name.text()); + if !name_matches { + return false; + } + + let called_binding = model.binding(&ref_id); + + match (parent_function_binding, called_binding) { + // Both have bindings - compare them directly + (Some(parent_binding), Some(called_binding)) => { + return called_binding == *parent_binding; + } + // Parent has no binding (e.g. in the case of a method), + // but call resolves to a binding + (None, Some(_)) => { + return false; + } + // Parent has binding but call doesn't resolve + (Some(_), None) => { + return false; + } + // Neither has a binding. Fall back to name comparison + (None, None) => { + return name_matches; + } + } } // Member expression: this.foo() or this?.foo() @@ -405,6 +484,8 @@ fn is_reference_in_recursive_call( function_name: Option<&TokenText>, parent_function: &AnyJsParameterParentFunction, param_name: &str, + model: &biome_js_semantic::SemanticModel, + parent_function_binding: Option<&biome_js_semantic::Binding>, ) -> bool { let ref_node = reference.syntax(); @@ -414,7 +495,13 @@ 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, + function_name, + param_name, + model, + parent_function_binding, + ) { return true; } } @@ -528,9 +615,11 @@ fn is_recursive_call_with_param_usage( call: &JsCallExpression, function_name: Option<&TokenText>, param_name: &str, + model: &biome_js_semantic::SemanticModel, + parent_function_binding: Option<&biome_js_semantic::Binding>, ) -> bool { // First check if this is a recursive call at all - if !is_recursive_call(call, function_name) { + if !is_recursive_call(call, function_name, model, parent_function_binding) { return false; } diff --git a/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/invalid.js b/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/invalid.js index 44e2b8dd9227..652ea28a745b 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/invalid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/invalid.js @@ -56,6 +56,13 @@ foo = (n, acc) => { return foo(n - 1, acc); }; +// Separate declaration and assignment with arrow function +let bar; +bar = (x, unused) => { + if (x === 0) return 0; + return bar(x - 1, unused); +}; + // Logical AND operator function fnAnd(n, acc) { if (n === 0) return 0; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/invalid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/invalid.js.snap index cf3256fe09e6..680969ba6bda 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/invalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/invalid.js.snap @@ -62,6 +62,13 @@ foo = (n, acc) => { return foo(n - 1, acc); }; +// Separate declaration and assignment with arrow function +let bar; +bar = (x, unused) => { + if (x === 0) return 0; + return bar(x - 1, unused); +}; + // Logical AND operator function fnAnd(n, acc) { if (n === 0) return 0; @@ -447,15 +454,46 @@ invalid.js:54:11 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ ``` ``` -invalid.js:60:19 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:61:11 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This parameter is only used in recursive calls. - 59 │ // Logical AND operator - > 60 │ function fnAnd(n, acc) { + 59 │ // Separate declaration and assignment with arrow function + 60 │ let bar; + > 61 │ bar = (x, unused) => { + │ ^^^^^^ + 62 │ if (x === 0) return 0; + 63 │ return bar(x - 1, unused); + + i Parameters that are only used in recursive calls are effectively unused and can be removed. + + i If the parameter is needed for the recursion to work, consider if the function can be refactored to avoid it. + + i Unsafe fix: If this is intentional, prepend unused with an underscore. + + 59 59 │ // Separate declaration and assignment with arrow function + 60 60 │ let bar; + 61 │ - bar·=·(x,·unused)·=>·{ + 61 │ + bar·=·(x,·_unused)·=>·{ + 62 62 │ if (x === 0) return 0; + 63 │ - ····return·bar(x·-·1,·unused); + 63 │ + ····return·bar(x·-·1,·_unused); + 64 64 │ }; + 65 65 │ + + +``` + +``` +invalid.js:67:19 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This parameter is only used in recursive calls. + + 66 │ // Logical AND operator + > 67 │ function fnAnd(n, acc) { │ ^^^ - 61 │ if (n === 0) return 0; - 62 │ return fnAnd(n - 1, acc && true); + 68 │ if (n === 0) return 0; + 69 │ return fnAnd(n - 1, acc && true); i Parameters that are only used in recursive calls are effectively unused and can be removed. @@ -463,29 +501,29 @@ invalid.js:60:19 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ i Unsafe fix: If this is intentional, prepend acc with an underscore. - 58 58 │ - 59 59 │ // Logical AND operator - 60 │ - function·fnAnd(n,·acc)·{ - 60 │ + function·fnAnd(n,·_acc)·{ - 61 61 │ if (n === 0) return 0; - 62 │ - ····return·fnAnd(n·-·1,·acc·&&·true); - 62 │ + ····return·fnAnd(n·-·1,·_acc·&&·true); - 63 63 │ } - 64 64 │ + 65 65 │ + 66 66 │ // Logical AND operator + 67 │ - function·fnAnd(n,·acc)·{ + 67 │ + function·fnAnd(n,·_acc)·{ + 68 68 │ if (n === 0) return 0; + 69 │ - ····return·fnAnd(n·-·1,·acc·&&·true); + 69 │ + ····return·fnAnd(n·-·1,·_acc·&&·true); + 70 70 │ } + 71 71 │ ``` ``` -invalid.js:66:18 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:73:18 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This parameter is only used in recursive calls. - 65 │ // Logical OR operator - > 66 │ function fnOr(n, acc) { + 72 │ // Logical OR operator + > 73 │ function fnOr(n, acc) { │ ^^^ - 67 │ if (n === 0) return 0; - 68 │ return fnOr(n - 1, acc || 0); + 74 │ if (n === 0) return 0; + 75 │ return fnOr(n - 1, acc || 0); i Parameters that are only used in recursive calls are effectively unused and can be removed. @@ -493,29 +531,29 @@ invalid.js:66:18 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ i Unsafe fix: If this is intentional, prepend acc with an underscore. - 64 64 │ - 65 65 │ // Logical OR operator - 66 │ - function·fnOr(n,·acc)·{ - 66 │ + function·fnOr(n,·_acc)·{ - 67 67 │ if (n === 0) return 0; - 68 │ - ····return·fnOr(n·-·1,·acc·||·0); - 68 │ + ····return·fnOr(n·-·1,·_acc·||·0); - 69 69 │ } - 70 70 │ + 71 71 │ + 72 72 │ // Logical OR operator + 73 │ - function·fnOr(n,·acc)·{ + 73 │ + function·fnOr(n,·_acc)·{ + 74 74 │ if (n === 0) return 0; + 75 │ - ····return·fnOr(n·-·1,·acc·||·0); + 75 │ + ····return·fnOr(n·-·1,·_acc·||·0); + 76 76 │ } + 77 77 │ ``` ``` -invalid.js:72:23 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:79:23 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This parameter is only used in recursive calls. - 71 │ // Nullish coalescing operator - > 72 │ function fnNullish(n, acc) { + 78 │ // Nullish coalescing operator + > 79 │ function fnNullish(n, acc) { │ ^^^ - 73 │ if (n === 0) return 0; - 74 │ return fnNullish(n - 1, acc ?? 0); + 80 │ if (n === 0) return 0; + 81 │ return fnNullish(n - 1, acc ?? 0); i Parameters that are only used in recursive calls are effectively unused and can be removed. @@ -523,29 +561,29 @@ invalid.js:72:23 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ i Unsafe fix: If this is intentional, prepend acc with an underscore. - 70 70 │ - 71 71 │ // Nullish coalescing operator - 72 │ - function·fnNullish(n,·acc)·{ - 72 │ + function·fnNullish(n,·_acc)·{ - 73 73 │ if (n === 0) return 0; - 74 │ - ····return·fnNullish(n·-·1,·acc·??·0); - 74 │ + ····return·fnNullish(n·-·1,·_acc·??·0); - 75 75 │ } - 76 76 │ + 77 77 │ + 78 78 │ // Nullish coalescing operator + 79 │ - function·fnNullish(n,·acc)·{ + 79 │ + function·fnNullish(n,·_acc)·{ + 80 80 │ if (n === 0) return 0; + 81 │ - ····return·fnNullish(n·-·1,·acc·??·0); + 81 │ + ····return·fnNullish(n·-·1,·_acc·??·0); + 82 82 │ } + 83 83 │ ``` ``` -invalid.js:78:22 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:85:22 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This parameter is only used in recursive calls. - 77 │ // Nested logical operators - > 78 │ function fnNested(n, acc) { + 84 │ // Nested logical operators + > 85 │ function fnNested(n, acc) { │ ^^^ - 79 │ if (n === 0) return 0; - 80 │ return fnNested(n - 1, (acc || 0) && true); + 86 │ if (n === 0) return 0; + 87 │ return fnNested(n - 1, (acc || 0) && true); i Parameters that are only used in recursive calls are effectively unused and can be removed. @@ -553,29 +591,29 @@ invalid.js:78:22 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ i Unsafe fix: If this is intentional, prepend acc with an underscore. - 76 76 │ - 77 77 │ // Nested logical operators - 78 │ - function·fnNested(n,·acc)·{ - 78 │ + function·fnNested(n,·_acc)·{ - 79 79 │ if (n === 0) return 0; - 80 │ - ····return·fnNested(n·-·1,·(acc·||·0)·&&·true); - 80 │ + ····return·fnNested(n·-·1,·(_acc·||·0)·&&·true); - 81 81 │ } - 82 82 │ + 83 83 │ + 84 84 │ // Nested logical operators + 85 │ - function·fnNested(n,·acc)·{ + 85 │ + function·fnNested(n,·_acc)·{ + 86 86 │ if (n === 0) return 0; + 87 │ - ····return·fnNested(n·-·1,·(acc·||·0)·&&·true); + 87 │ + ····return·fnNested(n·-·1,·(_acc·||·0)·&&·true); + 88 88 │ } + 89 89 │ ``` ``` -invalid.js:84:30 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:91:30 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This parameter is only used in recursive calls. - 83 │ // Conditional expression with parameter in consequent - > 84 │ function fnCondConsequent(n, acc) { + 90 │ // Conditional expression with parameter in consequent + > 91 │ function fnCondConsequent(n, acc) { │ ^^^ - 85 │ if (n === 0) return 0; - 86 │ return fnCondConsequent(n - 1, n > 5 ? acc : 0); + 92 │ if (n === 0) return 0; + 93 │ return fnCondConsequent(n - 1, n > 5 ? acc : 0); i Parameters that are only used in recursive calls are effectively unused and can be removed. @@ -583,29 +621,29 @@ invalid.js:84:30 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ i Unsafe fix: If this is intentional, prepend acc with an underscore. - 82 82 │ - 83 83 │ // Conditional expression with parameter in consequent - 84 │ - function·fnCondConsequent(n,·acc)·{ - 84 │ + function·fnCondConsequent(n,·_acc)·{ - 85 85 │ if (n === 0) return 0; - 86 │ - ····return·fnCondConsequent(n·-·1,·n·>·5·?·acc·:·0); - 86 │ + ····return·fnCondConsequent(n·-·1,·n·>·5·?·_acc·:·0); - 87 87 │ } - 88 88 │ + 89 89 │ + 90 90 │ // Conditional expression with parameter in consequent + 91 │ - function·fnCondConsequent(n,·acc)·{ + 91 │ + function·fnCondConsequent(n,·_acc)·{ + 92 92 │ if (n === 0) return 0; + 93 │ - ····return·fnCondConsequent(n·-·1,·n·>·5·?·acc·:·0); + 93 │ + ····return·fnCondConsequent(n·-·1,·n·>·5·?·_acc·:·0); + 94 94 │ } + 95 95 │ ``` ``` -invalid.js:90:29 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:97:29 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This parameter is only used in recursive calls. - 89 │ // Conditional expression with parameter in alternate - > 90 │ function fnCondAlternate(n, acc) { + 96 │ // Conditional expression with parameter in alternate + > 97 │ function fnCondAlternate(n, acc) { │ ^^^ - 91 │ if (n === 0) return 0; - 92 │ return fnCondAlternate(n - 1, n > 5 ? 0 : acc); + 98 │ if (n === 0) return 0; + 99 │ return fnCondAlternate(n - 1, n > 5 ? 0 : acc); i Parameters that are only used in recursive calls are effectively unused and can be removed. @@ -613,29 +651,29 @@ invalid.js:90:29 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ i Unsafe fix: If this is intentional, prepend acc with an underscore. - 88 88 │ - 89 89 │ // Conditional expression with parameter in alternate - 90 │ - function·fnCondAlternate(n,·acc)·{ - 90 │ + function·fnCondAlternate(n,·_acc)·{ - 91 91 │ if (n === 0) return 0; - 92 │ - ····return·fnCondAlternate(n·-·1,·n·>·5·?·0·:·acc); - 92 │ + ····return·fnCondAlternate(n·-·1,·n·>·5·?·0·:·_acc); - 93 93 │ } - 94 94 │ + 95 95 │ + 96 96 │ // Conditional expression with parameter in alternate + 97 │ - function·fnCondAlternate(n,·acc)·{ + 97 │ + function·fnCondAlternate(n,·_acc)·{ + 98 98 │ if (n === 0) return 0; + 99 │ - ····return·fnCondAlternate(n·-·1,·n·>·5·?·0·:·acc); + 99 │ + ····return·fnCondAlternate(n·-·1,·n·>·5·?·0·:·_acc); + 100 100 │ } + 101 101 │ ``` ``` -invalid.js:96:24 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:103:24 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This parameter is only used in recursive calls. - 95 │ // Conditional expression with parameter in test - > 96 │ function fnCondTest(n, flag) { - │ ^^^^ - 97 │ if (n === 0) return 0; - 98 │ return fnCondTest(n - 1, flag ? true : false); + 102 │ // Conditional expression with parameter in test + > 103 │ function fnCondTest(n, flag) { + │ ^^^^ + 104 │ if (n === 0) return 0; + 105 │ return fnCondTest(n - 1, flag ? true : false); i Parameters that are only used in recursive calls are effectively unused and can be removed. @@ -643,29 +681,29 @@ invalid.js:96:24 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ i Unsafe fix: If this is intentional, prepend flag with an underscore. - 94 94 │ - 95 95 │ // Conditional expression with parameter in test - 96 │ - function·fnCondTest(n,·flag)·{ - 96 │ + function·fnCondTest(n,·_flag)·{ - 97 97 │ if (n === 0) return 0; - 98 │ - ····return·fnCondTest(n·-·1,·flag·?·true·:·false); - 98 │ + ····return·fnCondTest(n·-·1,·_flag·?·true·:·false); - 99 99 │ } - 100 100 │ + 101 101 │ + 102 102 │ // Conditional expression with parameter in test + 103 │ - function·fnCondTest(n,·flag)·{ + 103 │ + function·fnCondTest(n,·_flag)·{ + 104 104 │ if (n === 0) return 0; + 105 │ - ····return·fnCondTest(n·-·1,·flag·?·true·:·false); + 105 │ + ····return·fnCondTest(n·-·1,·_flag·?·true·:·false); + 106 106 │ } + 107 107 │ ``` ``` -invalid.js:102:26 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:109:26 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This parameter is only used in recursive calls. - 101 │ // Nested conditional expressions - > 102 │ function fnCondNested(n, acc) { + 108 │ // Nested conditional expressions + > 109 │ function fnCondNested(n, acc) { │ ^^^ - 103 │ if (n === 0) return 0; - 104 │ return fnCondNested(n - 1, n > 5 ? (n > 10 ? acc : 0) : 0); + 110 │ if (n === 0) return 0; + 111 │ return fnCondNested(n - 1, n > 5 ? (n > 10 ? acc : 0) : 0); i Parameters that are only used in recursive calls are effectively unused and can be removed. @@ -673,30 +711,30 @@ invalid.js:102:26 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ i Unsafe fix: If this is intentional, prepend acc with an underscore. - 100 100 │ - 101 101 │ // Nested conditional expressions - 102 │ - function·fnCondNested(n,·acc)·{ - 102 │ + function·fnCondNested(n,·_acc)·{ - 103 103 │ if (n === 0) return 0; - 104 │ - ····return·fnCondNested(n·-·1,·n·>·5·?·(n·>·10·?·acc·:·0)·:·0); - 104 │ + ····return·fnCondNested(n·-·1,·n·>·5·?·(n·>·10·?·_acc·:·0)·:·0); - 105 105 │ } - 106 106 │ + 107 107 │ + 108 108 │ // Nested conditional expressions + 109 │ - function·fnCondNested(n,·acc)·{ + 109 │ + function·fnCondNested(n,·_acc)·{ + 110 110 │ if (n === 0) return 0; + 111 │ - ····return·fnCondNested(n·-·1,·n·>·5·?·(n·>·10·?·acc·:·0)·:·0); + 111 │ + ····return·fnCondNested(n·-·1,·n·>·5·?·(n·>·10·?·_acc·:·0)·:·0); + 112 112 │ } + 113 113 │ ``` ``` -invalid.js:109:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:116:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This parameter is only used in recursive calls. - 107 │ // Optional chaining in class method - 108 │ class CounterOptional { - > 109 │ count(n, acc) { + 114 │ // Optional chaining in class method + 115 │ class CounterOptional { + > 116 │ count(n, acc) { │ ^^^ - 110 │ if (n === 0) return 0; - 111 │ return this?.count(n - 1, acc); + 117 │ if (n === 0) return 0; + 118 │ return this?.count(n - 1, acc); i Parameters that are only used in recursive calls are effectively unused and can be removed. @@ -704,30 +742,30 @@ invalid.js:109:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ i Unsafe fix: If this is intentional, prepend acc with an underscore. - 107 107 │ // Optional chaining in class method - 108 108 │ class CounterOptional { - 109 │ - ····count(n,·acc)·{ - 109 │ + ····count(n,·_acc)·{ - 110 110 │ if (n === 0) return 0; - 111 │ - ········return·this?.count(n·-·1,·acc); - 111 │ + ········return·this?.count(n·-·1,·_acc); - 112 112 │ } - 113 113 │ } + 114 114 │ // Optional chaining in class method + 115 115 │ class CounterOptional { + 116 │ - ····count(n,·acc)·{ + 116 │ + ····count(n,·_acc)·{ + 117 117 │ if (n === 0) return 0; + 118 │ - ········return·this?.count(n·-·1,·acc); + 118 │ + ········return·this?.count(n·-·1,·_acc); + 119 119 │ } + 120 120 │ } ``` ``` -invalid.js:117:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:124:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This parameter is only used in recursive calls. - 115 │ // Optional chaining in object method - 116 │ const objOptional = { - > 117 │ count(n, step) { + 122 │ // Optional chaining in object method + 123 │ const objOptional = { + > 124 │ count(n, step) { │ ^^^^ - 118 │ if (n === 0) return 0; - 119 │ return this?.count(n - step, step); + 125 │ if (n === 0) return 0; + 126 │ return this?.count(n - step, step); i Parameters that are only used in recursive calls are effectively unused and can be removed. @@ -735,30 +773,30 @@ invalid.js:117:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ i Unsafe fix: If this is intentional, prepend step with an underscore. - 115 115 │ // Optional chaining in object method - 116 116 │ const objOptional = { - 117 │ - ····count(n,·step)·{ - 117 │ + ····count(n,·_step)·{ - 118 118 │ if (n === 0) return 0; - 119 │ - ········return·this?.count(n·-·step,·step); - 119 │ + ········return·this?.count(n·-·_step,·_step); - 120 120 │ } - 121 121 │ }; + 122 122 │ // Optional chaining in object method + 123 123 │ const objOptional = { + 124 │ - ····count(n,·step)·{ + 124 │ + ····count(n,·_step)·{ + 125 125 │ if (n === 0) return 0; + 126 │ - ········return·this?.count(n·-·step,·step); + 126 │ + ········return·this?.count(n·-·_step,·_step); + 127 127 │ } + 128 128 │ }; ``` ``` -invalid.js:125:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:132:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This parameter is only used in recursive calls. - 123 │ // Computed member with string literal - 124 │ class CounterComputed { - > 125 │ count(n, acc) { + 130 │ // Computed member with string literal + 131 │ class CounterComputed { + > 132 │ count(n, acc) { │ ^^^ - 126 │ if (n === 0) return 0; - 127 │ return this["count"](n - 1, acc); + 133 │ if (n === 0) return 0; + 134 │ return this["count"](n - 1, acc); i Parameters that are only used in recursive calls are effectively unused and can be removed. @@ -766,30 +804,30 @@ invalid.js:125:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ i Unsafe fix: If this is intentional, prepend acc with an underscore. - 123 123 │ // Computed member with string literal - 124 124 │ class CounterComputed { - 125 │ - ····count(n,·acc)·{ - 125 │ + ····count(n,·_acc)·{ - 126 126 │ if (n === 0) return 0; - 127 │ - ········return·this["count"](n·-·1,·acc); - 127 │ + ········return·this["count"](n·-·1,·_acc); - 128 128 │ } - 129 129 │ } + 130 130 │ // Computed member with string literal + 131 131 │ class CounterComputed { + 132 │ - ····count(n,·acc)·{ + 132 │ + ····count(n,·_acc)·{ + 133 133 │ if (n === 0) return 0; + 134 │ - ········return·this["count"](n·-·1,·acc); + 134 │ + ········return·this["count"](n·-·1,·_acc); + 135 135 │ } + 136 136 │ } ``` ``` -invalid.js:133:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ +invalid.js:140:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This parameter is only used in recursive calls. - 131 │ // Optional chaining with computed member - 132 │ class CounterOptionalComputed { - > 133 │ count(n, acc) { + 138 │ // Optional chaining with computed member + 139 │ class CounterOptionalComputed { + > 140 │ count(n, acc) { │ ^^^ - 134 │ if (n === 0) return 0; - 135 │ return this?.["count"](n - 1, acc); + 141 │ if (n === 0) return 0; + 142 │ return this?.["count"](n - 1, acc); i Parameters that are only used in recursive calls are effectively unused and can be removed. @@ -797,15 +835,15 @@ invalid.js:133:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━ i Unsafe fix: If this is intentional, prepend acc with an underscore. - 131 131 │ // Optional chaining with computed member - 132 132 │ class CounterOptionalComputed { - 133 │ - ····count(n,·acc)·{ - 133 │ + ····count(n,·_acc)·{ - 134 134 │ if (n === 0) return 0; - 135 │ - ········return·this?.["count"](n·-·1,·acc); - 135 │ + ········return·this?.["count"](n·-·1,·_acc); - 136 136 │ } - 137 137 │ } + 138 138 │ // Optional chaining with computed member + 139 139 │ class CounterOptionalComputed { + 140 │ - ····count(n,·acc)·{ + 140 │ + ····count(n,·_acc)·{ + 141 141 │ if (n === 0) return 0; + 142 │ - ········return·this?.["count"](n·-·1,·acc); + 142 │ + ········return·this?.["count"](n·-·1,·_acc); + 143 143 │ } + 144 144 │ } ``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/valid.js b/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/valid.js index b2cb6608b40e..0f5561b0727e 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/valid.js +++ b/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/valid.js @@ -84,3 +84,14 @@ class CounterNonLiteral { return this[methodName](n - 1, acc); // Won't be recognized as recursive } } + +// Method calling outer function with same name (not recursive) +function notRecursive(arg) { + return arg; +} + +const obj = { + notRecursive(arg) { + return notRecursive(arg); // Calls outer function, not self + } +}; diff --git a/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/valid.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/valid.js.snap index 443b4875842e..ed00dc044cd2 100644 --- a/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/nursery/noParametersOnlyUsedInRecursion/valid.js.snap @@ -91,4 +91,15 @@ class CounterNonLiteral { } } +// Method calling outer function with same name (not recursive) +function notRecursive(arg) { + return arg; +} + +const obj = { + notRecursive(arg) { + return notRecursive(arg); // Calls outer function, not self + } +}; + ```