Skip to main content
interlace
Plugin: maintainabilityRules

consistent-function-scoping

Move functions to the highest possible scope

Move functions to the highest possible scope. This rule is part of eslint-plugin-maintainability.

Quick Summary

AspectDetails
SeverityWarning (code quality)
Auto-FixโŒ No (requires manual move)
CategoryQuality
ESLint MCPโœ… Optimized for ESLint MCP integration
Best ForPerformance optimization, code organization

Rule Details

A helper declared inside a function - a function declaration, or a function or arrow expression bound to a variable - that references no binding of its enclosing function is recreated on every call and can move to module scope.

What counts is the binding, resolved by scope, not the name:

  • a variable the helper declares itself under an outer name shadows the outer one and is not a use;
  • a module-level binding is reachable after the move, so using only module bindings still lets the helper move;
  • a use counts wherever it is: in a nested callback, a default parameter value, a type annotation naming a type the enclosing function declares, or a call of a sibling helper.

It does not report a function at module level, a function passed as an argument (an inline callback), an object or class member, or an arrow that uses this (an arrow's this is the enclosing one). A function helper's this is its own, so using it does not tie the helper to the enclosing scope.

Configuration

OptionTypeDefaultDescription
checkArrowFunctionsbooleantrueCheck arrow helpers; false checks only function declarations and expressions

Why This Matters

IssueImpactSolution
โšก PerformanceRecreated on each callMove to outer scope
๐Ÿ“– ReadabilityDeep nestingFlatten structure
๐Ÿงช TestabilityHard to test nested functionsExtract for testing

Examples

โŒ Incorrect

function outer() {
  // This function doesn't use any variables from outer
  function helper(x: number) {
    return x * 2;
  }
  
  return [1, 2, 3].map(helper);
}

โœ… Correct

// Moved to module scope
function helper(x: number) {
  return x * 2;
}

function outer() {
  return [1, 2, 3].map(helper);
}

// Or when closure is needed (correct nesting)
function createMultiplier(factor: number) {
  return function multiply(x: number) {  // Uses factor from parent
    return x * factor;
  };
}

Configuration Examples

Basic Usage

{
  rules: {
    'maintainability/consistent-function-scoping': 'warn'
  }
}

Further Reading

Known False Negatives

The following patterns are not detected due to static analysis limitations:

Dynamic Variable References

Why: Static analysis cannot trace values stored in variables or passed through function parameters.

// โŒ NOT DETECTED - Value from variable
const value = externalSource();
processValue(value); // Variable origin not tracked

Mitigation: Implement runtime validation and review code manually. Consider using TypeScript branded types for validated inputs.

Imported Values

Why: When values come from imports, the rule cannot analyze their origin or construction.

// โŒ NOT DETECTED - Value from import
import { getValue } from './helpers';
processValue(getValue()); // Cross-file not tracked

Mitigation: Ensure imported values follow the same constraints. Use TypeScript for type safety.

Use this rule

npm install -D oxlint-plugin-maintainability
.oxlintrc.json
{  "jsPlugins": ["oxlint-plugin-maintainability"],  "rules": {    "maintainability/consistent-function-scoping": "error"  }}
vite.config.ts
import { defineConfig } from 'vite-plus';export default defineConfig({  lint: {    jsPlugins: ['oxlint-plugin-maintainability'],    rules: {      'maintainability/consistent-function-scoping': 'error',    },  },});

oxlint-plugin-maintainability re-exports eslint-plugin-maintainability/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.

Did this rule catch something? Star the repo to get new CWE coverage as we ship it โ€” or follow the AI-code-security benchmarks behind these rules.