Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(lint): support assignment expressions in noParametersOnlyUsedInR…
…ecursion

Add support for detecting recursive arrow functions defined via
assignment expressions (e.g., foo = (n) => ...). The rule now
extracts the function name from the left-hand side of assignment
expressions to properly track recursive calls.
  • Loading branch information
matanshavit committed Oct 30, 2025
commit 8c5950da12500e99f6f0b0d9ea85e2318bf91174
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use biome_console::markup;
use biome_diagnostics::Severity;
use biome_js_semantic::ReferencesExtensions;
use biome_js_syntax::{
AnyJsExpression, JsCallExpression, JsIdentifierBinding, JsVariableDeclarator,
binding_ext::AnyJsParameterParentFunction,
AnyJsExpression, JsAssignmentExpression, JsCallExpression, JsIdentifierBinding,
JsVariableDeclarator, binding_ext::AnyJsParameterParentFunction,
};
use biome_rowan::{AstNode, BatchMutationExt, TokenText};

Expand Down Expand Up @@ -264,6 +264,18 @@ fn get_arrow_function_name(
.map(|t| t.token_text_trimmed());
}

// Check for assignment expression: foo = () => ...
if let Some(assignment) = JsAssignmentExpression::cast_ref(&ancestor) {
return assignment
.left()
.ok()?
.as_any_js_assignment()?
.as_js_identifier_assignment()?
.name_token()
.ok()
.map(|t| t.token_text_trimmed());
}

// Stop searching if we hit a function boundary
// (prevents extracting wrong name from outer scope)
if is_function_like(&ancestor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,9 @@ const obj = {
return this.count(n - step, step);
}
};

// Assignment expression with recursive arrow function
foo = (n, acc) => {
if (n === 0) return 0;
return foo(n - 1, acc);
};
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ const obj = {
}
};

// Assignment expression with recursive arrow function
foo = (n, acc) => {
if (n === 0) return 0;
return foo(n - 1, acc);
};

```

# Diagnostics
Expand Down Expand Up @@ -329,3 +335,33 @@ invalid.js:47:14 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━


```

```
invalid.js:54:11 lint/nursery/noParametersOnlyUsedInRecursion FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━

! This parameter is only used in recursive calls.

53 │ // Assignment expression with recursive arrow function
> 54 │ foo = (n, acc) => {
│ ^^^
55 │ if (n === 0) return 0;
56 │ return foo(n - 1, acc);

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 acc with an underscore.

52 52 │
53 53 │ // Assignment expression with recursive arrow function
54 │ - foo·=·(n,·acc)·=>·{
54 │ + foo·=·(n,·_acc)·=>·{
55 55 │ if (n === 0) return 0;
56 │ - ····return·foo(n·-·1,·acc);
56 │ + ····return·foo(n·-·1,·_acc);
57 57 │ };
58 58 │


```
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ function clamp(n, max) {
if (n > max) return max; // max used in comparison
return clamp(n + 1, max);
}

// Assignment expression where parameter is used outside recursion
bar = (n, threshold) => {
if (n > threshold) return threshold;
return bar(n + 1, threshold);
};
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,10 @@ function clamp(n, max) {
return clamp(n + 1, max);
}

// Assignment expression where parameter is used outside recursion
bar = (n, threshold) => {
if (n > threshold) return threshold;
return bar(n + 1, threshold);
};

```