Skip to content

Latest commit

 

History

History
1649 lines (1301 loc) · 39.2 KB

File metadata and controls

1649 lines (1301 loc) · 39.2 KB

Contributing to CodeGuard

Thank you for your interest in contributing to CodeGuard! This document provides comprehensive guidelines and instructions for contributing to the project.

📋 Table of Contents

Code of Conduct

Our Pledge

We are committed to providing a welcoming and inclusive environment for all contributors. We expect:

  • Respectful communication: Be kind and considerate in all interactions
  • Constructive feedback: Focus on ideas, not individuals
  • Inclusive language: Avoid discriminatory or offensive language
  • Collaborative spirit: Help others learn and grow

Reporting Issues

If you experience or witness unacceptable behavior, please report it via GitHub Issues.

Getting Started

Prerequisites

Before you begin, ensure you have:

  • Node.js: Version 20.x or higher
  • npm: Version 10.x or higher
  • VSCode: Version 1.85.0 or higher
  • Git: Latest version
  • TypeScript: Familiarity with TypeScript 5.x

Quick Start

  1. Fork the repository on GitHub
  2. Clone your fork:
    git clone https://github.com/YOUR_USERNAME/codeguard.git
    cd codeguard
  3. Add upstream remote:
    git remote add upstream https://github.com/lekhanpro/CodeGuard.git
  4. Install dependencies:
    npm install
  5. Compile the extension:
    npm run compile
  6. Run tests:
    npm test
  7. Launch Extension Development Host:
    • Press F5 in VSCode
    • Or run: Debug: Start Debugging from Command Palette

Development Setup

Detailed Setup Instructions

1. Environment Setup

Install Node.js and npm:

# Using nvm (recommended)
nvm install 20
nvm use 20

# Verify installation
node --version  # Should be 20.x
npm --version   # Should be 10.x

Install VSCode:

2. Project Setup

# Clone and setup
git clone https://github.com/YOUR_USERNAME/codeguard.git
cd codeguard
npm install

# Build the project
npm run compile

# Verify setup
npm test

3. VSCode Configuration

The project includes VSCode configuration files:

  • .vscode/launch.json: Debug configurations
  • .vscode/tasks.json: Build tasks
  • .vscode/settings.json: Project settings
  • .vscode/extensions.json: Recommended extensions

Recommended Extensions:

  • ESLint
  • Prettier
  • TypeScript and JavaScript Language Features
  • Vitest

4. Development Tools

Watch Mode (auto-compile on changes):

npm run watch

Debug Mode:

  1. Open VSCode
  2. Press F5 or select Run > Start Debugging
  3. A new VSCode window opens with the extension loaded
  4. Set breakpoints in your code
  5. Trigger extension functionality to hit breakpoints

Testing in Watch Mode:

npm test -- --watch

Troubleshooting Setup

Issue: npm install fails

# Clear npm cache
npm cache clean --force
rm -rf node_modules package-lock.json
npm install

Issue: TypeScript compilation errors

# Ensure TypeScript is installed
npm install -g typescript
tsc --version

# Clean and rebuild
npm run clean
npm run compile

Issue: Extension doesn't load in debug mode

  • Check dist/extension.js exists
  • Verify package.json has correct main field
  • Check Output panel for errors (View → Output → Extension Host)

Project Structure

codeguard/
├── .vscode/                 # VSCode configuration
│   ├── launch.json         # Debug configurations
│   ├── tasks.json          # Build tasks
│   └── settings.json       # Project settings
├── src/                    # Source code
│   ├── analyzers/          # Code analysis modules
│   │   ├── Analyzer.ts     # Base analyzer interface
│   │   ├── SecurityAnalyzer.ts
│   │   ├── SecretScanner.ts
│   │   ├── PerformanceAnalyzer.ts
│   │   └── CodeSmellDetector.ts
│   ├── ai/                 # AI service integration
│   │   ├── AIService.ts    # AI service coordinator
│   │   ├── OpenAIClient.ts
│   │   ├── ClaudeClient.ts
│   │   └── OllamaClient.ts
│   ├── cache/              # Caching implementation
│   │   └── CacheManager.ts # Two-level cache (memory + disk)
│   ├── config/             # Configuration management
│   │   └── ConfigurationManager.ts
│   ├── diagnostics/        # Diagnostic management
│   │   └── DiagnosticManager.ts
│   ├── engine/             # Analysis engine
│   │   ├── AnalysisEngine.ts      # Main coordinator
│   │   ├── IncrementalAnalyzer.ts # Incremental analysis
│   │   ├── WorkerThreadManager.ts # Worker thread pool
│   │   └── AnalysisWorker.ts      # Worker implementation
│   ├── providers/          # VSCode providers
│   │   ├── CodeActionProvider.ts  # Quick fixes
│   │   ├── HoverProvider.ts       # Hover information
│   │   └── StatusBarProvider.ts   # Status bar integration
│   ├── privacy/            # Privacy monitoring
│   │   └── PrivacyMonitor.ts
│   ├── types/              # TypeScript type definitions
│   │   └── index.ts
│   ├── utils/              # Utility functions
│   │   └── debounce.ts
│   └── extension.ts        # Extension entry point
├── dist/                   # Compiled output (generated)
├── docs/                   # Documentation
├── .kiro/                  # Kiro spec files
│   └── specs/codeguard/
│       ├── requirements.md
│       ├── design.md
│       └── tasks.md
├── package.json            # Extension manifest
├── tsconfig.json           # TypeScript configuration
├── vitest.config.ts        # Test configuration
├── .eslintrc.json          # ESLint configuration
├── .gitignore              # Git ignore rules
├── README.md               # User documentation
├── CONTRIBUTING.md         # This file
└── LICENSE                 # MIT License

Key Directories

src/analyzers/: Contains all code analysis modules

  • Each analyzer implements the Analyzer interface
  • Analyzers run in parallel for performance
  • Support incremental analysis for large files

src/engine/: Core analysis engine

  • Coordinates analyzer execution
  • Manages worker threads
  • Handles caching and incremental analysis

src/providers/: VSCode integration

  • Implements VSCode provider interfaces
  • Handles UI interactions
  • Manages diagnostics display

src/ai/: AI service integration

  • Abstracts different AI providers
  • Handles API communication
  • Implements caching and rate limiting

Development Workflow

1. Create a Feature Branch

# Update main branch
git checkout main
git pull upstream main

# Create feature branch
git checkout -b feature/my-feature-name

2. Make Changes

Edit source files in src/ directory:

# Example: Adding a new analyzer
touch src/analyzers/MyAnalyzer.ts
touch src/analyzers/MyAnalyzer.test.ts

Compile changes:

npm run compile
# Or use watch mode
npm run watch

3. Test Changes

Run all tests:

npm test

Run specific test file:

npm test -- src/analyzers/MyAnalyzer.test.ts

Run with coverage:

npm run test:coverage

Test in Extension Development Host:

  1. Press F5 to launch debug mode
  2. Open a test file in the new window
  3. Verify your changes work as expected
  4. Check Output panel for logs

4. Debug Issues

Set breakpoints:

  • Click in the gutter next to line numbers
  • Or use debugger; statement in code

View logs:

  • Output panel: View → Output → CodeGuard
  • Debug Console: View → Debug Console
  • Developer Tools: Help → Toggle Developer Tools

Common debugging scenarios:

// Add logging
console.log('[CodeGuard] My debug message', data);

// Add breakpoint programmatically
debugger;

// Check analyzer execution
console.log('[Analyzer] Running:', analyzer.name);

5. Commit Changes

Stage changes:

git add src/analyzers/MyAnalyzer.ts
git add src/analyzers/MyAnalyzer.test.ts

Commit with conventional commit message:

git commit -m "feat(analyzers): add MyAnalyzer for detecting X"

6. Push and Create PR

# Push to your fork
git push origin feature/my-feature-name

# Create pull request on GitHub
# Include description, screenshots, and testing notes

Testing

CodeGuard uses a comprehensive testing strategy with both unit tests and property-based tests.

Test Types

1. Unit Tests

Test specific functionality with concrete examples:

import { describe, it, expect } from 'vitest';
import { SecurityAnalyzer } from './SecurityAnalyzer';

describe('SecurityAnalyzer', () => {
  it('should detect SQL injection in string concatenation', () => {
    const code = `const query = "SELECT * FROM users WHERE id = " + userId;`;
    const analyzer = new SecurityAnalyzer();
    const diagnostics = analyzer.analyze({
      content: code,
      languageId: 'javascript',
      fileUri: 'test.js'
    });
    
    expect(diagnostics).toHaveLength(1);
    expect(diagnostics[0].code).toBe('sql-injection');
    expect(diagnostics[0].severity).toBe(DiagnosticSeverity.Error);
  });
  
  it('should not flag parameterized queries', () => {
    const code = `const query = db.prepare("SELECT * FROM users WHERE id = ?").bind(userId);`;
    const analyzer = new SecurityAnalyzer();
    const diagnostics = analyzer.analyze({
      content: code,
      languageId: 'javascript',
      fileUri: 'test.js'
    });
    
    expect(diagnostics).toHaveLength(0);
  });
});

2. Property-Based Tests

Test universal properties across randomized inputs:

import fc from 'fast-check';
import { describe, it } from 'vitest';

describe('Property Tests', () => {
  // Feature: codeguard, Property 17: Cache Round-Trip Consistency
  it('Property 17: cached results should match fresh analysis', async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.string({ minLength: 10, maxLength: 1000 }),
        fc.constantFrom('javascript', 'typescript', 'python'),
        async (content, languageId) => {
          // First analysis (cache miss)
          const result1 = await analysisEngine.analyze({
            fileUri: 'test.js',
            content,
            languageId,
            version: 1
          });
          
          // Second analysis (cache hit)
          const result2 = await analysisEngine.analyze({
            fileUri: 'test.js',
            content,
            languageId,
            version: 1
          });
          
          // Results should be identical
          expect(result2.cacheHit).toBe(true);
          expect(result2.diagnostics).toEqual(result1.diagnostics);
        }
      ),
      { numRuns: 100 }
    );
  });
});

Running Tests

All tests:

npm test

Specific test file:

npm test -- src/analyzers/SecurityAnalyzer.test.ts

Watch mode (re-run on changes):

npm test -- --watch

Coverage report:

npm run test:coverage

Property tests only:

npm test -- --grep "Property"

Unit tests only:

npm test -- --grep -v "Property"

Writing Tests

Unit Test Guidelines

  1. Test specific examples: Focus on concrete scenarios
  2. Test edge cases: Empty inputs, boundary values, error conditions
  3. Test error handling: Verify graceful degradation
  4. Use descriptive names: Clearly state what is being tested
  5. Keep tests focused: One assertion per test when possible

Example:

describe('SecretScanner', () => {
  describe('AWS Access Key Detection', () => {
    it('should detect valid AWS access key pattern', () => {
      const code = 'const key = "AKIAIOSFODNN7EXAMPLE";';
      const diagnostics = scanner.analyze({ content: code });
      expect(diagnostics).toHaveLength(1);
      expect(diagnostics[0].code).toBe('aws-access-key');
    });
    
    it('should not flag low-entropy strings', () => {
      const code = 'const key = "AKIAAAAAAAAAAAAAAA";';
      const diagnostics = scanner.analyze({ content: code });
      expect(diagnostics).toHaveLength(0);
    });
    
    it('should handle empty input', () => {
      const diagnostics = scanner.analyze({ content: '' });
      expect(diagnostics).toHaveLength(0);
    });
  });
});

Property Test Guidelines

  1. Test universal properties: Properties that should hold for all inputs
  2. Use appropriate generators: Create realistic test data
  3. Run 100+ iterations: Ensure thorough coverage
  4. Reference design properties: Link to design document
  5. Handle edge cases: Ensure generators cover boundary conditions

Example:

// Feature: codeguard, Property 6: Secret Detection with Entropy Filtering
it('Property 6: high-entropy secrets detected, low-entropy filtered', async () => {
  await fc.assert(
    fc.asyncProperty(
      fc.string({ minLength: 20, maxLength: 40 }),
      async (str) => {
        const entropy = calculateEntropy(str);
        const code = `const apiKey = "${str}";`;
        
        const diagnostics = await secretScanner.analyze({
          content: code,
          languageId: 'javascript'
        });
        
        // High entropy strings should be flagged
        if (entropy > 4.0) {
          expect(diagnostics.length).toBeGreaterThan(0);
        }
        
        // Low entropy strings should not be flagged
        if (entropy < 2.0) {
          expect(diagnostics).toHaveLength(0);
        }
      }
    ),
    { numRuns: 100 }
  );
});

Test Coverage Goals

  • Unit test coverage: >80% for all modules
  • Property test coverage: All 39 correctness properties from design
  • Integration tests: All VSCode API interactions
  • E2E tests: All major user workflows

Debugging Tests

Run single test:

it.only('should test specific case', () => {
  // This test runs alone
});

Skip test:

it.skip('should test something', () => {
  // This test is skipped
});

Debug in VSCode:

  1. Set breakpoint in test file
  2. Run Debug: JavaScript Debug Terminal
  3. Run npm test in the debug terminal
  4. Debugger stops at breakpoints

Code Style

TypeScript Guidelines

Type Safety

  • Use strict mode: Enabled in tsconfig.json
  • Explicit return types: Always specify function return types
  • Avoid any: Use unknown or proper types instead
  • Use interfaces: For public APIs and data structures
  • Use type aliases: For complex or union types

Good:

interface AnalysisContext {
  fileUri: string;
  content: string;
  languageId: string;
}

function analyze(context: AnalysisContext): Promise<Diagnostic[]> {
  // Implementation
}

Bad:

function analyze(context: any): any {
  // Implementation
}

Naming Conventions

  • Files:

    • Classes: PascalCase.ts (e.g., SecurityAnalyzer.ts)
    • Utilities: camelCase.ts (e.g., debounce.ts)
    • Tests: *.test.ts or *.property.test.ts
  • Classes: PascalCase (e.g., SecurityAnalyzer, CacheManager)

  • Interfaces: PascalCase with descriptive names (e.g., AnalysisContext, Diagnostic)

  • Functions: camelCase (e.g., analyzeCode, computeHash)

  • Variables: camelCase (e.g., fileContent, diagnostics)

  • Constants: UPPER_SNAKE_CASE (e.g., MAX_CACHE_SIZE, DEFAULT_TIMEOUT)

  • Private members: Prefix with _ (e.g., _cache, _worker)

Example:

const MAX_RETRIES = 3;

interface AnalysisResult {
  diagnostics: Diagnostic[];
  metadata: AnalysisMetadata;
}

class SecurityAnalyzer implements Analyzer {
  private _patterns: VulnerabilityPattern[];
  
  async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
    return this._detectVulnerabilities(context);
  }
  
  private _detectVulnerabilities(context: AnalysisContext): Diagnostic[] {
    // Implementation
  }
}

Code Organization

Imports: Group and order imports

// 1. Node.js built-ins
import * as fs from 'fs';
import * as path from 'path';

// 2. External dependencies
import * as vscode from 'vscode';
import { LRUCache } from 'lru-cache';

// 3. Internal modules
import { Analyzer, AnalysisContext } from '../types';
import { debounce } from '../utils/debounce';

Class structure: Consistent ordering

class MyClass {
  // 1. Static properties
  static readonly DEFAULT_VALUE = 42;
  
  // 2. Instance properties
  private _cache: Cache;
  readonly name: string;
  
  // 3. Constructor
  constructor(name: string) {
    this.name = name;
  }
  
  // 4. Public methods
  public async analyze(): Promise<void> {
    // Implementation
  }
  
  // 5. Private methods
  private _helper(): void {
    // Implementation
  }
}

Error Handling

Always handle errors gracefully:

async function analyzeFile(uri: string): Promise<Diagnostic[]> {
  try {
    const content = await fs.promises.readFile(uri, 'utf-8');
    return await analyze(content);
  } catch (error) {
    logger.error('Analysis failed:', error);
    
    // Return empty results, don't crash
    return [];
  }
}

Use custom error types:

class AnalysisError extends Error {
  constructor(
    message: string,
    public readonly analyzer: string,
    public readonly cause?: Error
  ) {
    super(message);
    this.name = 'AnalysisError';
  }
}

throw new AnalysisError('Analysis failed', 'SecurityAnalyzer', originalError);

Async/Await

Prefer async/await over promises:

// Good
async function loadData(): Promise<Data> {
  const content = await fs.promises.readFile('data.json', 'utf-8');
  return JSON.parse(content);
}

// Avoid
function loadData(): Promise<Data> {
  return fs.promises.readFile('data.json', 'utf-8')
    .then(content => JSON.parse(content));
}

Handle concurrent operations:

// Run in parallel
const [result1, result2, result3] = await Promise.all([
  analyzer1.analyze(context),
  analyzer2.analyze(context),
  analyzer3.analyze(context)
]);

// Run sequentially (when order matters)
const result1 = await analyzer1.analyze(context);
const result2 = await analyzer2.analyze(context);
const result3 = await analyzer3.analyze(context);

Code Formatting

Use ESLint and Prettier:

# Check for issues
npm run lint

# Auto-fix issues
npm run lint -- --fix

# Format code
npm run format

Key formatting rules:

  • Indentation: 2 spaces
  • Line length: 100 characters max
  • Quotes: Single quotes for strings
  • Semicolons: Always use semicolons
  • Trailing commas: Always in multiline

Documentation

JSDoc comments for public APIs:

/**
 * Analyzes code for security vulnerabilities.
 * 
 * @param context - The analysis context containing file content and metadata
 * @returns Array of diagnostics representing detected vulnerabilities
 * @throws {AnalysisError} If analysis fails critically
 * 
 * @example
 * ```typescript
 * const diagnostics = await analyzer.analyze({
 *   fileUri: 'file.ts',
 *   content: 'const query = "SELECT * FROM users";',
 *   languageId: 'typescript'
 * });
 * ```
 */
async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
  // Implementation
}

Inline comments for complex logic:

// Use data flow analysis to track tainted variables
// from user input to SQL execution points
const taintedVars = this._trackDataFlow(ast, userInputNodes);

// Only flag as SQL injection if tainted data reaches SQL sink
if (this._isSQLSink(node) && this._isTainted(node, taintedVars)) {
  diagnostics.push(this._createDiagnostic(node, 'sql-injection'));
}

Pull Request Process

Before Submitting

Checklist:

  • Code compiles without errors (npm run compile)
  • All tests pass (npm test)
  • Test coverage is adequate (>80% for new code)
  • Code follows style guidelines (npm run lint)
  • Documentation is updated (if needed)
  • Commit messages follow conventional commits format
  • Branch is up to date with main

Creating a Pull Request

  1. Push your branch:

    git push origin feature/my-feature-name
  2. Create PR on GitHub:

  3. PR Title: Use conventional commit format

    feat(security): add XSS detection for React components
    fix(cache): resolve memory leak in LRU cache
    docs(readme): update installation instructions
    
  4. PR Description: Include:

    • What: What changes were made
    • Why: Why these changes were needed
    • How: How the changes work
    • Testing: How you tested the changes
    • Screenshots: If UI changes were made
    • Breaking Changes: If any (with migration guide)

Example PR Description:

## What
Adds XSS detection for React components by analyzing JSX expressions.

## Why
Current XSS detection only works for vanilla JavaScript. React apps need
specific patterns for detecting unsafe JSX expressions.

## How
- Added JSX parser to SecurityAnalyzer
- Implemented pattern matching for dangerouslySetInnerHTML
- Added data flow analysis for user input in JSX expressions

## Testing
- Added 15 unit tests covering various JSX patterns
- Added property test for JSX expression analysis
- Tested manually with sample React app

## Breaking Changes
None

## Related Issues
Closes #123

Review Process

  1. Automated Checks: CI runs tests and linting
  2. Code Review: Maintainers review your code
  3. Feedback: Address review comments
  4. Approval: At least one maintainer approval required
  5. Merge: Maintainer merges your PR

Addressing Review Feedback

Make changes:

# Make requested changes
git add .
git commit -m "fix: address review feedback"
git push origin feature/my-feature-name

Update PR:

  • Respond to comments
  • Mark conversations as resolved
  • Request re-review

Commit Message Format

Follow Conventional Commits:

<type>(<scope>): <subject>

<body>

<footer>

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • test: Test additions or changes
  • refactor: Code refactoring
  • perf: Performance improvements
  • chore: Build process or tooling changes
  • style: Code style changes (formatting, etc.)
  • ci: CI/CD changes

Scopes:

  • security: Security analyzer
  • performance: Performance analyzer
  • secrets: Secret scanner
  • smells: Code smell detector
  • ai: AI service
  • cache: Cache manager
  • engine: Analysis engine
  • providers: VSCode providers
  • config: Configuration

Examples:

feat(security): add command injection detection
fix(cache): resolve race condition in LRU eviction
docs(readme): add troubleshooting section
test(analyzer): add property tests for parallel execution
refactor(engine): simplify worker thread management
perf(cache): optimize hash computation
chore(deps): update TypeScript to 5.3

Breaking Changes:

feat(api)!: change Analyzer interface signature

BREAKING CHANGE: The analyze() method now requires a version parameter.
Migration: Add version: 1 to all analyze() calls.

Adding New Features

Adding a New Analyzer

Analyzers detect specific types of issues in code. Here's how to add one:

1. Create Analyzer File

Create 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[] = [];
    
    // 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; // Set to true if you implement incremental analysis
  }

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

interface Issue {
  line: number;
  column: number;
  length: number;
  message: string;
}

2. Register Analyzer

Add to src/engine/AnalysisEngine.ts:

import { MyAnalyzer } from '../analyzers/MyAnalyzer';

// In constructor or initialization
this.analyzers.push(new MyAnalyzer());

3. Add Configuration

Add to package.json:

{
  "contributes": {
    "configuration": {
      "properties": {
        "codeguard.analyzers.myAnalyzer.enabled": {
          "type": "boolean",
          "default": true,
          "description": "Enable my analyzer"
        }
      }
    }
  }
}

4. Write Tests

Create src/analyzers/MyAnalyzer.test.ts:

import { describe, it, expect } from 'vitest';
import { MyAnalyzer } from './MyAnalyzer';

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

  it('should detect issue X', async () => {
    const code = 'const x = problematicPattern();';
    const diagnostics = await analyzer.analyze({
      fileUri: 'test.js',
      content: code,
      languageId: 'javascript'
    });
    
    expect(diagnostics).toHaveLength(1);
    expect(diagnostics[0].code).toBe('my-rule-id');
  });

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

5. Add Property Tests

Create src/analyzers/MyAnalyzer.property.test.ts:

import fc from 'fast-check';
import { describe, it } from 'vitest';
import { MyAnalyzer } from './MyAnalyzer';

describe('MyAnalyzer Property Tests', () => {
  // Feature: codeguard, Property X: Description
  it('Property X: should maintain invariant', async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.string({ minLength: 10, maxLength: 500 }),
        async (code) => {
          const analyzer = new MyAnalyzer();
          const diagnostics = await analyzer.analyze({
            fileUri: 'test.js',
            content: code,
            languageId: 'javascript'
          });
          
          // Verify property holds
          expect(diagnostics.every(d => d.source === 'codeguard')).toBe(true);
        }
      ),
      { numRuns: 100 }
    );
  });
});

6. Update Documentation

Add to README.md:

  • Feature description
  • Example detection
  • Configuration options

Adding a New AI Provider

To add support for a new AI service:

1. Create Client File

Create src/ai/MyAIClient.ts:

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

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

  async complete(prompt: string, options: CompletionOptions): Promise<string> {
    try {
      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
        })
      });

      if (!response.ok) {
        throw new Error(`API error: ${response.statusText}`);
      }

      const data = await response.json();
      return data.choices[0].text;
    } catch (error) {
      throw new Error(`MyAI completion failed: ${error.message}`);
    }
  }

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

2. Register Provider

Add to src/ai/AIService.ts:

import { MyAIClient } from './MyAIClient';

private _createClient(): AIClient {
  switch (this.config.provider) {
    case 'openai':
      return new OpenAIClient(this.config.apiKey, this.config.model);
    case 'claude':
      return new ClaudeClient(this.config.apiKey, this.config.model);
    case 'myai':
      return new MyAIClient(this.config.apiKey, this.config.model);
    default:
      throw new Error(`Unknown AI provider: ${this.config.provider}`);
  }
}

3. Add Configuration

Add to package.json:

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

4. Write Tests

Create src/ai/MyAIClient.test.ts:

import { describe, it, expect, vi } from 'vitest';
import { MyAIClient } from './MyAIClient';

describe('MyAIClient', () => {
  it('should complete prompts successfully', async () => {
    const client = new MyAIClient('test-key', 'test-model');
    
    // Mock fetch
    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      json: async () => ({
        choices: [{ text: 'Fixed code here' }]
      })
    });

    const result = await client.complete('Fix this code', {
      maxTokens: 100,
      temperature: 0.2
    });

    expect(result).toBe('Fixed code here');
  });

  it('should handle API errors', async () => {
    const client = new MyAIClient('test-key', 'test-model');
    
    global.fetch = vi.fn().mockResolvedValue({
      ok: false,
      statusText: 'Unauthorized'
    });

    await expect(
      client.complete('Fix this code', { maxTokens: 100 })
    ).rejects.toThrow('API error: Unauthorized');
  });
});

5. Update Documentation

Add to README.md:

  • Provider description
  • Setup instructions
  • Configuration example
  • Supported models

Adding a New Language

To add support for a new programming language:

1. Update Activation Events

Add to package.json:

{
  "activationEvents": [
    "onLanguage:javascript",
    "onLanguage:typescript",
    "onLanguage:python",
    "onLanguage:java",
    "onLanguage:go"  // New language
  ]
}

2. Add Language-Specific Patterns

Update analyzers to support the new language:

export class SecurityAnalyzer implements Analyzer {
  readonly supportedLanguages = [
    'javascript',
    'typescript',
    'python',
    'java',
    'go'  // Add new language
  ];

  private _getPatterns(languageId: string): VulnerabilityPattern[] {
    switch (languageId) {
      case 'go':
        return this._goPatterns;
      // ... other languages
    }
  }

  private readonly _goPatterns: VulnerabilityPattern[] = [
    {
      id: 'sql-injection-go',
      pattern: /db\.Query\([^?]*\+/,
      severity: DiagnosticSeverity.Error,
      message: 'Potential SQL injection in Go'
    }
  ];
}

3. Add Tests

Test language-specific patterns:

describe('SecurityAnalyzer - Go', () => {
  it('should detect SQL injection in Go', async () => {
    const code = `
      query := "SELECT * FROM users WHERE id = " + userId
      rows, err := db.Query(query)
    `;
    
    const diagnostics = await analyzer.analyze({
      fileUri: 'test.go',
      content: code,
      languageId: 'go'
    });
    
    expect(diagnostics).toHaveLength(1);
    expect(diagnostics[0].code).toBe('sql-injection-go');
  });
});

4. Update Documentation

Add to README.md:

  • Language in supported languages table
  • Language-specific features
  • Examples for the new language

Performance Guidelines

CodeGuard must maintain high performance to avoid disrupting developer workflow.

Performance Requirements

  • Analysis time: <100ms for files under 1000 lines
  • Activation time: <500ms on VSCode startup
  • Memory usage: <5MB when idle, <200MB under load
  • Cache operations: <10ms for cache hits

Optimization Strategies

1. Use Worker Threads

CPU-intensive work should run in worker threads:

// Good: Run in worker thread
const worker = new Worker('./analysis-worker.js');
worker.postMessage({ content, languageId });

// Bad: Block main thread
const result = expensiveAnalysis(content);

2. Implement Caching

Cache expensive computations:

class MyAnalyzer {
  private _cache = new LRUCache<string, Diagnostic[]>({ max: 1000 });

  async analyze(context: AnalysisContext): Promise<Diagnostic[]> {
    const hash = this._computeHash(context.content);
    
    // Check cache first
    const cached = this._cache.get(hash);
    if (cached) return cached;
    
    // Compute and cache
    const result = await this._doAnalysis(context);
    this._cache.set(hash, result);
    return result;
  }
}

3. Use Incremental Analysis

For large files, analyze only changed regions:

async analyzeIncremental(
  context: IncrementalAnalysisContext
): Promise<Diagnostic[]> {
  // Only analyze affected ranges
  const affectedDiagnostics = [];
  
  for (const range of context.affectedRanges) {
    const regionDiagnostics = await this._analyzeRegion(
      context.content,
      range
    );
    affectedDiagnostics.push(...regionDiagnostics);
  }
  
  // Merge with cached diagnostics from unchanged regions
  return this._mergeDiagnostics(
    affectedDiagnostics,
    context.cachedDiagnostics,
    context.affectedRanges
  );
}

4. Optimize Hot Paths

Profile and optimize frequently-called code:

// Bad: Regex compilation in loop
for (const line of lines) {
  if (/pattern/.test(line)) {
    // ...
  }
}

// Good: Compile regex once
const pattern = /pattern/;
for (const line of lines) {
  if (pattern.test(line)) {
    // ...
  }
}

5. Batch Operations

Batch multiple operations together:

// Bad: Multiple individual operations
for (const file of files) {
  await analyzeFile(file);
}

// Good: Batch with Promise.all
await Promise.all(files.map(file => analyzeFile(file)));

Performance Testing

Benchmarks

Create benchmarks for critical paths:

import { describe, bench } from 'vitest';

describe('SecurityAnalyzer Performance', () => {
  bench('analyze 100-line file', async () => {
    const code = generateCode(100);
    await analyzer.analyze({ content: code, languageId: 'javascript' });
  });

  bench('analyze 1000-line file', async () => {
    const code = generateCode(1000);
    await analyzer.analyze({ content: code, languageId: 'javascript' });
  });

  bench('cache hit', async () => {
    const code = 'const x = 1;';
    await analyzer.analyze({ content: code, languageId: 'javascript' });
    // Second call should hit cache
    await analyzer.analyze({ content: code, languageId: 'javascript' });
  });
});

Profiling

Use VSCode's built-in profiler:

  1. Open Command Palette
  2. Run "Developer: Show Running Extensions"
  3. Click "Profile" next to CodeGuard
  4. Perform actions to profile
  5. Stop profiling and analyze results

Memory Profiling

Monitor memory usage:

function logMemoryUsage() {
  const usage = process.memoryUsage();
  console.log({
    rss: `${Math.round(usage.rss / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)}MB`,
    heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`
  });
}

Performance Checklist

Before submitting performance-sensitive code:

  • Profiled with realistic data
  • Benchmarked critical paths
  • Verified memory usage is acceptable
  • Tested with large files (>5000 lines)
  • Verified cache effectiveness
  • Checked for memory leaks
  • Ensured non-blocking execution

Documentation

When to Update Documentation

Update documentation when:

  • Adding features: Document new functionality
  • Changing APIs: Update API documentation
  • Modifying configuration: Update configuration reference
  • Fixing bugs: Update troubleshooting guide if relevant
  • Changing behavior: Update user guide

Documentation Files

  • README.md: User-facing documentation

    • Features and capabilities
    • Installation instructions
    • Usage examples
    • Configuration options
    • Troubleshooting
  • CONTRIBUTING.md: Developer documentation (this file)

    • Development setup
    • Code style guidelines
    • Testing requirements
    • Pull request process
  • API.md: API documentation

    • Public interfaces
    • Extension points
    • Integration guide
  • CHANGELOG.md: Version history

    • New features
    • Bug fixes
    • Breaking changes
    • Migration guides

Documentation Style

Be clear and concise:

<!-- Good -->
## Installation

Install from the VSCode Marketplace:
1. Open Extensions (Ctrl+Shift+X)
2. Search for "CodeGuard"
3. Click Install

<!-- Bad -->
## Installation

You can install this extension by opening the Extensions view in VSCode
which can be accessed by pressing Ctrl+Shift+X on Windows/Linux or
Cmd+Shift+X on Mac, then typing "CodeGuard" into the search box...

Use examples:

<!-- Good -->
### Configuration

Enable AI features:
```json
{
  "codeguard.ai.enabled": true,
  "codeguard.ai.provider": "ollama"
}

Configuration

You can enable AI features by setting the appropriate configuration values.


**Include screenshots** for UI features:
```markdown
![Status Bar](docs/images/status-bar.png)

Code Examples

Use realistic examples:

// Good: Realistic example
const query = db.prepare("SELECT * FROM users WHERE id = ?").bind(userId);

// Bad: Trivial example
const x = 1;

Show before and after:

// Before (with issue)
const query = "SELECT * FROM users WHERE id = " + userId;

// After (fixed)
const query = db.prepare("SELECT * FROM users WHERE id = ?").bind(userId);

Questions and Support

Getting Help

Community

License

By contributing to CodeGuard, you agree that your contributions will be licensed under the MIT License.

Acknowledgments

Thank you for contributing to CodeGuard! Your efforts help make code quality tools accessible to all developers.


Happy coding! 🚀