fix(linter/unicorn/consistent-function-scoping): count self-referencing variables as captures (#27726)

Treat a function's reference to its own variable binding as a parent-scope capture, matching eslint-plugin-unicorn. For example, a nested `const builder = (...args) => builder.apply(null, args)` was incorrectly reported as movable even though it captures `builder` from the enclosing scope.

Limit the recursion exemption to function-name symbols. Add regression cases for arrow functions, anonymous and named function expressions, parameter defaults, and `var` bindings, while retaining warnings for internal function-name recursion and shadowed parameters.

Addresses report 5 (`unicorn/consistent-function-scoping`) in #27710.
This commit is contained in:
camc314 committed 2026-10-15 12:53:36 +00:00
1 parent 033faa933e
commit eb461f8344
2 files changed
+41 -1

No files matched your search

@@ -332,7 +332,8 @@ impl ConsistentFunctionScoping {
}
let scope_id = ctx.scoping().symbol_scope_id(symbol_id);
if ctx.scoping().scope_is_descendant_of(function_scope_id, scope_id)
&& symbol_id != function_declaration_symbol_id
&& (symbol_id != function_declaration_symbol_id
|| !ctx.scoping().symbol_flags(symbol_id).is_function())
{
// Both class declarations and expressions have an internal self binding.
// Moving past the class body would lose that binding.
@@ -1165,6 +1166,22 @@ fn test() {
"class Box { #value; method() { const inner = () => class { field = box.#value; }; return inner; } }",
None,
),
(
"const make = (self) => {
const builder = (...args) => builder.apply(null, args);
builder.self = self;
return builder;
};
module.exports = make;",
None,
),
("function outer() { const inner = function () { return inner(); }; return inner; }", None),
(
"function outer() { const inner = function recur() { return inner(); }; return inner; }",
None,
),
("function outer() { const inner = (value = inner) => value; return inner; }", None),
("function outer() { var inner = () => inner(); return inner; }", None),
];
let fail = vec![
@@ -1575,6 +1592,11 @@ fn test() {
"class External { method() { function outer() { function inner() { return External; } return inner; } return outer; } }",
None,
),
(
"function outer() { const inner = function recur() { return recur(); }; return inner; }",
None,
),
("function outer() { const inner = (inner) => inner(); return inner; }", None),
];
Tester::new(ConsistentFunctionScoping::NAME, ConsistentFunctionScoping::PLUGIN, pass, fail)
@@ -741,3 +741,21 @@ source: crates/oxc_linter/src/tester.rs
· ╰── Outer scope where this function is defined
╰────
help: Move `inner` to the outer scope to avoid recreating it on every call.
⚠ unicorn(consistent-function-scoping): Function `inner` does not capture any variables from its parent scope
╭─[consistent_function_scoping.tsx:1:43]
1 │ function outer() { const inner = function recur() { return recur(); }; return inner; }
· ──┬── ──┬──
· │ ╰── This function does not use any variables from the parent function
· ╰── Outer scope where this function is defined
╰────
help: Move `inner` to the outer scope to avoid recreating it on every call.
⚠ unicorn(consistent-function-scoping): Function `inner` does not capture any variables from its parent scope
╭─[consistent_function_scoping.tsx:1:26]
1 │ function outer() { const inner = (inner) => inner(); return inner; }
· ──┬── ──┬──
· │ ╰── This function does not use any variables from the parent function
· ╰── Outer scope where this function is defined
╰────
help: Move `inner` to the outer scope to avoid recreating it on every call.