or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

eslint-scope.mdeslint-visitor-keys.mdespree.mdindex.md
tile.json

eslint-scope.mddocs/

ESLint Scope

ESLint Scope is an ECMAScript scope analyzer that identifies variable scopes, references, and declarations in JavaScript AST nodes. It analyzes the lexical scope structure of JavaScript code to understand variable binding, usage patterns, and scope relationships for static analysis tools.

Core Imports

import { analyze } from "eslint-scope";

For CommonJS:

const { analyze } = require("eslint-scope");

Named imports (ESM):

import { 
  analyze, 
  ScopeManager, 
  Scope, 
  Reference, 
  Variable, 
  Definition 
} from "eslint-scope";

Basic Usage

import * as espree from "espree";
import { analyze } from "eslint-scope";

// Parse JavaScript code
const ast = espree.parse(`
  let globalVar = 1;
  function outer(param) {
    let localVar = 2;
    function inner() {
      return globalVar + localVar + param;
    }
    return inner;
  }
`, { ecmaVersion: 2022, sourceType: "module" });

// Analyze scope structure
const scopeManager = analyze(ast, { 
  ecmaVersion: 2022, 
  sourceType: "module" 
});

// Access scopes and references
console.log(scopeManager.scopes.length); // 4 (global, outer function, inner function, module)
console.log(scopeManager.globalScope.variables.map(v => v.name)); // ["globalVar", "outer"]

Capabilities

Scope Analysis

Core function that analyzes AST and returns comprehensive scope information.

/**
 * Analyzes AST and returns scope information
 * @param {espree.Tree} tree - AST from espree.parse()
 * @param {AnalysisOptions} options - Analysis configuration options
 * @returns {ScopeManager} Scope manager with all scopes and references
 */
function analyze(tree, options);

Scope Manager

Manages all scopes in the analyzed code and provides methods to query scope information.

class ScopeManager {
  /** Array of all scopes in the program */
  scopes: Scope[];
  
  /** The global scope */
  globalScope: Scope;
  
  /**
   * Get the scope that contains the given AST node
   * @param {espree.Node} node - AST node
   * @param {boolean} inner - If true, get inner-most scope
   * @returns {Scope | null} Containing scope or null
   */
  acquire(node, inner);
  
  /**
   * Get variables declared by the given AST node
   * @param {espree.Node} node - AST node (usually a declaration)
   * @returns {Variable[]} Array of declared variables
   */
  getDeclaredVariables(node);
  
  /** Check if this is a global return (CommonJS-style) */
  isGlobalReturn();
  
  /** Check if this is ES module mode */
  isModule();
  
  /** Check if strict mode is implied */
  isImpliedStrict();
  
  /** Check if strict mode is supported */
  isStrictModeSupported();
}

Scope

Represents an individual lexical scope with its variables, references, and scope chain.

class Scope {
  /** Scope type: "global", "module", "function", "block", "switch", "catch", "with", "for", "class" */
  type: string;
  
  /** Whether this scope is in strict mode */
  isStrict: boolean;
  
  /** Variables declared in this scope */
  variables: Variable[];
  
  /** References to variables from this scope */
  references: Reference[];
  
  /** The scope that defines variables (for block scopes, this points to function scope) */
  variableScope: Scope;
  
  /** Whether this is a function expression scope */
  functionExpressionScope: boolean;
  
  /** Whether this scope contains a direct call to eval() */
  directCallToEvalScope: boolean;
  
  /** Whether 'this' is referenced in this scope */
  thisFound: boolean;
  
  /** Map of variable names to Variable objects */
  set: Map<string, Variable>;
  
  /** Map tracking tainted variables */
  taints: Map<string, boolean>;
  
  /** Whether this scope is dynamic (contains with or eval) */
  dynamic: boolean;
  
  /** AST node that defines this scope */
  block: espree.Node;
  
  /** References that could not be resolved in this scope */
  through: Reference[];
  
  /** Parent scope in the scope chain */
  upper: Scope;
  
  /** Check if this is a static scope (not dynamic) */
  isStatic();
  
  /**
   * Resolve a reference to a variable
   * @param {espree.Identifier} ident - Identifier to resolve
   * @returns {Reference} Reference object
   */
  resolve(ident);
  
  /** Check if the arguments object is materialized */
  isArgumentsMaterialized();
  
  /** Check if 'this' is materialized */
  isThisMaterialized();
  
  /**
   * Check if a name is used in this scope
   * @param {string} name - Variable name
   * @returns {boolean} True if name is used
   */
  isUsedName(name);
}

Reference

Represents a reference to a variable, including information about how it's used.

class Reference {
  /** The identifier AST node */
  identifier: espree.Identifier;
  
  /** The scope this reference comes from */
  from: Scope;
  
  /** Whether this reference is tainted by dynamic scope */
  tainted: boolean;
  
  /** The variable this reference resolves to (null if unresolved) */
  resolved: Variable | null;
  
  /** Reference flags: READ (1), WRITE (2), RW (3) */
  flag: number;
  
  /** Expression being written to this reference (for write references) */
  writeExpr?: espree.Node;
  
  /** Whether this is a partial write (destructuring) */
  partial?: boolean;
  
  /** Whether this is an initialization write */
  init?: boolean;
  
  /** Check if this is a static reference (not dynamic) */
  isStatic();
  
  /** Check if this reference reads the variable */
  isRead();
  
  /** Check if this reference writes to the variable */
  isWrite();
  
  /** Check if this reference only reads the variable */
  isReadOnly();
  
  /** Check if this reference only writes to the variable */
  isWriteOnly();
  
  /** Check if this reference both reads and writes the variable */
  isReadWrite();
}

Variable

Represents a variable declaration with all its references and definition sites.

class Variable {
  /** Variable name */
  name: string;
  
  /** Identifier nodes where this variable is declared */
  identifiers: espree.Identifier[];
  
  /** All references to this variable */
  references: Reference[];
  
  /** All definition sites of this variable */
  defs: Definition[];
  
  /** Whether this variable is tainted by dynamic scope */
  tainted: boolean;
  
  /** Whether this variable needs to be stored on the stack */
  stack: boolean;
  
  /** The scope this variable belongs to */
  scope: Scope;
}

Definition

Represents a definition site of a variable (declaration, parameter, etc.).

class Definition {
  /** Definition type: "Variable", "Parameter", "FunctionName", "ClassName", "CatchClause", "ImportBinding", "ImplicitGlobalVariable" */
  type: string;
  
  /** Variable name being defined */
  name: espree.Identifier;
  
  /** AST node containing the definition */
  node: espree.Node;
  
  /** Parent AST node of the definition */
  parent?: espree.Node;
  
  /** Index of parameter (for Parameter definitions) */
  index?: number;
  
  /** Whether this is a rest parameter */
  rest?: boolean;
}

Analysis Options

interface AnalysisOptions {
  /** Assume code is optimistic (treat unknown references as global) */
  optimistic?: boolean; // Default: false
  
  /** Enable Node.js scope analysis */
  nodejsScope?: boolean; // Default: false
  
  /** Enable implied strict mode */
  impliedStrict?: boolean; // Default: false
  
  /** Source type: "script", "module", or "commonjs" */
  sourceType?: "script" | "module" | "commonjs"; // Default: "script"
  
  /** ECMAScript version (3, 5, 6-17, 2015-2026, or "latest") */
  ecmaVersion?: number | "latest"; // Default: 5
  
  /** Enable JSX parsing */
  jsx?: boolean; // Default: false
  
  /** Additional visitor keys for custom AST nodes */
  childVisitorKeys?: VisitorKeys;
  
  /** Fallback strategy for unknown nodes: "iteration" */
  fallback?: string; // Default: "iteration"
  
  /** Ignore eval() calls in scope analysis */
  ignoreEval?: boolean; // Default: false
}

Usage Examples

Scope hierarchy analysis:

import * as espree from "espree";
import { analyze } from "eslint-scope";

const ast = espree.parse(`
  let global = 1;
  function outer() {
    let local = 2;
    function inner() {
      return global + local;
    }
  }
`, { ecmaVersion: 2022 });

const scopeManager = analyze(ast, { ecmaVersion: 2022 });

// Walk through scope hierarchy
scopeManager.scopes.forEach((scope, index) => {
  console.log(`Scope ${index}: ${scope.type}`);
  console.log(`Variables: ${scope.variables.map(v => v.name).join(", ")}`);
  console.log(`References: ${scope.references.length}`);
});

Variable usage tracking:

const ast = espree.parse(`
  let x = 1;
  x = x + 2;
  console.log(x);
`, { ecmaVersion: 2022 });

const scopeManager = analyze(ast, { ecmaVersion: 2022 });
const globalScope = scopeManager.globalScope;
const xVariable = globalScope.set.get("x");

console.log(`Variable 'x' has ${xVariable.references.length} references:`);
xVariable.references.forEach((ref, index) => {
  console.log(`  ${index + 1}. ${ref.isRead() ? "Read" : ""}${ref.isWrite() ? "Write" : ""} at line ${ref.identifier.loc.start.line}`);
});

Unresolved references:

const ast = espree.parse(`
  function test() {
    return undeclaredVar; // This will be unresolved
  }
`, { ecmaVersion: 2022 });

const scopeManager = analyze(ast, { 
  ecmaVersion: 2022,
  optimistic: false // Don't assume unknown references are global
});

scopeManager.scopes.forEach(scope => {
  scope.through.forEach(ref => {
    console.log(`Unresolved reference: ${ref.identifier.name} at line ${ref.identifier.loc.start.line}`);
  });
});

Module vs Script analysis:

// Module analysis
const moduleAst = espree.parse(`
  import { foo } from "bar";
  export let x = foo();
`, { ecmaVersion: 2022, sourceType: "module" });

const moduleScopeManager = analyze(moduleAst, { 
  ecmaVersion: 2022, 
  sourceType: "module" 
});

console.log(`Module has ${moduleScopeManager.scopes.length} scopes`);
console.log(`Is module: ${moduleScopeManager.isModule()}`);

// Script analysis  
const scriptAst = espree.parse(`
  var x = require("bar");
`, { ecmaVersion: 2022, sourceType: "script" });

const scriptScopeManager = analyze(scriptAst, { 
  ecmaVersion: 2022, 
  sourceType: "script" 
});

console.log(`Script has ${scriptScopeManager.scopes.length} scopes`);
console.log(`Is module: ${scriptScopeManager.isModule()}`);

Scope Types

eslint-scope identifies the following scope types:

  • global - The outermost scope containing global variables
  • module - ES module scope (when sourceType is "module")
  • function - Function scope created by function declarations/expressions
  • block - Block scope created by let/const/class in blocks
  • switch - Switch statement scope
  • catch - Catch clause scope for the exception variable
  • with - With statement scope (creates dynamic scope)
  • for - For statement scope for loop variables
  • class - Class scope for class expressions
  • function-expression-name - Scope for named function expressions

Reference Types

References are categorized by their usage pattern:

  • READ (1) - Variable is read/accessed
  • WRITE (2) - Variable is written/assigned
  • RW (3) - Variable is both read and written (e.g., x += 1)

Integration with ESLint

eslint-scope is designed to work seamlessly with ESLint rules:

// Example ESLint rule using eslint-scope
module.exports = {
  create(context) {
    return {
      Program() {
        const scopeManager = context.getSourceCode().scopeManager;
        
        // Access scope information
        scopeManager.scopes.forEach(scope => {
          scope.variables.forEach(variable => {
            if (variable.references.length === 0) {
              context.report({
                node: variable.identifiers[0],
                message: `'${variable.name}' is defined but never used.`
              });
            }
          });
        });
      }
    };
  }
};