diff --git a/crates/oxc_linter/src/rules/unicorn/consistent_function_scoping.rs b/crates/oxc_linter/src/rules/unicorn/consistent_function_scoping.rs index e2bb20760ad65..5e10ecd4ff24d 100644 --- a/crates/oxc_linter/src/rules/unicorn/consistent_function_scoping.rs +++ b/crates/oxc_linter/src/rules/unicorn/consistent_function_scoping.rs @@ -159,52 +159,59 @@ impl Rule for ConsistentFunctionScoping { } fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - let (function_declaration_symbol_id, function_body, reporter_span) = match node.kind() { - AstKind::Function(function) => { - if let Some(AstKind::AssignmentExpression(_)) = ctx.nodes().parent_kind(node.id()) { - return; - } - - if function.is_typescript_syntax() { - return; - } - - // NOTE: function.body will always be some here because of - // checks in `is_typescript_syntax` - let Some(function_body) = &function.body else { return }; - - if let Some(binding_ident) = get_function_like_declaration(node, ctx) { - ( - binding_ident.symbol_id.get().unwrap(), - function_body, - function - .id - .as_ref() - .map_or(Span::sized(function.span.start, 8), |func_binding_ident| { - func_binding_ident.span - }), - ) - } else if let Some(function_id) = &function.id { - let Some(symbol_id) = function_id.symbol_id.get() else { + let (function_declaration_symbol_id, function_body, reporter_span) = + match node.kind() { + AstKind::Function(function) => { + if function.is_typescript_syntax() { return; - }; - (symbol_id, function_body, function_id.span()) - } else { - return; + } + + if let Some(func_scope_id) = function.scope_id.get() { + if let Some(parent_scope_id) = ctx.scopes().get_parent_id(func_scope_id) { + // Example: const foo = function bar() {}; + // The bar function scope id is 1. In order to ignore this rule, + // its parent's scope id (in this case `foo`'s scope id is 0 and is equal to root scope id) + // should be considered. + if parent_scope_id == ctx.scopes().root_scope_id() { + return; + } + } + } + + // NOTE: function.body will always be some here because of + // checks in `is_typescript_syntax` + let Some(function_body) = &function.body else { return }; + + if let Some(binding_ident) = get_function_like_declaration(node, ctx) { + ( + binding_ident.symbol_id.get().unwrap(), + function_body, + function.id.as_ref().map_or( + Span::sized(function.span.start, 8), + |func_binding_ident| func_binding_ident.span, + ), + ) + } else if let Some(function_id) = &function.id { + let Some(symbol_id) = function_id.symbol_id.get() else { + return; + }; + (symbol_id, function_body, function_id.span()) + } else { + return; + } } - } - AstKind::ArrowFunctionExpression(arrow_function) if self.check_arrow_functions => { - let Some(binding_ident) = get_function_like_declaration(node, ctx) else { - return; - }; - let Some(symbol_id) = binding_ident.symbol_id.get() else { - return; - }; + AstKind::ArrowFunctionExpression(arrow_function) if self.check_arrow_functions => { + let Some(binding_ident) = get_function_like_declaration(node, ctx) else { + return; + }; + let Some(symbol_id) = binding_ident.symbol_id.get() else { + return; + }; - (symbol_id, &arrow_function.body, binding_ident.span()) - } - _ => return, - }; + (symbol_id, &arrow_function.body, binding_ident.span()) + } + _ => return, + }; // if the function is declared at the root scope, we don't need to check anything if ctx.symbols().get_scope_id(function_declaration_symbol_id) @@ -588,6 +595,17 @@ fn test() { ("module.exports = function foo() {};", None), ("module.exports.foo = function foo() {};", None), ("foo.bar.func = function foo() {};", None), + ( + "let inner; + + function foo1() { + inner = function() {} + } + function foo2() { + inner = function() {} + }", + None, + ), ("if(f) function f(){}", None), ]; @@ -626,6 +644,14 @@ fn test() { ), ("function foo() { function bar() { return ; } }", None), ("function doFoo(Foo) { const doBar = () => arguments; return doBar(); };", None), + ( + "let inner; + + function outer() { + inner = function inner() {} + }", + None, + ), // end of cases that eslint-plugin-unicorn passes, but we fail. ( "function doFoo(foo) { diff --git a/crates/oxc_linter/src/snapshots/consistent_function_scoping.snap b/crates/oxc_linter/src/snapshots/consistent_function_scoping.snap index 7dd3f0396f95a..c449ccf26bb06 100644 --- a/crates/oxc_linter/src/snapshots/consistent_function_scoping.snap +++ b/crates/oxc_linter/src/snapshots/consistent_function_scoping.snap @@ -42,6 +42,15 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Move this function to the outer scope. + ⚠ eslint-plugin-unicorn(consistent-function-scoping): Function does not capture any variables from the outer scope. + ╭─[consistent_function_scoping.tsx:4:34] + 3 │ function outer() { + 4 │ inner = function inner() {} + · ───── + 5 │ } + ╰──── + help: Move this function to the outer scope. + ⚠ eslint-plugin-unicorn(consistent-function-scoping): Function does not capture any variables from the outer scope. ╭─[consistent_function_scoping.tsx:2:26] 1 │ function doFoo(foo) {