Understanding how Code Evolution Lab analyzes JavaScript/TypeScript code.

Overview

What It Is: Code Evolution Lab uses Abstract Syntax Tree (AST) analysis to detect performance issues in JavaScript/TypeScript code. AST analysis goes beyond surface-level pattern matching to understand the actual structure, meaning, and execution flow of your code.

Why AST Over Regex: Traditional linters use regex patterns, which are brittle and prone to false positives:

Key Advantages:

Real Impact: AST analysis enables detection of complex patterns like “database query inside a loop nested in an async function” that would be impossible with regex.

Analysis Pipeline

Source Code


┌─────────────┐
│ Babel Parser│ ─── Converts code to AST
└─────────────┘


┌─────────────────┐
│ Import Analyzer │ ─── Identifies ORM packages and symbols
└─────────────────┘


┌─────────────────┐
│ Context Builder │ ─── Creates AnalysisContext
└─────────────────┘


┌─────────────────┐
│ Detectors (11)  │ ─── Run in parallel
└─────────────────┘


┌─────────────────────┐
│ Solution Generators │ ─── Create fix candidates
└─────────────────────┘


┌─────────────────────┐
│ Evolutionary Engine │ ─── (Optional) Refine solutions
└─────────────────────┘


┌─────────────────┐
│ Ranked Solutions│
└─────────────────┘

AST Parsing

What It Does: AST parsing converts source code into a structured tree representation where each node represents a syntactic construct (function, variable, loop, etc.). This tree preserves all semantic information about the code’s structure and relationships.

Why Babel: Babel is the industry-standard JavaScript parser, supporting all modern syntax:

Parser Configuration

The Babel parser is configured with 8 plugins:

const ast = parser.parse(code, {
  sourceType: 'module',
  plugins: [
    'typescript',
    'jsx',
    'decorators-legacy',
    'classProperties',
    'asyncGenerators',
    'dynamicImport',
    'optionalChaining',
    'nullishCoalescingOperator',
  ],
});

AST Example

Source Code:

async function getUsers() {
  const users = await User.findAll();
  return users;
}

AST Representation (simplified):

{
  "type": "FunctionDeclaration",
  "id": { "name": "getUsers" },
  "async": true,
  "body": {
    "type": "BlockStatement",
    "body": [
      {
        "type": "VariableDeclaration",
        "declarations": [{
          "id": { "name": "users" },
          "init": {
            "type": "AwaitExpression",
            "argument": {
              "type": "CallExpression",
              "callee": {
                "object": { "name": "User" },
                "property": { "name": "findAll" }
              }
            }
          }
        }]
      }
    ]
  }
}

Import Analysis

What It Does: The Import Analyzer scans all import/require statements to build a symbol table mapping variable names to their source packages. This enables ORM-specific detection patterns and eliminates false positives.

Why It Matters: Without import analysis, the detector can’t distinguish:

How It Works:

  1. Package Detection: Scans for known ORM imports (@prisma/client, sequelize, mongoose)
  2. Symbol Mapping: Tracks renamed imports (import { Prisma as DB }DB maps to Prisma)
  3. Instance Tracking: Follows const prisma = new PrismaClient() instantiation

Both import statements and require() calls are analysed. This context is passed to every detector for accurate pattern matching.

The Import Analyzer tracks ORM packages and their symbols to enable accurate detection:

// Input code
import { PrismaClient } from '@prisma/client';
import User from './models/User';  // Sequelize model

// Import Analyzer output
{
  detectedORMs: Set(['prisma', 'sequelize']),
  symbolToORM: Map([
    ['PrismaClient', 'prisma'],
    ['User', 'sequelize']
  ]),
  prismaClientVar: 'prisma'  // If instantiated
}

Supported ORMs

ORM Detection Pattern
Prisma @prisma/client import
Sequelize sequelize import, Model patterns
Mongoose mongoose import
TypeORM typeorm import
Knex knex import
Raw SQL pg, mysql, mysql2, better-sqlite3, sqlite3 imports

Analysis Context

What It Is: The AnalysisContext is a shared data structure passed to all detectors, containing the parsed AST, original source code, and metadata about the file’s dependencies and framework.

Why It’s Needed: Detectors need context to make intelligent decisions:

Note that framework information is not part of this shared context. Detectors that care about it determine it themselves and record it on the issue, under estimatedImpact.metrics.framework, where solution generators read it.

Without this context, detectors would need to re-parse and re-analyze, wasting CPU and duplicating logic.

Every detector receives an AnalysisContext object:

interface AnalysisContext {
  sourceCode: string;     // Original source code
  filePath: string;       // File path for reporting
  ast: any;               // Parsed AST
  ormContext?: {
    detectedORMs: Set<string>;
    symbolToORM: Map<string, string>;
    prismaClientVar?: string;
  };
}

Code Analyzer Class

The main orchestrator for analysis:

class CodeAnalyzer extends EventEmitter {
  // Initialize with optional tier context
  constructor(tierContext?: UserTierContext);
  
  // Analyze a file
  async analyzeFile(filePath: string): Promise<DetectorResult[]>;
  
  // Analyze code string
  async analyzeCode(
    sourceCode: string,
    filePath?: string,
    generateSolutions?: boolean
  ): Promise<DetectorResult[]>;
  
  // Get detector names
  getDetectorNames(): string[];
  getBasicDetectorNames(): string[];
  getAdvancedDetectorNames(): string[];
  
  // Tier management
  setTierContext(tierContext: UserTierContext): void;
  checkAnalysisAllowed(usageStats: UsageStats): LimitCheckResult;
}

Detector Results

Each detector returns a DetectorResult:

interface DetectorResult {
  issues: Issue[];
  detectorName: string;
}

interface Issue {
  id: string;
  type: string;                    // e.g., 'n_plus_1_query'
  severity: 'critical' | 'high' | 'medium' | 'low';
  filePath: string;
  lineNumber: number;
  title: string;
  description: string;
  codeBefore: string;              // Problematic code
  codeAfter?: string;              // Quick fix suggestion
  estimatedImpact?: EstimatedImpact;
  solutions?: Solution[];          // AI-generated solutions
}

Estimated Impact

Each issue includes impact assessment:

interface EstimatedImpact {
  severityScore: number;           // 0-100
  description: string;
  confidenceScore: number;         // 0-1
  category: 'performance' | 'memory' | 'network' | 'complexity';
  fixDifficulty: 'trivial' | 'easy' | 'moderate' | 'complex';
  metrics: {
    potentialQueryReduction?: string;
    memoryImpact?: string;
    cpuImpact?: string;
    [key: string]: any;
  };
}

Tier-Based Analysis

What It Does: All 11 detectors run on every tier. What varies by tier is how many solutions are generated per issue and how deeply the evolutionary engine searches.

Enforcement: Tier limits are checked before analysis starts:

Feature Free Pro
Detectors 11 11
Solutions per Issue 3 5
Population Size 10 15
Max Generations 5 10

See Pricing Tiers for the full comparison.

Event-Driven Progress

What It Does: The analyzer extends Node.js EventEmitter to broadcast real-time progress events during analysis. This enables UI updates, progress bars, and Server-Sent Events (SSE) streaming to the frontend.

Why Events: Analysis can take 5-30 seconds for large files:

Event Types:

The analyzer emits events for real-time updates:

const analyzer = new CodeAnalyzer();

// Quick heuristic solutions ready
analyzer.on('quick-solutions', (data) => {
  console.log(`Found ${data.solutions.length} solutions for ${data.issueType}`);
});

// Evolution starting
analyzer.on('evolution-start', (data) => {
  console.log(`Starting evolution for ${data.issueType}`);
});

// Evolution progress (per generation)
analyzer.on('evolution-progress', (data) => {
  console.log(`Gen ${data.generation}: Best fitness ${data.bestFitness}`);
});

// Evolution complete
analyzer.on('evolution-complete', (data) => {
  console.log(`Evolution complete with ${data.solutions.length} solutions`);
});

// Run analysis
const results = await analyzer.analyzeCode(code, 'example.js', true);

Performance Considerations

What It Does: The analyzer uses multiple optimization strategies to minimize latency and resource usage while analyzing code.

Why Performance Matters: Users expect fast feedback:

Optimization Strategies:

Concurrency

Caching

Timeouts

// Environment configuration
EVO_MAX_TIME_MS=30000  // Maximum evolution time per issue

This is a per-issue cap on evolution, not a request timeout. On expiry, the heuristic solutions from the first pass are returned.

Usage Example

import { CodeAnalyzer } from './analyzer/code-analyzer';

// Create analyzer
const analyzer = new CodeAnalyzer({
  tier: 'pro',
  userId: 'user-123',
  usageToday: 5,
  usageThisMonth: 45
});

// Analyze code with solutions
const results = await analyzer.analyzeCode(
  `
  async function processOrders() {
    const orders = await Order.findAll();
    for (const order of orders) {
      const user = await User.findByPk(order.userId);
      console.log(user.name);
    }
  }
  `,
  'orders.ts',
  true  // Generate solutions
);

// Process results
for (const result of results) {
  console.log(`Detector: ${result.detectorName}`);
  for (const issue of result.issues) {
    console.log(`  - ${issue.title} (${issue.severity})`);
    if (issue.solutions) {
      for (const solution of issue.solutions) {
        console.log(`    Solution: ${solution.type} (fitness: ${solution.fitnessScore})`);
      }
    }
  }
}

CLI Usage

Everything above describes the backend engine, which powers web and API scans. The published CLI runs a separate engine (packages/core-engine) that performs the same AST-based detection but does not generate or evolve solutions:

# Analyze the current directory
npx code-evolution-lab analyze

# Filter by severity and category
npx code-evolution-lab analyze --severity high --category loop

See CLI Commands for the full command set.

Next Steps