38.4 Static analysis and lint gates

Overview and links for this section of the guide.

Why Static Analysis

AI-generated code often has subtle issues that tests don't catch:

  • Unused variables: Dead code that confuses readers
  • Type errors: Wrong types that compile but fail at runtime
  • Style violations: Inconsistent with codebase conventions
  • Complexity: Overly complex functions that are hard to maintain
  • Potential bugs: Patterns known to cause issues

Static analysis catches these before code review.

Lint Gates

// lint-gate.ts
interface LintConfig {
  // Must pass - block merge if any fail
  required: {
    eslint: boolean;
    typescript: boolean;
    prettier: boolean;
  };
  
  // Nice to have - warn but don't block
  recommended: {
    complexity: number;  // Max cyclomatic complexity
    lineLength: number;
  };
}

interface LintResult {
  tool: string;
  passed: boolean;
  errors: LintError[];
  warnings: LintWarning[];
  fixable: number;  // How many can be auto-fixed
}

export class LintGate {
  async check(files: string[]): Promise {
    const results = await Promise.all([
      this.runESLint(files),
      this.runTypeScript(files),
      this.runPrettier(files)
    ]);
    
    return results;
  }
  
  private async runESLint(files: string[]): Promise {
    const eslint = new ESLint();
    const results = await eslint.lintFiles(files);
    
    const errors = results.flatMap(r => 
      r.messages.filter(m => m.severity === 2)
    );
    const warnings = results.flatMap(r => 
      r.messages.filter(m => m.severity === 1)
    );
    const fixable = results.reduce((sum, r) => 
      sum + r.fixableErrorCount + r.fixableWarningCount, 0
    );
    
    return {
      tool: 'eslint',
      passed: errors.length === 0,
      errors,
      warnings,
      fixable
    };
  }
  
  private async runTypeScript(files: string[]): Promise {
    const result = execSync(`npx tsc --noEmit ${files.join(' ')} 2>&1`, {
      encoding: 'utf8',
      stdio: ['pipe', 'pipe', 'pipe']
    }).catch(e => e.stdout);
    
    const errors = this.parseTypeScriptErrors(result);
    
    return {
      tool: 'typescript',
      passed: errors.length === 0,
      errors,
      warnings: [],
      fixable: 0
    };
  }
}

Implementation

// code-quality-pipeline.ts
export class CodeQualityPipeline {
  async validate(diff: DiffBlock[]): Promise {
    const changedFiles = diff.map(d => d.file);
    
    // Run all checks in parallel
    const [lint, typeCheck, complexity] = await Promise.all([
      this.lintGate.check(changedFiles),
      this.typeChecker.check(changedFiles),
      this.complexityAnalyzer.analyze(changedFiles)
    ]);
    
    // Aggregate results
    const blocking = [
      ...lint.filter(r => !r.passed),
      typeCheck.passed ? null : typeCheck
    ].filter(Boolean);
    
    const warnings = [
      ...lint.flatMap(r => r.warnings),
      ...complexity.filter(c => c.score > this.config.recommended.complexity)
    ];
    
    return {
      canProceed: blocking.length === 0,
      blocking,
      warnings,
      summary: this.generateSummary({ lint, typeCheck, complexity })
    };
  }
  
  private generateSummary(results: any): string {
    const lines = [];
    
    if (results.lint.every(r => r.passed)) {
      lines.push('✅ ESLint: All checks passed');
    } else {
      lines.push(`❌ ESLint: ${results.lint.reduce((s, r) => s + r.errors.length, 0)} errors`);
    }
    
    if (results.typeCheck.passed) {
      lines.push('✅ TypeScript: No type errors');
    } else {
      lines.push(`❌ TypeScript: ${results.typeCheck.errors.length} errors`);
    }
    
    return lines.join('\n');
  }
}

Auto-Fixing Lint Errors

// auto-fixer.ts
export class LintAutoFixer {
  async fixAndRecheck(files: string[]): Promise {
    // Run ESLint with --fix
    await this.runWithFix('eslint', files);
    
    // Run Prettier
    await this.runWithFix('prettier', files);
    
    // Re-check to see what's left
    const remaining = await this.lintGate.check(files);
    const stillFailing = remaining.filter(r => !r.passed);
    
    if (stillFailing.length > 0) {
      // Ask the agent to fix non-auto-fixable issues
      return {
        autoFixed: true,
        remainingErrors: stillFailing.flatMap(r => r.errors),
        needsManualFix: true
      };
    }
    
    return {
      autoFixed: true,
      remainingErrors: [],
      needsManualFix: false
    };
  }
  
  async agentFix(errors: LintError[], code: string): Promise {
    const prompt = `Fix these lint errors in the code:

Errors:
${errors.map(e => `- Line ${e.line}: ${e.message} (${e.ruleId})`).join('\n')}

Code:
\`\`\`
${code}
\`\`\`

Return the fixed code.`;

    const response = await this.model.generateContent({
      contents: [{ role: 'user', parts: [{ text: prompt }] }]
    });
    
    return this.extractCode(response.response.text());
  }
}
Strict Mode for AI Code

Consider stricter lint rules for AI-generated code than for human code. AI tends to produce subtly wrong patterns that stricter rules catch.

Where to go next