CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-typescript-eslint--scope-manager

TypeScript scope analyser for ESLint that provides comprehensive scope analysis capabilities for JavaScript and TypeScript code

Pending
Overview
Eval results
Files

reference-system.mddocs/

Reference System

The reference tracking system provides comprehensive reference tracking that identifies all identifier occurrences and their usage patterns throughout the analyzed code.

Capabilities

Reference Class

The main Reference class that represents a single occurrence of an identifier in code.

/**
 * A Reference represents a single occurrence of an identifier in code.
 */
class Reference {
  /** A unique ID for this instance - primarily used to help debugging and testing */
  readonly $id: number;

  /** Reference to the enclosing Scope */
  readonly from: Scope;

  /** Identifier syntax node */
  readonly identifier: TSESTree.Identifier | TSESTree.JSXIdentifier;

  /** True if this reference is the initial declaration/definition */
  readonly init: boolean;

  /** Information about implicit global references */
  readonly maybeImplicitGlobal: ReferenceImplicitGlobal | null;

  /** The variable that this identifier references, or null if unresolved */
  readonly resolved: Variable | null;

  /** The expression being written to, if this is a write reference */
  readonly writeExpr: TSESTree.Node | null;

  /** True if this reference is in a type context */
  get isTypeReference(): boolean;

  /** True if this reference is in a value context */
  get isValueReference(): boolean;

  /** Check if this reference represents a write operation */
  isWrite(): boolean;

  /** Check if this reference represents a read operation */
  isRead(): boolean;

  /** Check if this reference is read-only */
  isReadOnly(): boolean;

  /** Check if this reference is write-only */
  isWriteOnly(): boolean;

  /** Check if this reference is both read and write */
  isReadWrite(): boolean;
}

Reference Flags

Enumeration defining the read/write characteristics of references.

enum ReferenceFlag {
  Read = 0x1,      // Reference reads the variable
  Write = 0x2,     // Reference writes to the variable  
  ReadWrite = 0x3  // Reference both reads and writes (e.g., +=, ++, --)
}

enum ReferenceTypeFlag {
  Value = 0x1,     // Reference is in a value context
  Type = 0x2       // Reference is in a type context
}

Implicit Global Reference

Interface for tracking references that may create implicit global variables.

interface ReferenceImplicitGlobal {
  /** The AST node where the implicit global is referenced */
  node: TSESTree.Node;
  
  /** The identifier pattern being referenced */
  pattern: TSESTree.BindingName;
  
  /** The reference object if created */
  ref?: Reference;
}

Usage Examples:

import { analyze } from "@typescript-eslint/scope-manager";
import { parse } from "@typescript-eslint/parser";

const code = `
let count = 0;
const message = "Hello";

function increment() {
  count++;        // Read-write reference to 'count'
  return count;   // Read reference to 'count'
}

function greet(name: string) {
  const greeting = message + ", " + name;  // Read reference to 'message'
  return greeting;
}

// Type reference
interface User {
  name: string;
}

function createUser(): User {  // Type reference to 'User'
  return { name: "Alice" };
}
`;

const ast = parse(code);
const scopeManager = analyze(ast, { sourceType: 'module' });

// Analyze all references across all scopes
console.log('=== Reference Analysis ===');
scopeManager.scopes.forEach(scope => {
  console.log(`\nScope: ${scope.type}`);
  console.log(`References: ${scope.references.length}`);
  
  scope.references.forEach((ref, index) => {
    console.log(`  Reference ${index}: ${ref.identifier.name}`);
    console.log(`    Type: ${ref.isTypeReference ? 'Type' : 'Value'}`);
    console.log(`    Read: ${ref.isRead()}`);
    console.log(`    Write: ${ref.isWrite()}`);
    console.log(`    Resolved: ${ref.resolved ? ref.resolved.name : 'unresolved'}`);
    
    if (ref.writeExpr) {
      console.log(`    Write expression: ${ref.writeExpr.type}`);
    }
    
    console.log(`    From scope: ${ref.from.type}`);
  });
});

Reference Usage Patterns

Analyze different types of reference patterns:

// Collect and categorize references
const allReferences: Reference[] = [];
scopeManager.scopes.forEach(scope => {
  allReferences.push(...scope.references);
});

// Categorize by operation type
const readRefs = allReferences.filter(ref => ref.isReadOnly());
const writeRefs = allReferences.filter(ref => ref.isWriteOnly());  
const readWriteRefs = allReferences.filter(ref => ref.isReadWrite());

console.log('Reference Operation Analysis:');
console.log(`  Read-only: ${readRefs.length}`);
console.log(`  Write-only: ${writeRefs.length}`);
console.log(`  Read-write: ${readWriteRefs.length}`);

// Categorize by context
const typeRefs = allReferences.filter(ref => ref.isTypeReference);
const valueRefs = allReferences.filter(ref => ref.isValueReference);

console.log('\nReference Context Analysis:');
console.log(`  Type context: ${typeRefs.length}`);
console.log(`  Value context: ${valueRefs.length}`);

// Analyze read-write operations
console.log('\nRead-Write Operations:');
readWriteRefs.forEach(ref => {
  console.log(`  ${ref.identifier.name}: ${ref.writeExpr?.type}`);
});

Reference Resolution Analysis

Track how references resolve to their variable definitions:

// Analyze reference resolution
console.log('=== Reference Resolution Analysis ===');

const resolvedRefs = allReferences.filter(ref => ref.resolved);
const unresolvedRefs = allReferences.filter(ref => !ref.resolved);

console.log(`Resolved references: ${resolvedRefs.length}`);
console.log(`Unresolved references: ${unresolvedRefs.length}`);

// Group resolved references by variable
const refsByVariable = new Map<Variable, Reference[]>();
resolvedRefs.forEach(ref => {
  if (ref.resolved) {
    if (!refsByVariable.has(ref.resolved)) {
      refsByVariable.set(ref.resolved, []);
    }
    refsByVariable.get(ref.resolved)!.push(ref);
  }
});

console.log('\nReferences per variable:');
refsByVariable.forEach((refs, variable) => {
  console.log(`  ${variable.name}: ${refs.length} references`);
  
  const scopes = new Set(refs.map(ref => ref.from.type));
  console.log(`    Used in scopes: ${Array.from(scopes).join(', ')}`);
  
  const operations = {
    read: refs.filter(ref => ref.isRead()).length,
    write: refs.filter(ref => ref.isWrite()).length
  };
  console.log(`    Operations: ${operations.read} reads, ${operations.write} writes`);
});

// Analyze unresolved references (potential issues)
if (unresolvedRefs.length > 0) {
  console.log('\n⚠️  Unresolved References:');
  unresolvedRefs.forEach(ref => {
    console.log(`  ${ref.identifier.name} in ${ref.from.type} scope`);
    
    if (ref.maybeImplicitGlobal) {
      console.log(`    May be implicit global`);
    }
  });
}

Cross-Scope Reference Analysis

Analyze how references cross scope boundaries:

// Analyze cross-scope references
console.log('=== Cross-Scope Reference Analysis ===');

scopeManager.scopes.forEach(scope => {
  console.log(`\nScope: ${scope.type}`);
  
  // References that resolve to variables in parent scopes
  const crossScopeRefs = scope.references.filter(ref => 
    ref.resolved && ref.resolved.scope !== scope
  );
  
  if (crossScopeRefs.length > 0) {
    console.log(`  Cross-scope references: ${crossScopeRefs.length}`);
    crossScopeRefs.forEach(ref => {
      console.log(`    ${ref.identifier.name} -> ${ref.resolved?.scope.type} scope`);
    });
  }
  
  // Through references (unresolved in this scope)
  if (scope.through.length > 0) {
    console.log(`  Through references: ${scope.through.length}`);
    scope.through.forEach(ref => {
      console.log(`    ${ref.identifier.name} (unresolved)`);
    });
  }
});

Reference Context Analysis

Analyze TypeScript-specific type vs value contexts:

// TypeScript context analysis
const code = `
interface Point {
  x: number;
  y: number;
}

class Rectangle {
  width: number;
  height: number;
}

function createPoint(): Point {           // Type reference
  return { x: 0, y: 0 };
}

function createRect(): Rectangle {        // Type reference
  return new Rectangle();                 // Value reference
}

const point: Point = createPoint();       // Type and value references
const rect = new Rectangle();             // Value reference only
`;

const ast = parse(code);
const scopeManager = analyze(ast, { sourceType: 'module' });

// Analyze context usage
console.log('=== Context Analysis ===');
const contextAnalysis = new Map<string, { type: number, value: number }>();

scopeManager.scopes.forEach(scope => {
  scope.references.forEach(ref => {
    const name = ref.identifier.name;
    if (!contextAnalysis.has(name)) {
      contextAnalysis.set(name, { type: 0, value: 0 });
    }
    
    const stats = contextAnalysis.get(name)!;
    if (ref.isTypeReference) stats.type++;
    if (ref.isValueReference) stats.value++;
  });
});

contextAnalysis.forEach((stats, name) => {
  console.log(`${name}:`);
  console.log(`  Type context: ${stats.type} references`);
  console.log(`  Value context: ${stats.value} references`);
  
  if (stats.type > 0 && stats.value > 0) {
    console.log(`  ✓ Used in both contexts`);
  } else if (stats.type > 0) {
    console.log(`  📝 Type-only usage`);
  } else {
    console.log(`  💾 Value-only usage`);
  }
});

Write Expression Analysis

Analyze different types of write operations:

// Analyze write expressions
console.log('=== Write Expression Analysis ===');

const writeRefs = allReferences.filter(ref => ref.writeExpr);

const writeTypes = new Map<string, number>();
writeRefs.forEach(ref => {
  const type = ref.writeExpr!.type;
  writeTypes.set(type, (writeTypes.get(type) || 0) + 1);
});

console.log('Write expression types:');
writeTypes.forEach((count, type) => {
  console.log(`  ${type}: ${count}`);
});

// Examples of write expressions:
// - AssignmentExpression: x = 5
// - UpdateExpression: x++, --y
// - VariableDeclarator: let x = 5
// - FunctionDeclaration: function f() {}
// - Parameter: function f(x) {} (x is written to)

Reference Lifecycle

Understanding the lifecycle of references:

// Track reference lifecycle
console.log('=== Reference Lifecycle Analysis ===');

resolvedRefs.forEach(ref => {
  if (ref.resolved) {
    const variable = ref.resolved;
    console.log(`\nReference: ${ref.identifier.name}`);
    console.log(`  Variable defined in: ${variable.scope.type} scope`);
    console.log(`  Referenced from: ${ref.from.type} scope`);
    console.log(`  Definition count: ${variable.defs.length}`);
    console.log(`  Total references: ${variable.references.length}`);
    
    // Find if this is the first reference
    const refIndex = variable.references.indexOf(ref);
    console.log(`  Reference order: ${refIndex + 1} of ${variable.references.length}`);
    
    if (ref.init) {
      console.log(`  ✓ This is a defining reference`);
    }
  }
});

Install with Tessl CLI

npx tessl i tessl/npm-typescript-eslint--scope-manager

docs

analysis.md

definition-system.md

index.md

reference-system.md

scope-management.md

scope-types.md

variable-system.md

tile.json