CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-typescript-eslint--experimental-utils

(Experimental) Utilities for working with TypeScript + ESLint together

Pending
Overview
Eval results
Files

scope-analysis.mddocs/

Scope Analysis

Utilities for analyzing variable scopes, references, and bindings in TypeScript code. This module provides comprehensive scope analysis capabilities essential for understanding variable usage, detecting unused variables, and tracking references across different scopes.

Capabilities

Scope Analysis Function

Core function for analyzing AST and creating scope managers.

/**
 * Analyzes an AST to create a scope manager with variable and reference information
 * @param ast - Root AST node to analyze
 * @param options - Optional analysis configuration
 * @returns ScopeManager instance containing all scope information
 */
function analyze(ast: TSESTree.Node, options?: AnalysisOptions): ScopeManager;

/**
 * Configuration options for scope analysis
 */
interface AnalysisOptions {
  /** Whether to be optimistic about dynamic references */
  optimistic?: boolean;
  /** Whether to support directive prologues */
  directive?: boolean;
  /** Whether to ignore eval() calls in scope analysis */
  ignoreEval?: boolean;
  /** Whether to create Node.js-style global scope */
  nodejsScope?: boolean;
  /** Whether code is in implied strict mode */
  impliedStrict?: boolean;
  /** Fallback function for unrecognized AST nodes */
  fallback?: string | ((node: any) => string[]);
  /** Source type: 'module' or 'script' */
  sourceType?: 'module' | 'script';
  /** ECMAScript version */
  ecmaVersion?: number;
  /** ECMAScript features configuration */
  ecmaFeatures?: {
    globalReturn?: boolean;
    impliedStrict?: boolean;
    jsx?: boolean;
  };
}

Scope Manager

Central manager for all scopes in an analyzed AST.

/**
 * Manages all scopes in an analyzed AST
 */
interface ScopeManager {
  /** All scopes in the AST */
  readonly scopes: Scope[];
  /** Global scope (null if not available) */
  readonly globalScope: GlobalScope | null;
  /** Current scope being processed */
  readonly __currentScope: Scope | null;
  /** Analysis options used */
  readonly __options: Required<AnalysisOptions>;
  /** Node to scope mapping */
  readonly __nodeToScope: WeakMap<TSESTree.Node, Scope>;
  /** Declared variables cache */
  readonly __declaredVariables: WeakMap<TSESTree.Node, Variable[]>;

  /**
   * Acquires the scope for a given AST node
   * @param node - AST node to get scope for
   * @param inner - Whether to get inner scope
   * @returns Scope for the node, or null if not found
   */
  acquire(node: TSESTree.Node, inner?: boolean): Scope | null;

  /**
   * Releases a scope (cleanup)
   * @param scope - Scope to release
   */
  release(scope: Scope): void;

  /**
   * Gets all variables declared by a specific AST node
   * @param node - AST node that declares variables
   * @returns Array of variables declared by the node
   */
  getDeclaredVariables(node: TSESTree.Node): Variable[];

  /**
   * Checks if a scope is strict mode
   * @param scope - Scope to check
   * @returns True if scope is in strict mode
   */
  isStrictModeSupported(): boolean;

  /**
   * Checks if global return is supported
   * @returns True if global return is supported
   */
  isGlobalReturn(): boolean;

  /**
   * Checks if implied strict mode is enabled
   * @returns True if implied strict mode is enabled
   */
  isImpliedStrict(): boolean;

  /**
   * Checks if the source type is module
   * @returns True if source type is module
   */
  isModule(): boolean;

  /**
   * Gets the scope that contains the given node
   * @param node - AST node to find scope for
   * @returns Scope containing the node
   */
  __get(node: TSESTree.Node): Scope | null;
}

Scope Types

Different types of scopes with their specific characteristics.

/**
 * Union type of all possible scope types
 */
type ScopeType = 
  | 'block'
  | 'catch'
  | 'class'
  | 'for'
  | 'function'
  | 'function-expression-name'
  | 'global'
  | 'module'
  | 'switch'
  | 'with'
  | 'conditional-type'
  | 'function-type'
  | 'mapped-type'
  | 'ts-enum'
  | 'ts-module'
  | 'type';

/**
 * Base scope interface with common properties and methods
 */
interface Scope {
  /** Type of this scope */
  readonly type: ScopeType;
  /** Whether this scope is in strict mode */
  readonly isStrict: boolean;
  /** Parent scope (null for global scope) */
  readonly upper: Scope | null;
  /** Direct child scopes */
  readonly childScopes: Scope[];
  /** Nearest function or global scope */
  readonly variableScope: Scope;
  /** AST node that created this scope */
  readonly block: TSESTree.Node;
  /** Variables defined in this scope */
  readonly variables: Variable[];
  /** References to variables in this scope */
  readonly references: Reference[];
  /** Map of variable names to Variable objects */
  readonly set: Map<string, Variable>;
  /** References that go through this scope to outer scopes */
  readonly through: Reference[];
  /** Function parameter names (function scopes only) */
  readonly functionExpressionScope?: boolean;

  /**
   * Checks if this scope contains a variable with the given name
   * @param name - Variable name to check
   * @returns True if variable exists in this scope
   */
  __shouldStaticallyClose(scopeManager: ScopeManager): boolean;

  /**
   * Closes this scope
   * @param scopeManager - Scope manager instance
   */
  __close(scopeManager: ScopeManager): void;

  /**
   * Checks if a variable name is defined in this scope
   * @param name - Variable name to check
   * @returns True if variable is defined
   */
  __isValidResolution(ref: Reference, variable: Variable): boolean;

  /**
   * Adds a variable to this scope
   * @param variable - Variable to add
   */
  __addVariable(variable: Variable): void;

  /**
   * Removes a variable from this scope
   * @param variable - Variable to remove
   */
  __removeVariable(variable: Variable): void;

  /**
   * Defines a variable in this scope
   * @param name - Variable name
   * @param set - Variable set to add to
   * @param variables - Variables array to add to
   * @param node - AST node that defines the variable
   * @param def - Variable definition
   */
  __define(name: string, set: Map<string, Variable>, variables: Variable[], node: TSESTree.Node, def: Definition): void;

  /**
   * References a variable from this scope
   * @param ref - Reference to add
   */
  __referencing(ref: Reference): void;

  /**
   * Detaches a reference from this scope
   * @param ref - Reference to detach
   */
  __detach(): void;

  /**
   * Checks if this scope contains the given node
   * @param node - AST node to check
   * @returns True if scope contains the node
   */
  __isValidResolution(reference: Reference, variable: Variable): boolean;
}

Specific Scope Types

Specialized scope types for different contexts.

/**
 * Global scope - the root scope containing global variables
 */
interface GlobalScope extends Scope {
  type: 'global';
  upper: null;
  variableScope: GlobalScope;
}

/**
 * Module scope - for ES modules
 */
interface ModuleScope extends Scope {
  type: 'module';
  upper: GlobalScope;
  variableScope: ModuleScope;
}

/**
 * Function scope - created by function declarations and expressions
 */
interface FunctionScope extends Scope {
  type: 'function';
  variableScope: FunctionScope;
  /** Whether this is a function expression name scope */
  functionExpressionScope: boolean;
}

/**
 * Function expression name scope - for named function expressions
 */
interface FunctionExpressionNameScope extends Scope {
  type: 'function-expression-name';
  functionExpressionScope: true;
}

/**
 * Block scope - created by block statements, for/while loops, etc.
 */
interface BlockScope extends Scope {
  type: 'block';
}

/**
 * Class scope - created by class declarations and expressions
 */
interface ClassScope extends Scope {
  type: 'class';
}

/**
 * Catch scope - created by catch clauses
 */
interface CatchScope extends Scope {
  type: 'catch';
}

/**
 * With scope - created by with statements
 */
interface WithScope extends Scope {
  type: 'with';
}

/**
 * Switch scope - created by switch statements
 */
interface SwitchScope extends Scope {
  type: 'switch';
}

/**
 * For scope - created by for/for-in/for-of statements
 */
interface ForScope extends Scope {
  type: 'for';
}

/**
 * TypeScript enum scope
 */
interface TSEnumScope extends Scope {
  type: 'ts-enum';
}

/**
 * TypeScript module scope
 */
interface TSModuleScope extends Scope {
  type: 'ts-module';
}

/**
 * TypeScript type scope
 */
interface TypeScope extends Scope {
  type: 'type';
}

/**
 * TypeScript conditional type scope
 */
interface ConditionalTypeScope extends Scope {
  type: 'conditional-type';
}

/**
 * TypeScript function type scope
 */
interface FunctionTypeScope extends Scope {
  type: 'function-type';
}

/**
 * TypeScript mapped type scope
 */
interface MappedTypeScope extends Scope {
  type: 'mapped-type';
}

Variables

Variable representation with definition and usage information.

/**
 * Represents a variable with its definitions and references
 */
interface Variable {
  /** Variable name */
  readonly name: string;
  /** Identifiers where this variable is defined */
  readonly identifiers: TSESTree.Identifier[];
  /** References to this variable */
  readonly references: Reference[];
  /** Variable definitions */
  readonly defs: Definition[];
  /** Scope where this variable is defined */
  readonly scope: Scope;
  /** Stack of definitions (for complex cases) */
  readonly stack?: boolean;
  /** Whether this is a type-only variable */
  readonly tainted?: boolean;
  /** ESLint-specific: whether variable is used */
  eslintUsed?: boolean;
  /** ESLint-specific: whether to explicitly mark as used */
  eslintExplicitGlobal?: boolean;
  /** ESLint-specific: whether variable is defined implicitly */
  eslintImplicitGlobalSetting?: 'readonly' | 'writable' | 'off';
  /** ESLint-specific: line where variable is defined */
  eslintExplicitGlobalComments?: TSESTree.Comment[];
}

References

Variable reference information tracking usage locations.

/**
 * Represents a reference to a variable
 */
interface Reference {
  /** Identifier node of the reference */
  readonly identifier: TSESTree.Identifier;
  /** Scope containing this reference */
  readonly from: Scope;
  /** Variable being referenced (null if unresolved) */
  resolved: Variable | null;
  /** Type of reference */
  readonly flag: Reference.Flag;
  /** Whether this is a write reference */
  readonly isWrite: boolean;
  /** Whether this is a read reference */
  readonly isRead: boolean;
  /** Whether this is a read-write reference */
  readonly isReadWrite: boolean;
  /** Whether this reference creates a new binding */
  readonly isReadOnly: boolean;
  /** Partial flag information */
  readonly partial: boolean;
  /** Whether this reference initializes the variable */
  init?: boolean;
  /** Write expression (for write references) */
  writeExpr?: TSESTree.Node;
  /** Whether reference may throw */
  maybeImplicitGlobal?: boolean;
}

declare namespace Reference {
  /**
   * Reference type flags
   */
  const enum Flag {
    Read = 0x1,
    Write = 0x2,
    ReadWrite = Read | Write,
  }
}

Variable Definitions

Information about where and how variables are defined.

/**
 * Base interface for variable definitions
 */
interface Definition {
  /** Type of definition */
  readonly type: Definition.Type;
  /** Variable name being defined */
  readonly name: TSESTree.Identifier;
  /** AST node that defines the variable */
  readonly node: TSESTree.Node;
  /** Parent node of the definition */
  readonly parent?: TSESTree.Node;
  /** Index in parent node (for array-like parents) */
  readonly index?: number;
  /** Kind of definition (var, let, const, etc.) */
  readonly kind?: string;
}

declare namespace Definition {
  /**
   * Types of variable definitions
   */
  type Type = 
    | 'CatchClause'
    | 'ClassName'
    | 'FunctionName'
    | 'ImplicitGlobalVariable'
    | 'ImportBinding'
    | 'Parameter'
    | 'TSEnumName'
    | 'TSEnumMember'
    | 'TSModuleName'
    | 'Type'
    | 'Variable';
}

/**
 * Variable declaration definition (var, let, const)
 */
interface VariableDefinition extends Definition {
  type: 'Variable';
  node: TSESTree.VariableDeclarator;
  parent: TSESTree.VariableDeclaration;
  kind: 'var' | 'let' | 'const';
}

/**
 * Function parameter definition
 */
interface ParameterDefinition extends Definition {
  type: 'Parameter';
  node: TSESTree.Parameter;
  rest?: boolean;
}

/**
 * Function name definition
 */
interface FunctionNameDefinition extends Definition {
  type: 'FunctionName';
  node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression;
}

/**
 * Class name definition
 */
interface ClassNameDefinition extends Definition {
  type: 'ClassName';
  node: TSESTree.ClassDeclaration | TSESTree.ClassExpression;
}

/**
 * Import binding definition
 */
interface ImportBindingDefinition extends Definition {
  type: 'ImportBinding';
  node: TSESTree.ImportSpecifier | TSESTree.ImportDefaultSpecifier | TSESTree.ImportNamespaceSpecifier;
  parent: TSESTree.ImportDeclaration;
}

/**
 * Catch clause parameter definition
 */
interface CatchClauseDefinition extends Definition {
  type: 'CatchClause';
  node: TSESTree.CatchClause;
}

/**
 * TypeScript enum name definition
 */
interface TSEnumNameDefinition extends Definition {
  type: 'TSEnumName';
  node: TSESTree.TSEnumDeclaration;
}

/**
 * TypeScript enum member definition
 */
interface TSEnumMemberDefinition extends Definition {
  type: 'TSEnumMember';
  node: TSESTree.TSEnumMember;
}

/**
 * TypeScript module name definition
 */
interface TSModuleNameDefinition extends Definition {
  type: 'TSModuleName';
  node: TSESTree.TSModuleDeclaration;
}

/**
 * Type definition (interface, type alias)
 */
interface TypeDefinition extends Definition {
  type: 'Type';
  node: TSESTree.TSInterfaceDeclaration | TSESTree.TSTypeAliasDeclaration;
}

/**
 * Implicit global variable definition
 */
interface ImplicitGlobalVariableDefinition extends Definition {
  type: 'ImplicitGlobalVariable';
  node: TSESTree.Program;
}

Version Information

/**
 * ESLint scope version string
 */
const version: string;

Usage Examples

import { TSESLintScope, TSESTree } from "@typescript-eslint/experimental-utils";

// Analyze an AST to create scopes
function analyzeScopes(ast: TSESTree.Program): TSESLintScope.ScopeManager {
  const scopeManager = TSESLintScope.analyze(ast, {
    ecmaVersion: 2020,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true
    }
  });
  
  return scopeManager;
}

// Find all variables in global scope
function findGlobalVariables(scopeManager: TSESLintScope.ScopeManager): TSESLintScope.Variable[] {
  const globalScope = scopeManager.globalScope;
  return globalScope ? globalScope.variables : [];
}

// Check if a variable is used
function isVariableUsed(variable: TSESLintScope.Variable): boolean {
  return variable.references.some(ref => ref.isRead);
}

// Find unused variables in a scope
function findUnusedVariables(scope: TSESLintScope.Scope): TSESLintScope.Variable[] {
  return scope.variables.filter(variable => {
    // Skip function names and parameters that might be required
    const isParameter = variable.defs.some(def => def.type === 'Parameter');
    const isFunctionName = variable.defs.some(def => def.type === 'FunctionName');
    
    if (isParameter || isFunctionName) {
      return false;
    }
    
    // Check if variable has any read references
    return !variable.references.some(ref => ref.isRead);
  });
}

// Track variable references across scopes
function trackVariableUsage(scopeManager: TSESLintScope.ScopeManager, variableName: string): void {
  scopeManager.scopes.forEach(scope => {
    const variable = scope.set.get(variableName);
    if (variable) {
      console.log(`Variable '${variableName}' defined in ${scope.type} scope`);
      
      variable.references.forEach(ref => {
        const refType = ref.isRead ? 'read' : ref.isWrite ? 'write' : 'unknown';
        console.log(`  Referenced as ${refType} at line ${ref.identifier.loc.start.line}`);
      });
    }
  });
}

// Find variables that escape their scope
function findEscapingVariables(scope: TSESLintScope.Scope): TSESLintScope.Variable[] {
  return scope.variables.filter(variable => {
    return variable.references.some(ref => ref.from !== scope);
  });
}

// Analyze function parameters
function analyzeFunctionParameters(functionScope: TSESLintScope.FunctionScope): void {
  const parameters = functionScope.variables.filter(variable => 
    variable.defs.some(def => def.type === 'Parameter')
  );
  
  parameters.forEach(param => {
    const isUsed = param.references.some(ref => ref.isRead);
    const paramDef = param.defs.find(def => def.type === 'Parameter') as TSESLintScope.ParameterDefinition;
    
    console.log(`Parameter '${param.name}' is ${isUsed ? 'used' : 'unused'}`);
    if (paramDef.rest) {
      console.log(`  Rest parameter`);
    }
  });
}

// Check scope hierarchy
function printScopeHierarchy(scope: TSESLintScope.Scope, indent = 0): void {
  const indentStr = '  '.repeat(indent);
  console.log(`${indentStr}${scope.type} scope (${scope.variables.length} variables)`);
  
  scope.childScopes.forEach(childScope => {
    printScopeHierarchy(childScope, indent + 1);
  });
}

// Find variable definition location
function findVariableDefinition(variable: TSESLintScope.Variable): TSESTree.Node | null {
  const definition = variable.defs[0];
  if (!definition) return null;
  
  switch (definition.type) {
    case 'Variable':
      return definition.node; // VariableDeclarator
    case 'FunctionName':
      return definition.node; // FunctionDeclaration
    case 'Parameter':
      return definition.node; // Parameter node
    case 'ImportBinding':
      return definition.node; // ImportSpecifier
    default:
      return definition.node;
  }
}

// Usage in an ESLint rule
const rule: TSESLint.RuleModule<'unusedVariable', []> = {
  meta: {
    type: 'suggestion',
    messages: {
      unusedVariable: 'Variable "{{name}}" is defined but never used'
    },
    schema: []
  },
  create(context) {
    return {
      'Program:exit'(node: TSESTree.Program) {
        const sourceCode = context.getSourceCode();
        const scopeManager = sourceCode.scopeManager;
        
        scopeManager.scopes.forEach(scope => {
          const unusedVars = findUnusedVariables(scope);
          
          unusedVars.forEach(variable => {
            const def = variable.identifiers[0];
            if (def) {
              context.report({
                node: def,
                messageId: 'unusedVariable',
                data: { name: variable.name }
              });
            }
          });
        });
      }
    };
  }
};

Install with Tessl CLI

npx tessl i tessl/npm-typescript-eslint--experimental-utils

docs

ast-utils.md

eslint-utils.md

index.md

json-schema.md

scope-analysis.md

ts-eslint.md

ts-estree.md

tile.json