codeflash-internal/js/VSC-Extension/EXTENSION_GUIDE.md
mohammed ahmed 549b42b6f1
[VSC] React sidebar & reduce bundle size (#1761)
depends on https://github.com/codeflash-ai/codeflash/pull/688,
https://github.com/codeflash-ai/codeflash-internal/pull/1767

---------

Signed-off-by: ali <mohammed18200118@gmail.com>
Co-authored-by: Sarthak Agarwal <sarthak.saga@gmail.com>
2025-09-04 18:58:54 +05:30

21 KiB

Complete VSCode Extension Development Guide: Codeflash

Table of Contents

  1. Extension Architecture Overview
  2. Project Structure & Files
  3. Core Concepts & Technologies
  4. Detailed Component Analysis
  5. VSCode Extension APIs Used
  6. Decision Making Process
  7. Build System & Tooling
  8. Extension Lifecycle

Extension Architecture Overview

High-Level Architecture

┌─────────────────────────────────────────────────────────────┐
│                    VSCode Extension Host                    │
├─────────────────────────────────────────────────────────────┤
│  Extension.ts (Main Entry Point)                          │
│  ├── Registers Providers & Commands                        │
│  ├── Manages Service Lifecycle                            │
│  └── Handles Extension Activation/Deactivation            │
├─────────────────────────────────────────────────────────────┤
│  Providers (UI Components)                                │
│  ├── SidebarProvider (Webview)                           │
│  └── CodeLensProvider (Inline Buttons)                   │
├─────────────────────────────────────────────────────────────┤
│  Services (Business Logic)                               │
│  ├── LSP Service (Language Server Communication)          │
│  ├── Analysis Service (Code Analysis)                     │
│  ├── Optimization Service (Code Optimization)             │
│  ├── Diff Service (Diff View Management)                 │
│  └── Python Service (Environment Detection)               │
├─────────────────────────────────────────────────────────────┤
│  Utils & Constants                                       │
│  ├── Logger (Centralized Logging)                        │
│  ├── Error Handling                                      │
│  └── Path Utilities                                      │
└─────────────────────────────────────────────────────────────┘

Why This Architecture?

  • Separation of Concerns: UI (Providers) separated from business logic (Services)
  • Testability: Each service can be tested independently
  • Maintainability: Clear boundaries between components
  • Scalability: Easy to add new providers or services

Project Structure & Files

Core Files Analysis

package.json - Extension Manifest

{
  "name": "codeflash",
  "displayName": "Codeflash",
  "description": "Optimize Your Code - Automatically",
  "main": "./dist/extension.js",
  "engines": { "vscode": "^1.98.0" }
}

Key Decisions:

  • main: Points to compiled JavaScript, not TypeScript source
  • engines.vscode: Minimum VSCode version required
  • activationEvents: []: Empty array means activate on any event (modern approach)

contributes Section - VSCode Integration Points

"contributes": {
  "viewsContainers": { /* Custom sidebar container */ },
  "views": { /* Sidebar webview */ },
  "commands": { /* Extension commands */ },
  "menus": { /* Command palette & context menus */ },
  "keybindings": { /* Keyboard shortcuts */ }
}

Why Each Contribution:

  • viewsContainers: Creates our own sidebar section (not mixed with Explorer/Git)
  • commands: Defines callable actions from command palette
  • menus: Controls where commands appear (command palette, context menus)
  • keybindings: Provides keyboard shortcuts for power users

TypeScript Configuration

tsconfig.json - Compilation Settings

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "strict": true
  }
}

Decisions:

  • ES2020: Modern JavaScript features while maintaining compatibility
  • commonjs: Required for Node.js/VSCode extension environment
  • strict: true: Enables all TypeScript strict type checking

Core Concepts & Technologies

1. Language Server Protocol (LSP)

What is LSP? Language Server Protocol is a standardized way for editors to communicate with language analysis tools.

Implementation in Codeflash:

// src/services/lspService.ts
export class LspService {
  async startClient(pythonPath: string): Promise<LanguageClient> {
    const client = new LanguageClient(
      LSP_CLIENT_ID,
      LSP_CLIENT_NAME,
      serverOptions,
      clientOptions,
    );
    await client.start();
    return client;
  }
}

Why LSP?

  • Standardized: Works with any LSP-compliant language server
  • Performant: Separate process handles heavy analysis
  • Extensible: Can add new LSP commands easily

2. Webview API

What are Webviews? Webviews let you display custom HTML/CSS/JavaScript UI within VSCode.

Implementation:

// src/providers/SidebarProvider.ts
export class SidebarProvider implements vscode.WebviewViewProvider {
  resolveWebviewView(webviewView: vscode.WebviewView) {
    webviewView.webview.html = generateWebviewHtml(/* ... */);
    webviewView.webview.onDidReceiveMessage(this.handleWebviewMessage);
  }
}

Security Considerations:

  • Content Security Policy: Prevents XSS attacks
  • localResourceRoots: Limits file access
  • enableScripts: Explicitly enables JavaScript

3. CodeLens API

What is CodeLens? CodeLens provides inline actionable information in the editor.

Implementation:

// src/providers/CodeLensProvider.ts
export class CodeflashCodeLensProvider implements vscode.CodeLensProvider {
  async provideCodeLenses(
    document: vscode.TextDocument,
  ): Promise<vscode.CodeLens[]> {
    // Analyze document and return CodeLens items
    return codeLenses;
  }
}

Why CodeLens?

  • Contextual: Appears exactly where it's relevant
  • Non-intrusive: Doesn't modify the actual code
  • Discoverable: Users see optimization opportunities immediately

Detailed Component Analysis

Extension Entry Point (src/extension.ts)

Activation Function

export async function activate(
  context: vscode.ExtensionContext,
): Promise<void> {
  // 1. Initialize services
  const pythonService = new PythonService();
  const lspService = new LspService();

  // 2. Start language client
  const client = await lspService.startClient(pythonPath);

  // 3. Register providers
  const sidebarProvider = new SidebarProvider(context.extensionUri, client);
  const codeLensProvider = new CodeflashCodeLensProvider(
    client,
    analysisService,
  );

  // 4. Register with VSCode
  context.subscriptions.push(
    vscode.window.registerWebviewViewProvider(SIDEBAR_VIEW_ID, sidebarProvider),
    vscode.languages.registerCodeLensProvider(
      { language: "python" },
      codeLensProvider,
    ),
  );
}

Key Concepts:

  • ExtensionContext: Provides extension lifetime management
  • subscriptions: Automatic cleanup when extension deactivates
  • Dependency Injection: Services receive their dependencies

Command Registration

const optimizeFunctionCommand = vscode.commands.registerCommand(
  "codeflash.optimizeFunction",
  async (uri: vscode.Uri, functionName: string) => {
    await handleOptimizeFunction(uri, functionName);
  },
);

Command Design Patterns:

  • Async handlers: All operations are asynchronous
  • Typed parameters: Commands receive strongly-typed parameters
  • Error boundaries: Each command has error handling

Sidebar Provider (src/providers/SidebarProvider.ts)

Webview Communication Pattern

// Extension → Webview
this.sendMessage({
  type: "updateState",
  payload: { status, message, functionData },
});

// Webview → Extension
webviewView.webview.onDidReceiveMessage(async (data) => {
  switch (data.type) {
    case "requestAnalysis":
      await this.handleRequestAnalysis();
      break;
  }
});

Message Types:

  • updateState: Updates UI state (loading, success, error)
  • optimizationStarted: Shows progress overlay
  • optimizationComplete: Shows results

State Management:

  • Centralized: All state changes go through updateWebviewState
  • Reactive: UI updates automatically when state changes
  • Persistent: State survives webview reloads

CodeLens Provider (src/providers/CodeLensProvider.ts)

Function Detection Algorithm

private findFunctionPosition(lines: string[], functionName: string): Position | null {
  const isMethod = functionName.includes('.');

  if (isMethod) {
    const [className, methodName] = functionName.split('.');
    // 1. Find class definition
    // 2. Find method within class
  } else {
    // Find standalone function
  }
}

Why This Approach?

  • Handles both functions and methods: Single algorithm for all cases
  • Line-by-line parsing: Simple and reliable
  • Position accuracy: Returns exact character position for button placement

CodeLens Refresh Strategy

// Refresh triggers
this._lspClient.onDidChangeState(() => this._onDidChangeCodeLenses.fire());
vscode.workspace.onDidSaveTextDocument(() =>
  this._onDidChangeCodeLenses.fire(),
);

Refresh Strategy:

  • LSP state changes: When language server starts/stops
  • File saves: When user saves Python files
  • Manual refresh: Via command palette

Services Architecture

Service Communication Pattern

// Service dependencies are injected
export class AnalysisService {
  constructor(private client: LanguageClient) {}

  async getOptimizableFunctions(uri: vscode.Uri) {
    return await this.client.sendRequest(
      LSP_COMMANDS.GET_OPTIMIZABLE_FUNCTIONS,
      {
        textDocument: { uri: uri.toString() },
      },
    );
  }
}

Design Principles:

  • Single Responsibility: Each service has one clear purpose
  • Dependency Injection: Services receive dependencies in constructor
  • Interface Segregation: Services expose minimal public APIs

Error Handling Strategy

try {
  const result = await this.optimizationService.optimizeFunction(
    functionName,
    uri,
  );
  // Handle success
} catch (error) {
  this.logger.error(`Error optimizing function ${functionName}`, error);
  vscode.window.showErrorMessage(`Failed to optimize ${functionName}`);
}

Error Handling Layers:

  1. Service Level: Catch and log errors
  2. UI Level: Show user-friendly messages
  3. Global Level: Extension-wide error boundaries

VSCode Extension APIs Used

Core APIs

1. Commands API

// Register command
vscode.commands.registerCommand("codeflash.optimizeFunction", handler);

// Execute command
vscode.commands.executeCommand("vscode.diff", originalUri, optimizedUri, title);

Use Cases:

  • User-triggered actions: Optimize function, refresh analysis
  • Internal operations: Open diff view, navigate to function

2. Language Features API

// CodeLens provider
vscode.languages.registerCodeLensProvider(selector, provider);

// Document selectors
{ language: 'python', scheme: 'file' }

Selector Strategy:

  • language: Only Python files
  • scheme: Only file system files (not git:// or other schemes)

3. Workspace API

// File operations
vscode.workspace.openTextDocument(uri);
vscode.workspace.fs.writeFile(uri, content);

// Event listening
vscode.workspace.onDidSaveTextDocument(handler);

4. Window API

// User interaction
vscode.window.showInformationMessage(message, ...actions);
vscode.window.withProgress(options, handler);

// Editor operations
vscode.window.showTextDocument(document, options);

Advanced APIs

1. Webview API

// Security configuration
webview.options = {
  enableScripts: true,
  localResourceRoots: [extensionUri, nodeModulesUri]
};

// Content Security Policy
<meta http-equiv="Content-Security-Policy" content="
  default-src 'none';
  script-src ${webview.cspSource} 'nonce-${nonce}';
  style-src ${webview.cspSource} 'unsafe-inline';
">

2. Language Client API (from vscode-languageclient)

const client = new LanguageClient(id, name, serverOptions, clientOptions);
await client.start();
const result = await client.sendRequest(method, params);

Decision Making Process

Technology Choices

1. TypeScript vs JavaScript

Chosen: TypeScript

Reasons:

  • Type Safety: Catches errors at compile time
  • IDE Support: Better IntelliSense and refactoring
  • Maintainability: Self-documenting code with types
  • VSCode Standard: Most VSCode extensions use TypeScript

2. ESBuild vs Webpack

Chosen: ESBuild

Reasons:

  • Speed: 10-100x faster than Webpack
  • Simplicity: Minimal configuration required
  • Size: Smaller bundle sizes
  • Modern: Built for modern JavaScript/TypeScript

3. Webview vs TreeView for Sidebar

Chosen: Webview

Reasons:

  • Flexibility: Complete control over UI
  • Rich Interactions: Complex UI elements (progress bars, buttons)
  • Styling: Custom CSS for branding
  • Future-proof: Can add any web technology

4. CodeLens vs Decorations for Inline UI

Chosen: CodeLens

Reasons:

  • Clickable: CodeLens supports commands, decorations don't
  • Standard: Users familiar with CodeLens from other extensions
  • Semantic: CodeLens is meant for actionable metadata
  • Positioning: Automatically positioned above code

Architectural Decisions

1. Service-Based Architecture

Why:

  • Testability: Services can be unit tested independently
  • Reusability: Services can be used by multiple providers
  • Separation: UI logic separated from business logic

2. LSP Communication

Why:

  • Performance: Heavy analysis runs in separate process
  • Language Agnostic: Same extension could support multiple languages
  • Standardized: Uses industry-standard protocol

3. Message-Based Webview Communication

Why:

  • Security: No direct object sharing between contexts
  • Reliability: Structured communication reduces bugs
  • Debuggability: All messages can be logged

Build System & Tooling

ESBuild Configuration (esbuild.js)

const esbuild = require("esbuild");

const production = process.argv.includes("--production");
const watch = process.argv.includes("--watch");

const ctx = await esbuild.context({
  entryPoints: ["src/extension.ts"],
  bundle: true,
  format: "cjs",
  minify: production,
  sourcemap: !production,
  platform: "node",
  outdir: "dist",
  external: ["vscode"],
});

Configuration Explained:

  • bundle: true: Combines all files into single output
  • format: 'cjs': CommonJS format for Node.js
  • external: ['vscode']: VSCode API provided by host
  • platform: 'node': Target Node.js environment

ESLint Configuration (eslint.config.mjs)

export default [
  {
    files: ["src/**/*.ts"],
    languageOptions: {
      parser: tsParser,
      parserOptions: { project: "./tsconfig.json" },
    },
    rules: {
      "@typescript-eslint/no-unused-vars": "error",
      "@typescript-eslint/no-explicit-any": "warn",
    },
  },
];

Development Scripts

{
  "dev": "npm run compile && npm run watch",
  "build": "npm run check-types && npm run lint && node esbuild.js",
  "package": "npm run build --production"
}

Script Strategy:

  • dev: Development with watch mode
  • build: Full build with type checking and linting
  • package: Production build with minification

Extension Lifecycle

1. Activation Sequence

1. VSCode loads extension.js
2. activate() function called
3. Services initialized
4. LSP client started
5. Providers registered
6. Commands registered
7. Extension ready

2. Runtime Operations

User opens Python file
├── CodeLens provider activated
├── Analysis service queries LSP
├── CodeLens buttons appear
│
User clicks "Optimize with CF"
├── Command handler invoked
├── Optimization service called
├── Progress indicator shown
├── Diff service opens results
└── User accepts/rejects changes

3. Deactivation Sequence

1. deactivate() function called
2. LSP client stopped
3. Services disposed
4. Event listeners removed
5. Resources cleaned up

Advanced Concepts

Memory Management

export async function deactivate(): Promise<void> {
  // Dispose services in reverse order
  codeLensProvider?.dispose();
  diffService?.dispose();
  await lspService?.stopClient();

  // Clear references to prevent memory leaks
  codeLensProvider = undefined;
  diffService = undefined;
}

Error Boundaries

// Global error handling
process.on("uncaughtException", (error) => {
  logger?.error("Uncaught exception", error);
});

// Service-level error handling
async function handleOptimizeFunction(uri: vscode.Uri, functionName: string) {
  try {
    // Operation
  } catch (error) {
    logger?.error(`Error optimizing function ${functionName}`, error);
    vscode.window.showErrorMessage(`Failed to optimize ${functionName}`);
  }
}

Performance Optimization

// Debounced CodeLens refresh
private refreshDebounced = debounce(() => {
  this._onDidChangeCodeLenses.fire();
}, 500);

// Lazy loading
async provideCodeLenses(document: vscode.TextDocument) {
  if (this.client.state !== LanguageClientState.Running) {
    return []; // Early return if not ready
  }
  // Expensive operations only when needed
}

Testing Strategy

Unit Testing Example

describe("AnalysisService", () => {
  let service: AnalysisService;
  let mockClient: jest.Mocked<LanguageClient>;

  beforeEach(() => {
    mockClient = createMockLanguageClient();
    service = new AnalysisService(mockClient);
  });

  it("should return optimizable functions", async () => {
    mockClient.sendRequest.mockResolvedValue({
      "file:///test.py": ["function1", "Class.method1"],
    });

    const result = await service.getOptimizableFunctions(testUri);

    expect(result.status).toBe("success");
    expect(result.functions).toHaveLength(2);
  });
});

Integration Testing

describe("Extension Integration", () => {
  it("should activate successfully", async () => {
    const context = createMockExtensionContext();
    await activate(context);

    expect(context.subscriptions).toHaveLength(5);
    expect(vscode.commands.registerCommand).toHaveBeenCalled();
  });
});

Best Practices Applied

1. Security

  • CSP: Content Security Policy for webviews
  • Input validation: All user inputs validated
  • Resource isolation: Webview resources restricted

2. Performance

  • Lazy loading: Components loaded only when needed
  • Debouncing: Frequent operations debounced
  • Caching: Results cached when appropriate

3. User Experience

  • Progressive disclosure: Simple UI with advanced features available
  • Feedback: Loading states and progress indicators
  • Accessibility: Proper ARIA labels and keyboard navigation

4. Maintainability

  • Type safety: Comprehensive TypeScript types
  • Documentation: Code comments and this guide
  • Testing: Unit and integration tests

Common Patterns in VSCode Extensions

1. Provider Pattern

interface MyProvider {
  provideX(document: TextDocument): X[];
}

vscode.languages.registerXProvider(selector, provider);

2. Command Pattern

const command = vscode.commands.registerCommand("extension.action", (args) => {
  // Handle command
});

3. Event-Driven Architecture

vscode.workspace.onDidChangeTextDocument((event) => {
  // React to changes
});

4. Disposable Pattern

class MyService implements vscode.Disposable {
  private disposables: vscode.Disposable[] = [];

  dispose() {
    vscode.Disposable.from(...this.disposables).dispose();
  }
}

This guide covers the fundamental concepts and decisions behind building a professional VSCode extension. Each component serves a specific purpose and follows established patterns for maintainability and user experience.