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:
- Regex:
await.*findByPkmatches strings, comments, and unrelated code - AST: Understands that
await User.findByPk(id)is a database call inside a loop, whileconsole.log('await User.findByPk')is just a string
Key Advantages:
- Semantic Understanding: Knows the difference between a function call and a string
- Context Awareness: Detects patterns across nested scopes and control flow
- ORM Detection: Identifies which ORM you’re using and adapts detection
- Framework Awareness: Where a detector can tell which framework a file belongs to, it records that on the issue so generators can offer the right cleanup idiom
- Fewer False Positives: Comments, strings, and unrelated identifiers cannot trigger a match the way they can with text patterns
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:
- TypeScript: Full type annotation support without needing
tsc - JSX/TSX: React component parsing
- Experimental Features: Stage 3 proposals, decorators, optional chaining
- Battle-Tested: Used by Webpack, Rollup, ESLint, Prettier
- Plugin Ecosystem: Syntax support enabled per-plugin as needed
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:
User.findAll()from Sequelize vs custom codeprisma.user.findMany()vs a variable coincidentally namedprisma- Third-party libraries vs your own code
How It Works:
- Package Detection: Scans for known ORM imports (
@prisma/client,sequelize,mongoose) - Symbol Mapping: Tracks renamed imports (
import { Prisma as DB }→DBmaps to Prisma) - 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:
- Source Code: Generate code snippets for issues
- File Path: Report accurate locations
- AST: Traverse code structure
- ORM Context: Apply ORM-specific patterns
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:
- Usage limits (daily/monthly) prevent API abuse
- Solutions capped at 3 (Free) or 5 (Pro) per issue
- Private repository analysis is Pro-only
| 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:
- User Experience: Show “Analyzing file 3/10…” instead of frozen spinner
- Evolutionary Progress: Display generation-by-generation fitness improvements
- SSE Integration: Stream progress to browser in real-time
- Debugging: Log detailed analysis stages
Event Types:
quick-solutions: Heuristic solutions ready (fast, <1s)evolution-start: Evolutionary refinement beginningevolution-progress: Per-generation updates (fitness, population)evolution-complete: Final evolved solutions ready
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:
- User Patience: >3s feels slow, >10s causes abandonment
- Cost: CPU time = money on cloud platforms
- Concurrency: Multiple users analyzing simultaneously
Optimization Strategies:
Concurrency
- Detectors are dispatched together:
Promise.allruns all 11 detectors over the same parsed AST. Detection is synchronous CPU work on a single-threaded event loop, so this is a structural convenience rather than a source of speedup — the win comes from parsing once, not from running detectors at the same time - Issue processing limited to 5 concurrent: Prevents memory exhaustion when analyzing files with 100+ issues
- Evolution has a configurable timeout: 30s per issue by default, so a slow evolution cannot stall a scan
Caching
- AST is parsed once and shared
- ORM context built once per file
- Solutions cached in database
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.