Skip to content

Latest commit

Β 

History

History
2408 lines (1930 loc) Β· 60.7 KB

File metadata and controls

2408 lines (1930 loc) Β· 60.7 KB

CodeGuard Developer Documentation

Comprehensive guide for developers working on or extending CodeGuard

Table of Contents

Architecture Overview

CodeGuard follows a modular, plugin-based architecture designed for performance, extensibility, and maintainability.

High-Level Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    VSCode Extension Host                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ Extension  β”‚  β”‚  Diagnostic  β”‚  β”‚    Providers     β”‚   β”‚
β”‚  β”‚ Entry Point│──│   Manager    │──│ (Actions/Hover)  β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚         β”‚                                    β”‚               β”‚
β”‚         β”‚ Worker Thread Communication        β”‚               β”‚
β”‚         β–Ό                                    β–Ό               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚           Analysis Engine (Worker Thread)           β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚   β”‚
β”‚  β”‚  β”‚ Security β”‚  β”‚Performanceβ”‚  β”‚  Code Smells     β”‚ β”‚   β”‚
β”‚  β”‚  β”‚ Analyzer β”‚  β”‚ Analyzer  β”‚  β”‚  Detector        β”‚ β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚   β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚   β”‚
β”‚  β”‚  β”‚  Secret  β”‚  β”‚Incrementalβ”‚  β”‚  Cache Manager   β”‚ β”‚   β”‚
β”‚  β”‚  β”‚ Scanner  β”‚  β”‚ Analyzer  β”‚  β”‚  (LRU + Disk)    β”‚ β”‚   β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                                    β”‚
         β”‚ Optional AI Integration            β”‚
         β–Ό                                    β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  AI Service  β”‚                    β”‚ Local Cache  β”‚
  β”‚ (OpenAI/     β”‚                    β”‚  (SQLite)    β”‚
  β”‚  Claude/     β”‚                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  β”‚  Ollama)     β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Core Design Principles

  1. Local-First: All static analysis runs locally without network calls
  2. Non-Blocking: Analysis runs in worker threads to keep UI responsive
  3. Incremental: Only analyze changed regions in large files
  4. Cached: Aggressive caching with content-based hashing
  5. Modular: Pluggable analyzer architecture for easy extension

Data Flow

  1. User Types β†’ VSCode fires onDidChangeTextDocument event
  2. Debouncer β†’ Waits 500ms (configurable) after typing stops
  3. Analysis Engine β†’ Receives analysis request with file content
  4. Cache Check β†’ Computes SHA-256 hash, checks memory/disk cache
  5. Parallel Analysis β†’ Executes all enabled analyzers concurrently
  6. Result Aggregation β†’ Collects diagnostics from all analyzers
  7. Diagnostic Display β†’ Updates VSCode diagnostic collection
  8. UI Update β†’ Status bar and hover providers reflect new state

Threading Model

Main Thread (VSCode UI)
β”œβ”€β”€ Extension activation
β”œβ”€β”€ Event listeners (document changes)
β”œβ”€β”€ Diagnostic display
β”œβ”€β”€ Code action handling
└── Worker thread communication

Worker Thread (Analysis)
β”œβ”€β”€ Analysis engine coordination
β”œβ”€β”€ All analyzer execution
β”œβ”€β”€ AST parsing
β”œβ”€β”€ Pattern matching
└── Cache management

Core Components

Extension Entry Point (src/extension.ts)

The main entry point that initializes all components and registers VSCode providers.

Key Responsibilities:

  • Extension activation and deactivation
  • Document change listener registration with debouncing
  • Diagnostic collection initialization
  • Configuration loading
  • Worker thread initialization

Important Functions:

export function activate(context: vscode.ExtensionContext): void
export function deactivate(): Promise<void>

Activation Events:

  • onLanguage:javascript
  • onLanguage:typescript
  • onLanguage:python
  • onLanguage:java

Analysis Engine (src/engine/AnalysisEngine.ts)

Coordinates analyzer execution, manages caching, and handles timeouts.

Key Responsibilities:

  • Parallel analyzer execution using Promise.all
  • Content hash computation (SHA-256) for cache keys
  • Timeout handling (5 second limit per analyzer)
  • Result aggregation from multiple analyzers
  • Cache integration

Core Interface:

interface AnalysisEngine {
  analyze(request: AnalysisRequest): Promise<AnalysisResult>;
  analyzeIncremental(request: IncrementalAnalysisRequest): Promise<AnalysisResult>;
  invalidateCache(fileUri: string): void;
}

interface AnalysisRequest {
  fileUri: string;
  content: string;
  languageId: string;
  version: number;
}

interface AnalysisResult {
  diagnostics: Diagnostic[];
  analysisTimeMs: number;
  cacheHit: boolean;
  analyzersRun: string[];
}

Performance Characteristics:

  • <100ms for files under 1000 lines
  • Parallel execution of all analyzers
  • Automatic timeout after 5 seconds
  • Cache hit returns results in <10ms

Analyzer Base Interface (src/analyzers/Analyzer.ts)

Defines the contract that all analyzers must implement.

Interface Definition:

interface Analyzer {
  readonly name: string;
  readonly supportedLanguages: string[];
  
  analyze(context: AnalysisContext): Promise<Diagnostic[]>;
  supportsIncremental(): boolean;
  analyzeIncremental?(context: IncrementalAnalysisContext): Promise<Diagnostic[]>;
}

interface AnalysisContext {
  fileUri: string;
  content: string;
  languageId: string;
  syntaxTree?: SyntaxTree;
  config: AnalyzerConfig;
}

Implementation Requirements:

  1. Must return diagnostics array (can be empty)
  2. Must handle errors gracefully (catch and log)
  3. Should complete within 5 seconds
  4. Should support incremental analysis for large files
  5. Must respect configuration settings

Cache Manager (src/cache/CacheManager.ts)

Implements two-level caching (memory + disk) with LRU eviction.

Architecture:

class TwoLevelCache implements CacheManager {
  private memoryCache: LRUCache<string, AnalysisResult>;  // 50MB default
  private diskCache: SQLiteCache;                          // 500MB default
  
  async get(key: string): Promise<AnalysisResult | null>;
  async set(key: string, value: AnalysisResult): Promise<void>;
  async invalidate(key: string): Promise<void>;
  async clear(): Promise<void>;
  getStats(): CacheStats;
}

Caching Strategy:

  1. Check memory cache first (fastest)
  2. If miss, check disk cache
  3. If disk hit, promote to memory cache
  4. On set, write to both memory and disk
  5. LRU eviction when limits exceeded

Cache Key Generation:

function computeCacheKey(content: string, languageId: string): string {
  const hash = crypto.createHash('sha256')
    .update(content)
    .update(languageId)
    .digest('hex');
  return `${languageId}:${hash}`;
}

Diagnostic Manager (src/diagnostics/DiagnosticManager.ts)

Manages VSCode diagnostic collection and updates.

Key Features:

  • Debounced diagnostic updates
  • Diagnostic persistence during analysis
  • Automatic cleanup on file close
  • Severity-based grouping

Interface:

interface DiagnosticManager {
  updateDiagnostics(uri: Uri, diagnostics: Diagnostic[]): void;
  clearDiagnostics(uri: Uri): void;
  getDiagnostics(uri: Uri): Diagnostic[];
  getAllDiagnostics(): [Uri, Diagnostic[]][];
}

Code Action Provider (src/providers/CodeActionProvider.ts)

Provides quick fixes and actions for diagnostics.

Available Actions:

  1. Fix with AI: AI-powered fix suggestions (when enabled)
  2. Ignore Issue: Add to .codeguardignore file
  3. Explain Issue: Show detailed documentation

Implementation:

class CodeGuardActionProvider implements vscode.CodeActionProvider {
  provideCodeActions(
    document: TextDocument,
    range: Range,
    context: CodeActionContext
  ): Promise<CodeAction[]>;
}

AI Service (src/ai/AIService.ts)

Abstracts AI provider integration for fix suggestions.

Supported Providers:

  • OpenAI (GPT-4, GPT-3.5)
  • Anthropic Claude (Opus, Sonnet, Haiku)
  • Ollama (local models)

Architecture:

interface AIService {
  generateFix(request: FixRequest): Promise<FixSuggestion>;
  explainIssue(diagnostic: Diagnostic, context: string): Promise<string>;
  isAvailable(): boolean;
}

interface AIClient {
  complete(prompt: string, options: CompletionOptions): Promise<string>;
  getRateLimitInfo(): RateLimitInfo;
}

Privacy Features:

  • Only sends minimal context (snippet + 10 lines)
  • Never sends entire files
  • Caches responses to reduce API calls
  • Optional - can be completely disabled

Error Handling:

  • Timeout after 3 seconds
  • Fallback to rule-based fixes
  • Rate limit handling with exponential backoff
  • Graceful degradation on network errors

API Reference

Analyzer API

Creating a Custom Analyzer

import { Analyzer, AnalysisContext, Diagnostic } from '../types';
import * as vscode from 'vscode';

export class MyCustomAnalyzer implements Analyzer {
  readonly name = 'my-custom-analyzer';
  readonly supportedLanguages = ['javascript', 'typescript'];

  async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
    const diagnostics: Diagnostic[] = [];
    
    // Your analysis logic here
    const issues = this.detectIssues(context.content);
    
    for (const issue of issues) {
      diagnostics.push({
        range: new vscode.Range(
          issue.line, issue.column,
          issue.line, issue.column + issue.length
        ),
        severity: vscode.DiagnosticSeverity.Warning,
        message: issue.message,
        source: 'codeguard',
        code: 'my-rule-id'
      });
    }
    
    return diagnostics;
  }

  supportsIncremental(): boolean {
    return false;
  }

  private detectIssues(content: string): Issue[] {
    // Implementation
    return [];
  }
}

Registering an Analyzer

Add your analyzer to the analysis engine:

// In src/engine/AnalysisEngine.ts
import { MyCustomAnalyzer } from '../analyzers/MyCustomAnalyzer';

constructor() {
  this.analyzers = [
    new SecurityAnalyzer(),
    new PerformanceAnalyzer(),
    new SecretScanner(),
    new CodeSmellDetector(),
    new MyCustomAnalyzer()  // Add your analyzer
  ];
}

Analyzer Configuration

Add configuration options in package.json:

{
  "contributes": {
    "configuration": {
      "properties": {
        "codeguard.analyzers.myCustomAnalyzer.enabled": {
          "type": "boolean",
          "default": true,
          "description": "Enable my custom analyzer"
        },
        "codeguard.analyzers.myCustomAnalyzer.severity": {
          "type": "object",
          "default": {
            "my-rule-id": "warning"
          },
          "description": "Severity levels for custom analyzer rules"
        }
      }
    }
  }
}

Configuration API

Reading Configuration

import { ConfigurationManager } from './config/ConfigurationManager';

const configManager = new ConfigurationManager();
const config = configManager.getConfig();

// Access analyzer settings
if (config.analyzers.security.enabled) {
  // Run security analysis
}

// Access custom rules
const maxComplexity = config.analyzers.codeSmells.customRules?.maxComplexity || 10;

Watching Configuration Changes

const disposable = configManager.watchConfig((newConfig) => {
  console.log('Configuration changed:', newConfig);
  // React to configuration changes
});

// Clean up when done
disposable.dispose();

Cache API

Using the Cache

import { CacheManager } from './cache/CacheManager';

const cache = new CacheManager({
  maxMemoryMB: 50,
  maxDiskMB: 500,
  ttlSeconds: 3600
});

// Get cached result
const cached = await cache.get(cacheKey);
if (cached) {
  return cached;
}

// Perform analysis
const result = await performAnalysis();

// Store in cache
await cache.set(cacheKey, result);

// Invalidate cache entry
await cache.invalidate(cacheKey);

// Get cache statistics
const stats = cache.getStats();
console.log(`Hit rate: ${stats.hitRate}%`);

Custom Cache Key Generation

import * as crypto from 'crypto';

function computeCacheKey(
  content: string,
  languageId: string,
  analyzerName: string
): string {
  const hash = crypto.createHash('sha256')
    .update(content)
    .update(languageId)
    .update(analyzerName)
    .digest('hex');
  return `${analyzerName}:${languageId}:${hash}`;
}

AI Service API

Generating Fix Suggestions

import { AIService } from './ai/AIService';

const aiService = new AIService({
  provider: 'openai',
  apiKey: 'sk-...',
  model: 'gpt-4'
});

// Generate fix
const fix = await aiService.generateFix({
  diagnostic: diagnostic,
  codeSnippet: problematicCode,
  contextBefore: beforeCode,
  contextAfter: afterCode,
  languageId: 'typescript'
});

console.log('Fixed code:', fix.fixedCode);
console.log('Explanation:', fix.explanation);
console.log('Confidence:', fix.confidence);

Adding a Custom AI Provider

import { AIClient, CompletionOptions } from './ai/AIService';

export class MyAIClient implements AIClient {
  constructor(private apiKey: string, private model: string) {}

  async complete(prompt: string, options: CompletionOptions): Promise<string> {
    const response = await fetch('https://api.myai.com/v1/completions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        model: this.model,
        prompt,
        max_tokens: options.maxTokens,
        temperature: options.temperature
      })
    });

    const data = await response.json();
    return data.choices[0].text;
  }

  getRateLimitInfo(): RateLimitInfo {
    return {
      remaining: 100,
      reset: Date.now() + 3600000
    };
  }
}

Creating Custom Analyzers

Step-by-Step Guide

1. Define Your Analyzer

Create a new file src/analyzers/MyAnalyzer.ts:

import { Analyzer, AnalysisContext, Diagnostic } from '../types';
import * as vscode from 'vscode';

export class MyAnalyzer implements Analyzer {
  readonly name = 'my-analyzer';
  readonly supportedLanguages = ['javascript', 'typescript'];

  async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
    const diagnostics: Diagnostic[] = [];
    
    // Parse the code
    const lines = context.content.split('\n');
    
    // Analyze each line
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];
      
      // Check for issues
      if (this.hasIssue(line)) {
        diagnostics.push(this.createDiagnostic(i, line));
      }
    }
    
    return diagnostics;
  }

  supportsIncremental(): boolean {
    return false;
  }

  private hasIssue(line: string): boolean {
    // Your detection logic
    return line.includes('problematic-pattern');
  }

  private createDiagnostic(lineNumber: number, line: string): Diagnostic {
    const startColumn = line.indexOf('problematic-pattern');
    const endColumn = startColumn + 'problematic-pattern'.length;
    
    return {
      range: new vscode.Range(lineNumber, startColumn, lineNumber, endColumn),
      severity: vscode.DiagnosticSeverity.Warning,
      message: 'Problematic pattern detected',
      source: 'codeguard',
      code: 'my-rule-id',
      relatedInformation: [
        {
          location: new vscode.Location(
            vscode.Uri.file(context.fileUri),
            new vscode.Range(lineNumber, startColumn, lineNumber, endColumn)
          ),
          message: 'Consider using alternative pattern'
        }
      ]
    };
  }
}

2. Add Pattern Matching

For more sophisticated analysis, use regex patterns:

export class PatternBasedAnalyzer implements Analyzer {
  private patterns: Pattern[] = [
    {
      id: 'unsafe-eval',
      regex: /\beval\s*\(/g,
      severity: vscode.DiagnosticSeverity.Error,
      message: 'Avoid using eval() - it can execute arbitrary code'
    },
    {
      id: 'console-log',
      regex: /console\.log\(/g,
      severity: vscode.DiagnosticSeverity.Information,
      message: 'Remove console.log before production'
    }
  ];

  async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
    const diagnostics: Diagnostic[] = [];
    
    for (const pattern of this.patterns) {
      const matches = context.content.matchAll(pattern.regex);
      
      for (const match of matches) {
        const position = this.getPosition(context.content, match.index!);
        diagnostics.push({
          range: new vscode.Range(
            position.line,
            position.character,
            position.line,
            position.character + match[0].length
          ),
          severity: pattern.severity,
          message: pattern.message,
          source: 'codeguard',
          code: pattern.id
        });
      }
    }
    
    return diagnostics;
  }

  private getPosition(content: string, index: number): vscode.Position {
    const lines = content.substring(0, index).split('\n');
    const line = lines.length - 1;
    const character = lines[lines.length - 1].length;
    return new vscode.Position(line, character);
  }
}

3. Add AST-Based Analysis

For deeper analysis, parse the code into an AST:

import * as parser from '@babel/parser';
import traverse from '@babel/traverse';

export class ASTBasedAnalyzer implements Analyzer {
  async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
    const diagnostics: Diagnostic[] = [];
    
    try {
      // Parse code to AST
      const ast = parser.parse(context.content, {
        sourceType: 'module',
        plugins: ['typescript', 'jsx']
      });
      
      // Traverse AST
      traverse(ast, {
        FunctionDeclaration: (path) => {
          // Check function length
          const loc = path.node.loc;
          if (loc) {
            const lines = loc.end.line - loc.start.line;
            if (lines > 50) {
              diagnostics.push({
                range: new vscode.Range(
                  loc.start.line - 1,
                  loc.start.column,
                  loc.end.line - 1,
                  loc.end.column
                ),
                severity: vscode.DiagnosticSeverity.Information,
                message: `Function is ${lines} lines long (max: 50)`,
                source: 'codeguard',
                code: 'long-function'
              });
            }
          }
        },
        
        CallExpression: (path) => {
          // Check for dangerous function calls
          if (path.node.callee.type === 'Identifier' &&
              path.node.callee.name === 'eval') {
            const loc = path.node.loc;
            if (loc) {
              diagnostics.push({
                range: new vscode.Range(
                  loc.start.line - 1,
                  loc.start.column,
                  loc.end.line - 1,
                  loc.end.column
                ),
                severity: vscode.DiagnosticSeverity.Error,
                message: 'Avoid using eval() - security risk',
                source: 'codeguard',
                code: 'unsafe-eval'
              });
            }
          }
        }
      });
    } catch (error) {
      // Handle parse errors gracefully
      console.error('Parse error:', error);
    }
    
    return diagnostics;
  }
}

4. Implement Incremental Analysis

For large files, support incremental analysis:

export class IncrementalAnalyzer implements Analyzer {
  private syntaxTreeCache = new Map<string, SyntaxTree>();

  supportsIncremental(): boolean {
    return true;
  }

  async analyzeIncremental(
    context: IncrementalAnalysisContext
  ): Promise<Diagnostic[]> {
    // Get cached syntax tree
    let syntaxTree = this.syntaxTreeCache.get(context.fileUri);
    
    if (!syntaxTree) {
      // First analysis - parse entire file
      syntaxTree = this.parseFile(context.content);
      this.syntaxTreeCache.set(context.fileUri, syntaxTree);
      return this.analyze(context);
    }
    
    // Incremental analysis - only analyze affected regions
    const affectedNodes = this.getAffectedNodes(
      syntaxTree,
      context.affectedRanges
    );
    
    const diagnostics: Diagnostic[] = [];
    for (const node of affectedNodes) {
      diagnostics.push(...this.analyzeNode(node));
    }
    
    return diagnostics;
  }

  private getAffectedNodes(
    tree: SyntaxTree,
    ranges: vscode.Range[]
  ): ASTNode[] {
    const affected: ASTNode[] = [];
    
    for (const range of ranges) {
      // Find nodes that overlap with changed ranges
      const nodes = this.findNodesInRange(tree.root, range);
      affected.push(...nodes);
    }
    
    return affected;
  }
}

5. Add Configuration Support

Make your analyzer configurable:

export class ConfigurableAnalyzer implements Analyzer {
  private config: AnalyzerConfig;

  constructor(config: AnalyzerConfig) {
    this.config = config;
  }

  async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
    // Check if analyzer is enabled
    if (!this.config.enabled) {
      return [];
    }
    
    const diagnostics: Diagnostic[] = [];
    
    // Use custom rules from config
    const maxLength = this.config.customRules?.maxLength || 50;
    const severity = this.config.severity['long-function'] || 
                     vscode.DiagnosticSeverity.Information;
    
    // Analyze with configured settings
    const issues = this.detectIssues(context.content, maxLength);
    
    for (const issue of issues) {
      diagnostics.push({
        ...issue,
        severity: severity
      });
    }
    
    return diagnostics;
  }
}

6. Write Tests

Create comprehensive tests for your analyzer:

// src/analyzers/MyAnalyzer.test.ts
import { describe, it, expect } from 'vitest';
import { MyAnalyzer } from './MyAnalyzer';

describe('MyAnalyzer', () => {
  const analyzer = new MyAnalyzer();

  describe('Pattern Detection', () => {
    it('should detect problematic pattern', async () => {
      const code = 'const x = problematic-pattern();';
      const diagnostics = await analyzer.analyze({
        fileUri: 'test.js',
        content: code,
        languageId: 'javascript',
        config: { enabled: true, severity: {} }
      });
      
      expect(diagnostics).toHaveLength(1);
      expect(diagnostics[0].code).toBe('my-rule-id');
      expect(diagnostics[0].severity).toBe(vscode.DiagnosticSeverity.Warning);
    });

    it('should not flag safe patterns', async () => {
      const code = 'const x = safePattern();';
      const diagnostics = await analyzer.analyze({
        fileUri: 'test.js',
        content: code,
        languageId: 'javascript',
        config: { enabled: true, severity: {} }
      });
      
      expect(diagnostics).toHaveLength(0);
    });
  });

  describe('Edge Cases', () => {
    it('should handle empty input', async () => {
      const diagnostics = await analyzer.analyze({
        fileUri: 'test.js',
        content: '',
        languageId: 'javascript',
        config: { enabled: true, severity: {} }
      });
      
      expect(diagnostics).toHaveLength(0);
    });

    it('should handle malformed code gracefully', async () => {
      const code = 'const x = {{{';
      const diagnostics = await analyzer.analyze({
        fileUri: 'test.js',
        content: code,
        languageId: 'javascript',
        config: { enabled: true, severity: {} }
      });
      
      // Should not throw, may return empty or partial results
      expect(Array.isArray(diagnostics)).toBe(true);
    });
  });
});

Best Practices for Analyzer Development

Performance

  1. Keep analysis fast: Target <100ms for typical files
  2. Use incremental analysis: For files >5000 lines
  3. Cache expensive operations: AST parsing, regex compilation
  4. Avoid blocking operations: Use async/await
  5. Limit regex backtracking: Use atomic groups, possessive quantifiers
// Good: Fast regex with atomic group
const pattern = /(?:const|let|var)\s+(\w+)/g;

// Bad: Slow regex with backtracking
const pattern = /(const|let|var)\s+(\w+)*/g;

Accuracy

  1. Minimize false positives: Use data flow analysis when possible
  2. Provide clear messages: Explain what's wrong and why
  3. Include fix suggestions: Help users resolve issues
  4. Test thoroughly: Unit tests + property-based tests
  5. Handle edge cases: Empty files, malformed code, comments
// Good: Clear, actionable message
message: 'SQL injection risk: Use parameterized queries instead of string concatenation'

// Bad: Vague message
message: 'Security issue detected'

Maintainability

  1. Document patterns: Explain what each pattern detects
  2. Use descriptive names: For rules, functions, variables
  3. Separate concerns: Detection logic vs diagnostic creation
  4. Make it configurable: Allow users to customize behavior
  5. Version your rules: Track changes to detection logic
/**
 * Detects SQL injection vulnerabilities by identifying string concatenation
 * in SQL query contexts. Uses data flow analysis to track user input.
 * 
 * @example
 * // Detected:
 * const query = "SELECT * FROM users WHERE id = " + userId;
 * 
 * // Safe:
 * const query = db.prepare("SELECT * FROM users WHERE id = ?").bind(userId);
 */
private detectSQLInjection(ast: AST): Diagnostic[] {
  // Implementation
}

Performance Optimization

Profiling and Benchmarking

Running Benchmarks

# Run all benchmarks
npm run benchmark

# Run specific benchmark
npm run benchmark -- --grep "Cache performance"

# Run with profiling
node --prof dist/benchmarks/performance.bench.js

Creating Benchmarks

// src/benchmarks/my-feature.bench.ts
import { describe, bench } from 'vitest';
import { MyAnalyzer } from '../analyzers/MyAnalyzer';

describe('MyAnalyzer Performance', () => {
  const analyzer = new MyAnalyzer();
  const smallFile = generateCode(100);   // 100 lines
  const mediumFile = generateCode(500);  // 500 lines
  const largeFile = generateCode(1000);  // 1000 lines

  bench('analyze small file (100 lines)', async () => {
    await analyzer.analyze({
      fileUri: 'test.js',
      content: smallFile,
      languageId: 'javascript'
    });
  });

  bench('analyze medium file (500 lines)', async () => {
    await analyzer.analyze({
      fileUri: 'test.js',
      content: mediumFile,
      languageId: 'javascript'
    });
  });

  bench('analyze large file (1000 lines)', async () => {
    await analyzer.analyze({
      fileUri: 'test.js',
      content: largeFile,
      languageId: 'javascript'
    });
  });
});

Optimization Techniques

1. Caching

Cache expensive computations:

export class OptimizedAnalyzer implements Analyzer {
  private regexCache = new Map<string, RegExp>();
  private astCache = new Map<string, AST>();

  private getRegex(pattern: string): RegExp {
    if (!this.regexCache.has(pattern)) {
      this.regexCache.set(pattern, new RegExp(pattern, 'g'));
    }
    return this.regexCache.get(pattern)!;
  }

  private getAST(content: string, hash: string): AST {
    if (!this.astCache.has(hash)) {
      this.astCache.set(hash, this.parseCode(content));
    }
    return this.astCache.get(hash)!;
  }
}

2. Lazy Evaluation

Defer expensive operations until needed:

export class LazyAnalyzer implements Analyzer {
  private _ast?: AST;

  private get ast(): AST {
    if (!this._ast) {
      this._ast = this.parseCode(this.content);
    }
    return this._ast;
  }

  async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
    // Only parse if we need AST-based analysis
    if (this.needsAST(context)) {
      return this.analyzeWithAST(this.ast);
    }
    return this.analyzeWithRegex(context.content);
  }
}

3. Parallel Processing

Process independent tasks concurrently:

export class ParallelAnalyzer implements Analyzer {
  async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
    // Run multiple checks in parallel
    const [
      securityIssues,
      performanceIssues,
      styleIssues
    ] = await Promise.all([
      this.checkSecurity(context),
      this.checkPerformance(context),
      this.checkStyle(context)
    ]);

    return [
      ...securityIssues,
      ...performanceIssues,
      ...styleIssues
    ];
  }
}

4. Early Exit

Stop processing when possible:

export class EarlyExitAnalyzer implements Analyzer {
  async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
    // Check if file is too large
    if (context.content.length > 1000000) {
      return [{
        range: new vscode.Range(0, 0, 0, 0),
        severity: vscode.DiagnosticSeverity.Information,
        message: 'File too large for analysis',
        source: 'codeguard',
        code: 'file-too-large'
      }];
    }

    // Check if language is supported
    if (!this.supportedLanguages.includes(context.languageId)) {
      return [];
    }

    // Proceed with analysis
    return this.performAnalysis(context);
  }
}

5. Incremental Updates

Only reprocess changed regions:

export class IncrementalOptimizer implements Analyzer {
  private previousResults = new Map<string, Diagnostic[]>();

  async analyzeIncremental(
    context: IncrementalAnalysisContext
  ): Promise<Diagnostic[]> {
    const previous = this.previousResults.get(context.fileUri) || [];
    
    // Filter out diagnostics in affected ranges
    const unaffected = previous.filter(d => 
      !context.affectedRanges.some(range => range.contains(d.range))
    );
    
    // Analyze only affected ranges
    const newDiagnostics = await this.analyzeRanges(
      context,
      context.affectedRanges
    );
    
    // Merge results
    const merged = [...unaffected, ...newDiagnostics];
    this.previousResults.set(context.fileUri, merged);
    
    return merged;
  }
}

Memory Optimization

1. Limit Cache Size

export class MemoryEfficientCache {
  private cache = new LRUCache<string, AnalysisResult>({
    max: 100,  // Maximum entries
    maxSize: 50 * 1024 * 1024,  // 50MB
    sizeCalculation: (value) => {
      return JSON.stringify(value).length;
    },
    dispose: (value, key) => {
      console.log(`Evicting cache entry: ${key}`);
    }
  });
}

2. Clean Up Resources

export class ResourceAwareAnalyzer implements Analyzer {
  private workers: Worker[] = [];

  async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
    const worker = new Worker('./analyzer-worker.js');
    this.workers.push(worker);
    
    try {
      return await this.runInWorker(worker, context);
    } finally {
      // Clean up worker
      worker.terminate();
      this.workers = this.workers.filter(w => w !== worker);
    }
  }

  dispose(): void {
    // Terminate all workers
    for (const worker of this.workers) {
      worker.terminate();
    }
    this.workers = [];
  }
}

Performance Targets

Metric Target Measurement
Analysis time (< 1000 lines) < 100ms 95th percentile
Analysis time (1000-5000 lines) < 500ms 95th percentile
Analysis time (> 5000 lines) < 1000ms 95th percentile (incremental)
Cache hit latency < 10ms Average
Memory usage (idle) < 5MB Maximum
Memory usage (active) < 100MB 95th percentile
Extension activation < 500ms Maximum

Profiling Tools

VSCode Performance Profiler

# Start VSCode with profiling
code --prof-startup

# Analyze profile
node --prof-process isolate-*.log > profile.txt

Chrome DevTools

  1. Open VSCode
  2. Help β†’ Toggle Developer Tools
  3. Performance tab β†’ Record
  4. Trigger analysis
  5. Stop recording and analyze

Node.js Profiler

# Run with profiler
node --prof dist/extension.js

# Generate report
node --prof-process isolate-*.log > profile.txt

Testing Strategy

Test Types

1. Unit Tests

Test individual functions and components:

describe('SecurityAnalyzer', () => {
  it('should detect SQL injection', () => {
    const code = 'const query = "SELECT * FROM users WHERE id = " + userId;';
    const diagnostics = analyzer.analyze({ content: code });
    expect(diagnostics).toHaveLength(1);
    expect(diagnostics[0].code).toBe('sql-injection');
  });
});

2. Property-Based Tests

Test universal properties with randomized inputs:

import fc from 'fast-check';

// Feature: codeguard, Property 17: Cache Round-Trip Consistency
it('Property 17: cached results match fresh analysis', async () => {
  await fc.assert(
    fc.asyncProperty(
      fc.string({ minLength: 10, maxLength: 1000 }),
      async (content) => {
        const result1 = await engine.analyze({ content });
        const result2 = await engine.analyze({ content });
        
        expect(result2.cacheHit).toBe(true);
        expect(result2.diagnostics).toEqual(result1.diagnostics);
      }
    ),
    { numRuns: 100 }
  );
});

3. Integration Tests

Test component interactions:

describe('Analysis Engine Integration', () => {
  it('should coordinate multiple analyzers', async () => {
    const engine = new AnalysisEngine();
    const result = await engine.analyze({
      fileUri: 'test.js',
      content: 'const x = eval("code");',
      languageId: 'javascript'
    });
    
    expect(result.analyzersRun).toContain('security');
    expect(result.diagnostics.length).toBeGreaterThan(0);
  });
});

4. End-to-End Tests

Test complete user workflows:

describe('E2E: User Workflow', () => {
  it('should analyze file on open', async () => {
    // Open file
    const doc = await vscode.workspace.openTextDocument({
      content: 'const x = eval("code");',
      language: 'javascript'
    });
    
    // Wait for analysis
    await waitForDiagnostics(doc.uri);
    
    // Verify diagnostics
    const diagnostics = vscode.languages.getDiagnostics(doc.uri);
    expect(diagnostics.length).toBeGreaterThan(0);
  });
});

Test Coverage Goals

  • Unit tests: >80% code coverage
  • Property tests: All 39 design properties
  • Integration tests: All component interactions
  • E2E tests: All major user workflows

Running Tests

# All tests
npm test

# Unit tests only
npm test -- --grep -v "Property"

# Property tests only
npm test -- --grep "Property"

# With coverage
npm run test:coverage

# Watch mode
npm test -- --watch

# Specific file
npm test -- src/analyzers/SecurityAnalyzer.test.ts

Debugging Guide

VSCode Debugging

Launch Configuration

.vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Run Extension",
      "type": "extensionHost",
      "request": "launch",
      "args": [
        "--extensionDevelopmentPath=${workspaceFolder}"
      ],
      "outFiles": [
        "${workspaceFolder}/dist/**/*.js"
      ],
      "preLaunchTask": "npm: compile"
    },
    {
      "name": "Extension Tests",
      "type": "extensionHost",
      "request": "launch",
      "args": [
        "--extensionDevelopmentPath=${workspaceFolder}",
        "--extensionTestsPath=${workspaceFolder}/dist/test"
      ],
      "outFiles": [
        "${workspaceFolder}/dist/**/*.js"
      ],
      "preLaunchTask": "npm: compile"
    }
  ]
}

Debugging Steps

  1. Set breakpoints: Click in gutter or press F9
  2. Start debugging: Press F5
  3. Extension Development Host opens: New VSCode window
  4. Trigger functionality: Open file, type code, etc.
  5. Debugger pauses: At breakpoints
  6. Inspect variables: Hover or use Debug panel
  7. Step through code: F10 (step over), F11 (step into)

Debug Console

Use the Debug Console for interactive debugging:

// In your code
debugger;  // Pauses execution

// In Debug Console
> context.content
> diagnostics.length
> JSON.stringify(result, null, 2)

Logging

Output Channel

import * as vscode from 'vscode';

const outputChannel = vscode.window.createOutputChannel('CodeGuard');

// Log messages
outputChannel.appendLine('[INFO] Analysis started');
outputChannel.appendLine(`[DEBUG] Found ${diagnostics.length} issues`);
outputChannel.appendLine('[ERROR] Analysis failed: ' + error.message);

// Show output panel
outputChannel.show();

Structured Logging

class Logger {
  private output: vscode.OutputChannel;
  private logLevel: 'error' | 'warn' | 'info' | 'debug';

  constructor(name: string, level: string = 'info') {
    this.output = vscode.window.createOutputChannel(name);
    this.logLevel = level as any;
  }

  error(message: string, ...args: any[]): void {
    this.log('ERROR', message, args);
  }

  warn(message: string, ...args: any[]): void {
    if (this.shouldLog('warn')) {
      this.log('WARN', message, args);
    }
  }

  info(message: string, ...args: any[]): void {
    if (this.shouldLog('info')) {
      this.log('INFO', message, args);
    }
  }

  debug(message: string, ...args: any[]): void {
    if (this.shouldLog('debug')) {
      this.log('DEBUG', message, args);
    }
  }

  private log(level: string, message: string, args: any[]): void {
    const timestamp = new Date().toISOString();
    const formatted = `[${timestamp}] [${level}] ${message}`;
    this.output.appendLine(formatted);
    
    if (args.length > 0) {
      this.output.appendLine(JSON.stringify(args, null, 2));
    }
  }

  private shouldLog(level: string): boolean {
    const levels = ['error', 'warn', 'info', 'debug'];
    return levels.indexOf(level) <= levels.indexOf(this.logLevel);
  }
}

// Usage
const logger = new Logger('CodeGuard', 'debug');
logger.info('Analysis started', { fileUri, languageId });
logger.debug('Cache hit', { key, hitRate });
logger.error('Analysis failed', { error: error.message });

Common Issues

Issue: Extension not activating

Symptoms: Extension doesn't load, no diagnostics appear

Debug steps:

  1. Check Output panel (View β†’ Output β†’ Extension Host)
  2. Verify activation events in package.json
  3. Check for compilation errors: npm run compile
  4. Verify main field in package.json points to dist/extension.js

Solution:

# Clean and rebuild
rm -rf dist/
npm run compile

# Check for errors
npm run lint

Issue: Diagnostics not appearing

Symptoms: Code has issues but no squiggly lines appear

Debug steps:

  1. Check if analyzer is enabled in settings
  2. Verify file language is supported
  3. Check Output panel for errors
  4. Add logging to analyzer:
    console.log('[Analyzer] Running:', this.name);
    console.log('[Analyzer] Found issues:', diagnostics.length);

Solution:

// Verify analyzer is registered
const analyzers = engine.getAnalyzers();
console.log('Registered analyzers:', analyzers.map(a => a.name));

// Check if diagnostics are created
const diagnostics = await analyzer.analyze(context);
console.log('Diagnostics:', diagnostics);

Issue: Slow performance

Symptoms: Analysis takes >1 second, UI feels sluggish

Debug steps:

  1. Run performance benchmarks: npm run benchmark
  2. Profile with Chrome DevTools
  3. Check cache hit rate:
    const stats = cache.getStats();
    console.log('Cache hit rate:', stats.hitRate);
  4. Measure analyzer execution time:
    const start = Date.now();
    const result = await analyzer.analyze(context);
    console.log('Analysis time:', Date.now() - start, 'ms');

Solution:

  • Enable caching
  • Implement incremental analysis
  • Optimize regex patterns
  • Use worker threads

Issue: Memory leaks

Symptoms: Memory usage grows over time, VSCode becomes slow

Debug steps:

  1. Take heap snapshots in Chrome DevTools
  2. Check for unclosed resources:
    // Bad: Worker not terminated
    const worker = new Worker('./worker.js');
    
    // Good: Worker cleaned up
    try {
      await runInWorker(worker);
    } finally {
      worker.terminate();
    }
  3. Check cache size:
    const stats = cache.getStats();
    console.log('Cache size:', stats.size, 'MB');

Solution:

  • Implement proper cleanup in dispose()
  • Limit cache size
  • Terminate workers after use
  • Clear event listeners

Extension Points

Adding New Language Support

1. Update Supported Languages

// src/analyzers/MyAnalyzer.ts
export class MyAnalyzer implements Analyzer {
  readonly supportedLanguages = [
    'javascript',
    'typescript',
    'python',
    'java',
    'go'  // Add new language
  ];
}

2. Add Language-Specific Patterns

export class LanguageAwareAnalyzer implements Analyzer {
  private patterns: Record<string, Pattern[]> = {
    javascript: [
      { regex: /eval\(/g, message: 'Avoid eval()' }
    ],
    python: [
      { regex: /exec\(/g, message: 'Avoid exec()' }
    ],
    go: [
      { regex: /unsafe\./g, message: 'Avoid unsafe package' }
    ]
  };

  async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
    const patterns = this.patterns[context.languageId] || [];
    return this.analyzeWithPatterns(context.content, patterns);
  }
}

3. Update Activation Events

// package.json
{
  "activationEvents": [
    "onLanguage:javascript",
    "onLanguage:typescript",
    "onLanguage:python",
    "onLanguage:java",
    "onLanguage:go"
  ]
}

Adding New Code Actions

1. Create Action Provider

export class CustomActionProvider implements vscode.CodeActionProvider {
  provideCodeActions(
    document: vscode.TextDocument,
    range: vscode.Range,
    context: vscode.CodeActionContext
  ): vscode.CodeAction[] {
    const actions: vscode.CodeAction[] = [];
    
    for (const diagnostic of context.diagnostics) {
      if (diagnostic.source !== 'codeguard') continue;
      
      // Add custom action
      const action = new vscode.CodeAction(
        'My Custom Fix',
        vscode.CodeActionKind.QuickFix
      );
      action.diagnostics = [diagnostic];
      action.command = {
        command: 'codeguard.myCustomFix',
        title: 'My Custom Fix',
        arguments: [document, diagnostic]
      };
      actions.push(action);
    }
    
    return actions;
  }
}

2. Register Action Provider

// src/extension.ts
export function activate(context: vscode.ExtensionContext): void {
  const actionProvider = new CustomActionProvider();
  
  context.subscriptions.push(
    vscode.languages.registerCodeActionsProvider(
      ['javascript', 'typescript'],
      actionProvider,
      {
        providedCodeActionKinds: [vscode.CodeActionKind.QuickFix]
      }
    )
  );
}

3. Implement Command

// src/extension.ts
context.subscriptions.push(
  vscode.commands.registerCommand(
    'codeguard.myCustomFix',
    async (document: vscode.TextDocument, diagnostic: vscode.Diagnostic) => {
      const edit = new vscode.WorkspaceEdit();
      
      // Create fix
      const fixedText = await generateFix(document, diagnostic);
      edit.replace(document.uri, diagnostic.range, fixedText);
      
      // Apply fix
      await vscode.workspace.applyEdit(edit);
      
      // Show message
      vscode.window.showInformationMessage('Fix applied!');
    }
  )
);

Adding Configuration Options

1. Define in package.json

{
  "contributes": {
    "configuration": {
      "title": "CodeGuard",
      "properties": {
        "codeguard.myFeature.enabled": {
          "type": "boolean",
          "default": true,
          "description": "Enable my custom feature"
        },
        "codeguard.myFeature.threshold": {
          "type": "number",
          "default": 10,
          "minimum": 1,
          "maximum": 100,
          "description": "Threshold for my feature"
        },
        "codeguard.myFeature.patterns": {
          "type": "array",
          "default": ["pattern1", "pattern2"],
          "description": "Custom patterns for my feature"
        }
      }
    }
  }
}

2. Read Configuration

import * as vscode from 'vscode';

function getMyFeatureConfig() {
  const config = vscode.workspace.getConfiguration('codeguard.myFeature');
  
  return {
    enabled: config.get<boolean>('enabled', true),
    threshold: config.get<number>('threshold', 10),
    patterns: config.get<string[]>('patterns', [])
  };
}

3. Watch for Changes

export function activate(context: vscode.ExtensionContext): void {
  // Initial config
  let config = getMyFeatureConfig();
  
  // Watch for changes
  context.subscriptions.push(
    vscode.workspace.onDidChangeConfiguration(e => {
      if (e.affectsConfiguration('codeguard.myFeature')) {
        config = getMyFeatureConfig();
        console.log('Configuration changed:', config);
        
        // React to changes
        updateFeature(config);
      }
    })
  );
}

Adding Custom Commands

1. Register Command

// src/extension.ts
export function activate(context: vscode.ExtensionContext): void {
  context.subscriptions.push(
    vscode.commands.registerCommand(
      'codeguard.myCustomCommand',
      async () => {
        const editor = vscode.window.activeTextEditor;
        if (!editor) {
          vscode.window.showErrorMessage('No active editor');
          return;
        }
        
        // Command logic
        const result = await performCustomAction(editor.document);
        
        // Show result
        vscode.window.showInformationMessage(
          `Custom command completed: ${result}`
        );
      }
    )
  );
}

2. Add to package.json

{
  "contributes": {
    "commands": [
      {
        "command": "codeguard.myCustomCommand",
        "title": "CodeGuard: My Custom Command",
        "category": "CodeGuard"
      }
    ],
    "keybindings": [
      {
        "command": "codeguard.myCustomCommand",
        "key": "ctrl+shift+alt+c",
        "mac": "cmd+shift+alt+c",
        "when": "editorTextFocus"
      }
    ],
    "menus": {
      "editor/context": [
        {
          "command": "codeguard.myCustomCommand",
          "when": "editorTextFocus",
          "group": "codeguard"
        }
      ]
    }
  }
}

Extending the AI Service

1. Create Custom AI Client

// src/ai/MyAIClient.ts
import { AIClient, CompletionOptions, RateLimitInfo } from './AIService';

export class MyAIClient implements AIClient {
  constructor(
    private apiKey: string,
    private model: string,
    private baseUrl: string = 'https://api.myai.com'
  ) {}

  async complete(prompt: string, options: CompletionOptions): Promise<string> {
    const response = await fetch(`${this.baseUrl}/v1/completions`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        model: this.model,
        prompt,
        max_tokens: options.maxTokens,
        temperature: options.temperature,
        stop: options.stop
      })
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`AI API error: ${response.status} ${error}`);
    }

    const data = await response.json();
    return data.choices[0].text;
  }

  getRateLimitInfo(): RateLimitInfo {
    // Parse from response headers or API
    return {
      remaining: 100,
      reset: Date.now() + 3600000,
      limit: 1000
    };
  }
}

2. Register AI Provider

// src/ai/AIService.ts
export class AIService {
  private client: AIClient;

  constructor(config: AIConfig) {
    switch (config.provider) {
      case 'openai':
        this.client = new OpenAIClient(config.apiKey, config.model);
        break;
      case 'claude':
        this.client = new ClaudeClient(config.apiKey, config.model);
        break;
      case 'myai':
        this.client = new MyAIClient(config.apiKey, config.model);
        break;
      default:
        throw new Error(`Unknown AI provider: ${config.provider}`);
    }
  }
}

3. Add Configuration

{
  "contributes": {
    "configuration": {
      "properties": {
        "codeguard.ai.provider": {
          "type": "string",
          "enum": ["openai", "claude", "ollama", "myai", "none"],
          "default": "none",
          "description": "AI service provider"
        }
      }
    }
  }
}

Advanced Topics

Worker Thread Communication

Main Thread

// src/engine/WorkerThreadManager.ts
import { Worker } from 'worker_threads';

export class WorkerThreadManager {
  private workers: Worker[] = [];
  private nextWorker = 0;

  constructor(private workerCount: number = 4) {
    for (let i = 0; i < workerCount; i++) {
      const worker = new Worker('./AnalysisWorker.js');
      this.workers.push(worker);
    }
  }

  async analyze(request: AnalysisRequest): Promise<AnalysisResult> {
    const worker = this.getNextWorker();
    
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error('Worker timeout'));
      }, 5000);

      worker.once('message', (result: AnalysisResult) => {
        clearTimeout(timeout);
        resolve(result);
      });

      worker.once('error', (error) => {
        clearTimeout(timeout);
        reject(error);
      });

      worker.postMessage(request);
    });
  }

  private getNextWorker(): Worker {
    const worker = this.workers[this.nextWorker];
    this.nextWorker = (this.nextWorker + 1) % this.workers.length;
    return worker;
  }

  dispose(): void {
    for (const worker of this.workers) {
      worker.terminate();
    }
    this.workers = [];
  }
}

Worker Thread

// src/engine/AnalysisWorker.ts
import { parentPort } from 'worker_threads';
import { AnalysisEngine } from './AnalysisEngine';

const engine = new AnalysisEngine();

parentPort?.on('message', async (request: AnalysisRequest) => {
  try {
    const result = await engine.analyze(request);
    parentPort?.postMessage(result);
  } catch (error) {
    parentPort?.postMessage({
      error: error.message,
      stack: error.stack
    });
  }
});

Data Flow Analysis

Implement taint tracking for security analysis:

export class DataFlowAnalyzer {
  private taintedVariables = new Set<string>();

  analyzeDataFlow(ast: AST): Map<string, TaintInfo> {
    const taintMap = new Map<string, TaintInfo>();
    
    // Find sources (user input)
    const sources = this.findTaintSources(ast);
    for (const source of sources) {
      this.taintedVariables.add(source.name);
      taintMap.set(source.name, {
        source: source.location,
        tainted: true
      });
    }
    
    // Track data flow
    this.trackDataFlow(ast, taintMap);
    
    return taintMap;
  }

  private findTaintSources(ast: AST): Source[] {
    const sources: Source[] = [];
    
    traverse(ast, {
      // User input from request parameters
      MemberExpression: (path) => {
        if (this.isUserInput(path.node)) {
          sources.push({
            name: this.getVariableName(path.node),
            location: path.node.loc
          });
        }
      }
    });
    
    return sources;
  }

  private trackDataFlow(ast: AST, taintMap: Map<string, TaintInfo>): void {
    traverse(ast, {
      // Track variable assignments
      VariableDeclarator: (path) => {
        if (path.node.init && this.isTainted(path.node.init, taintMap)) {
          const name = path.node.id.name;
          taintMap.set(name, {
            source: path.node.loc,
            tainted: true
          });
        }
      },
      
      // Track function calls
      CallExpression: (path) => {
        if (this.isSink(path.node)) {
          // Check if any argument is tainted
          for (const arg of path.node.arguments) {
            if (this.isTainted(arg, taintMap)) {
              // Found tainted data flowing to sink
              this.reportVulnerability(path.node, taintMap);
            }
          }
        }
      }
    });
  }

  private isTainted(node: ASTNode, taintMap: Map<string, TaintInfo>): boolean {
    if (node.type === 'Identifier') {
      return taintMap.has(node.name) && taintMap.get(node.name)!.tainted;
    }
    
    if (node.type === 'BinaryExpression') {
      return this.isTainted(node.left, taintMap) || 
             this.isTainted(node.right, taintMap);
    }
    
    return false;
  }

  private isSink(node: CallExpression): boolean {
    // Check if this is a dangerous sink (SQL, exec, etc.)
    const sinks = ['execute', 'query', 'exec', 'eval'];
    return node.callee.type === 'Identifier' && 
           sinks.includes(node.callee.name);
  }
}

Entropy Calculation

Implement Shannon entropy for secret detection:

export class EntropyCalculator {
  /**
   * Calculate Shannon entropy of a string.
   * Returns value between 0 (no randomness) and 8 (maximum randomness for bytes).
   * 
   * @param str - String to analyze
   * @returns Entropy value (0-8)
   */
  static calculate(str: string): number {
    if (str.length === 0) return 0;
    
    // Count character frequencies
    const freq = new Map<string, number>();
    for (const char of str) {
      freq.set(char, (freq.get(char) || 0) + 1);
    }
    
    // Calculate entropy
    let entropy = 0;
    const len = str.length;
    
    for (const count of freq.values()) {
      const probability = count / len;
      entropy -= probability * Math.log2(probability);
    }
    
    return entropy;
  }

  /**
   * Check if string has high enough entropy to be a secret.
   * 
   * @param str - String to check
   * @param minEntropy - Minimum entropy threshold (default: 4.0)
   * @returns True if entropy exceeds threshold
   */
  static isHighEntropy(str: string, minEntropy: number = 4.0): boolean {
    return this.calculate(str) >= minEntropy;
  }

  /**
   * Calculate entropy for different character sets.
   * Useful for detecting base64, hex, etc.
   */
  static analyzeCharacterSet(str: string): EntropyAnalysis {
    const hasLower = /[a-z]/.test(str);
    const hasUpper = /[A-Z]/.test(str);
    const hasDigit = /[0-9]/.test(str);
    const hasSpecial = /[^a-zA-Z0-9]/.test(str);
    
    let charsetSize = 0;
    if (hasLower) charsetSize += 26;
    if (hasUpper) charsetSize += 26;
    if (hasDigit) charsetSize += 10;
    if (hasSpecial) charsetSize += 32;  // Approximate
    
    const maxEntropy = Math.log2(charsetSize);
    const actualEntropy = this.calculate(str);
    
    return {
      entropy: actualEntropy,
      maxEntropy,
      ratio: actualEntropy / maxEntropy,
      charsetSize,
      isHighEntropy: actualEntropy >= 4.0
    };
  }
}

interface EntropyAnalysis {
  entropy: number;
  maxEntropy: number;
  ratio: number;
  charsetSize: number;
  isHighEntropy: boolean;
}

Complexity Calculation

Implement cyclomatic complexity calculation:

export class ComplexityCalculator {
  /**
   * Calculate cyclomatic complexity of a function.
   * Complexity = decision points + 1
   * 
   * Decision points: if, else, for, while, case, catch, &&, ||, ?:
   */
  static calculate(ast: FunctionNode): number {
    let complexity = 1;  // Base complexity
    
    traverse(ast, {
      // Conditional statements
      IfStatement: () => complexity++,
      ConditionalExpression: () => complexity++,
      
      // Loops
      ForStatement: () => complexity++,
      ForInStatement: () => complexity++,
      ForOfStatement: () => complexity++,
      WhileStatement: () => complexity++,
      DoWhileStatement: () => complexity++,
      
      // Switch cases
      SwitchCase: (path) => {
        if (path.node.test !== null) {  // Exclude default case
          complexity++;
        }
      },
      
      // Exception handling
      CatchClause: () => complexity++,
      
      // Logical operators
      LogicalExpression: (path) => {
        if (path.node.operator === '&&' || path.node.operator === '||') {
          complexity++;
        }
      }
    });
    
    return complexity;
  }

  /**
   * Get complexity rating based on value.
   */
  static getRating(complexity: number): ComplexityRating {
    if (complexity <= 5) return 'simple';
    if (complexity <= 10) return 'moderate';
    if (complexity <= 20) return 'complex';
    return 'very-complex';
  }

  /**
   * Get recommended action based on complexity.
   */
  static getRecommendation(complexity: number): string {
    if (complexity <= 10) {
      return 'Complexity is acceptable';
    } else if (complexity <= 20) {
      return 'Consider refactoring to reduce complexity';
    } else {
      return 'High complexity - refactoring strongly recommended';
    }
  }
}

type ComplexityRating = 'simple' | 'moderate' | 'complex' | 'very-complex';

Resources

Documentation

Tools

Security Resources

Performance Resources

Contributing

We welcome contributions! Please see CONTRIBUTING.md for:

  • Development setup
  • Code style guidelines
  • Testing requirements
  • Pull request process
  • Adding new analyzers
  • Adding new AI providers

Support

Getting Help

Reporting Issues

When reporting issues, please include:

  1. Environment:

    • VSCode version
    • CodeGuard version
    • Operating system
    • Node.js version
  2. Steps to reproduce:

    • Minimal code example
    • Configuration settings
    • Expected vs actual behavior
  3. Logs:

    • Output panel logs (View β†’ Output β†’ CodeGuard)
    • Extension Host logs
    • Console errors (if any)

Feature Requests

We love hearing your ideas! When requesting features:

  1. Describe the problem: What are you trying to solve?
  2. Propose a solution: How would you like it to work?
  3. Consider alternatives: Are there other ways to solve this?
  4. Provide examples: Show use cases and code examples

Happy coding! πŸš€

CodeGuard - Catch issues before they reach production