UNPKG

ds-sfcoe-ailabs

Version:

AI-powered code review tool with static analysis integration for comprehensive code quality assessment.

358 lines (357 loc) 16.9 kB
import { Command } from '@oclif/core'; import { AIProviderType } from '../../common/aiProvider/index.js'; /** * @file index.ts * * AI-powered Pull Request review command that performs comprehensive static code analysis * using a hybrid approach combining Salesforce Code Analyzer (SCA) for Salesforce-specific files * and PMD for general file types, then leverages AI providers to generate intelligent code reviews. * * This command: * 1. Analyzes code diffs between two Git references * 2. Runs static analysis tools (SCA + PMD) to detect code quality issues * 3. Uses AI providers (OpenAI, Anthropic) to generate contextual review comments * 4. Identifies Docusign-specific patterns and best practices * 5. Posts comprehensive review comments to Pull Requests or outputs locally * * @author SF COE Team * @since 0.1.0 */ export default class AIPRReview extends Command { static readonly summary: string; static readonly description: string; static readonly examples: string[]; /** * Command line flags configuration for the AIPRReview command. * Defines all available command line options including their types, defaults, and validation */ static readonly flags: { 'repo-dir': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>; from: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>; to: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>; 'pull-request-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>; 'sf-config-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>; 'pmd-config-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>; 'ai-provider': import("@oclif/core/interfaces").OptionFlag<AIProviderType, import("@oclif/core/interfaces").CustomOptions>; 'ai-token': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>; 'ai-model': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>; 'ai-api-endpoint': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>; 'ai-api-version': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>; 'git-provider': import("@oclif/core/interfaces").OptionFlag<"GitHub" | undefined, import("@oclif/core/interfaces").CustomOptions>; 'git-token': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>; 'git-owner': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>; 'git-repo': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>; }; /** * Initialize the AIProvider and GitProvider based on command flags * * @param flags Command line flags containing provider configuration * @returns Object containing initialized AIProvider and optional GitProvider instances * @throws Error if AI provider initialization fails * @example * ```typescript * const { aiProvider, gitProvider } = AIPRReview.initializeProviders(flags); * ``` */ private static initializeProviders; /** * Get the commit SHA for PR comments with fallback mechanism * * @param repoDir Path to the Git repository directory * @param flags Command line flags containing the 'to' commit reference * @returns Promise resolving to the commit SHA for the PR commit * @throws Error if unable to determine any valid commit SHA * @example * ```typescript * const commitSha = await AIPRReview.getCommitSha('/path/to/repo', flags); * ``` */ private static getCommitSha; /** * Create a file map from SARIF data by parsing runs and results * * @param sarifData Parsed SARIF data from PMD or SCA analysis * @returns Map of normalized file paths to review hints with rule violations * @example * ```typescript * const fileMap = AIPRReview.createFileMap(sarifData); * console.log(`Found issues in ${fileMap.size} files`); * ``` */ private static createFileMap; /** * Main command execution method that orchestrates the entire PR review workflow * * @returns Promise that resolves when the command execution is complete * @throws Error if any critical step in the workflow fails * @example * ```typescript * const diffCommand = new AIPRReview(); * await diffCommand.run(); * ``` */ run(): Promise<void>; /** * Perform cleanup and completion tasks after the review process * * @param diffDir Directory containing the diff files * @param startTime Start time of the process */ private performCompletion; /** * Process PMD/SCA results through AI analysis and generate comprehensive review comments * * @param fileMap Map of file paths to review hints from static analysis * @param diffDir Directory containing the diff files to analyze * @param fileStatuses Map of file paths to their status (modified, added, deleted) * @param aiProvider AI provider instance for code review generation * @param flags Command line flags containing configuration options * @param commitSha Git commit SHA for PR comment posting * @param repoDir Path to the repository directory * @param gitProvider Optional git provider for posting PR comments * @param changedLinesRanges Map of file paths to changed line ranges for filtering * @returns Promise that resolves when processing is complete * @throws Error if file reading or AI analysis fails */ private processSCAResults; /** * Read and process files from the file map, handling path resolution and duplicate prevention * * @param fileMap Map of file paths to review hints from static analysis * @param diffDir Directory containing the diff files to read * @returns Promise resolving to object containing file contexts with hints and status * @throws Error if file reading operations fail critically * @example * ```typescript * const fileContexts = await this.readAndProcessFiles(fileMap, diffDir); * ``` */ private readAndProcessFiles; /** * Try to read a file from multiple possible paths with fallback strategies * * @param filePath Original file path from static analysis * @param diffDir Directory containing diff files * @param hints Review hints associated with this file * @param fileStatus Git status of the file (A=added, M=modified, etc.) * @returns Promise resolving to FileContext object if successful, null if all attempts fail * @example * ```typescript * const fileContext = await this.tryReadFile('src/test.js', diffDir, hints, 'A'); * ``` */ private tryReadFile; /** * Add missing new Apex files to fileContexts for DocuSign analysis * Reuses existing tryReadFile method for consistent file reading behavior * * @param fileContexts Array of file contexts to potentially add new files to (modified in place) * @param fileStatuses Map of file paths to their status for identifying new files * @param diffDir Directory containing diff files for fallback file reading * @returns Promise that resolves when all new Apex files have been processed * @example * ```typescript * await this.addMissingNewApexFiles(fileContexts, fileStatuses, diffDir); * ``` */ private addMissingNewApexFiles; /** * Separate hints into those that occur on changed lines and those that don't, grouped by file * * @param fileContexts Array of file contexts containing file information and hints * @param changedLinesRanges Map of file paths to changed line ranges * @returns Object containing changedLinesHintsPerFile and remainingHintsPerFile maps * @example * ```typescript * const { changedLinesHintsPerFile, remainingHintsPerFile } = this.separateHintsByChangedLines(fileContexts, changedLinesRanges); * ``` */ private separateHintsByChangedLines; /** * Separate AI analysis entries into changed lines and remaining entries * * @param allTableEntries All AI analysis table entries * @param changedLinesHints Hints that are on changed lines * @returns Object containing changedLineEntries and remainingEntries */ private separateAIAnalysisByChangedLines; /** * Build comprehensive code snippet for AI analysis by concatenating all file contents * * @param fileContexts Array of file contexts containing file names and content * @returns Formatted code snippet string with file separators for AI consumption * @example * ```typescript * const codeSnippet = this.buildCodeSnippet(fileContexts); * ``` */ private buildCodeSnippet; /** * Post PR comment with the review results, either to git provider or local output * * @param message The comprehensive review message to post * @param flags Command line flags containing PR and git configuration * @param commitSha Git commit SHA for associating the comment with specific commit * @param gitProvider Optional git provider for posting comments to remote repository * @returns Promise that resolves when comment posting is complete * @throws Error if PR comment posting fails when git provider is configured */ private postPRComment; /** * Build comprehensive PMD review comment including AI analysis and Docusign-specific issues * Shows both total vulnerabilities and vulnerabilities only on changed lines. * * @param aiResponse Response from AI provider containing code review comments * @param fileContexts Array of file contexts with content and associated hints * @param changedLinesRanges Map of file paths to changed line ranges for filtering * @param fileStatuses Map of file paths to their status (modified, added, etc.) * @param diffDir Directory containing diff files for reading new Apex files * @returns Formatted markdown message for PR comment with issues breakdown and AI analysis table * @example * ```typescript * const message = await this.buildReviewComment(aiResponse, fileContexts, changedLinesRanges, fileStatuses, diffDir); * ``` */ private buildReviewComment; /** * Generate a formatted AI analysis table section with header, description, and entries * * @param sectionTitle The title for the AI analysis section (e.g., "Changed Files", "Docusign Specific Issues") * @param entries Array of table entries to display * @param description Optional custom description for the section * @returns Formatted markdown string for the AI analysis section * @example * ```typescript * const section = this.generateAIAnalysisSection("Changed Files", changedLineEntries, "issues in your modifications"); * ``` */ private generateAIAnalysisSection; /** * Add Docusign-specific entries to the analysis table, including custom pattern checks * for JavaScript/TypeScript files and ESLint violations from static analysis hints * * @param fileContexts Array of file contexts containing file information, content, hints, and status * @returns tableEntries - Array of TableEntry objects with Docusign-specific issues * @example * ```typescript * this.addDocuSignAnalysis(fileContexts); * ``` */ private addDocuSignAnalysis; /** * Add AI-generated entries to the analysis table, filtering by PMD class name matches * * @param comments Array of AI-generated review comments * @param fileContexts Array of file contexts for class name matching * @param tableEntries Array to populate with AI-generated table entries * @returns void - method modifies tableEntries array in place * @example * ```typescript * this.addAIGeneratedAnalysis(aiResponse.comments, fileContexts, tableEntries); * ``` */ private addAIGeneratedAnalysis; /** * Get Docusign-specific issues for display in Code Quality Review section, grouped by file * * @param fileContexts Array of file contexts to analyze for Docusign-specific patterns * @param fileStatuses Map of file paths to their git status for identifying new files * @returns Map of file names to arrays of DocuSign-specific issues * @example * ```typescript * const issuesPerFile = this.getDocuSignIssues(fileContexts); * console.log(`Found DocuSign issues in ${issuesPerFile.size} files`); * ``` */ private getDocuSignIssues; /** * Run hybrid analysis workflow combining SCA for Salesforce files and PMD for other file types * * @param repoDir Path to the repository directory containing files to analyze * @param flags Command line flags containing configuration file paths * @returns Promise resolving to a map of file paths to review hints from combined analysis * @throws Error if analysis workflow fails critically * @example * ```typescript * const fileMap = await this.runHybridAnalysis(repoDir, flags); * ``` */ private runHybridAnalysis; /** * Categorize files into Salesforce and non-Salesforce groups based on file extensions * * @param files Array of file paths to categorize * @returns Object containing arrays of salesforceFiles and nonSalesforceFiles * @example * ```typescript * const { salesforceFiles, nonSalesforceFiles } = this.categorizeFiles(allFiles); * ``` */ private categorizeFiles; /** * Run Salesforce Code Analyzer (SCA) on Salesforce-specific files * * @param repoDir Path to the repository directory * @param salesforceFiles Array of Salesforce file paths to analyze * @param sfConfigFile Optional path to SCA configuration file * @returns Promise resolving to a map of file paths to review hints from SCA analysis * @throws Error if SCA analysis fails or temp directory operations fail */ private runScaAnalysis; /** * Run PMD static analysis on non-Salesforce files using SCATProvider * * @param repoDir Path to the repository directory * @param nonSalesforceFiles Array of non-Salesforce file paths to analyze * @param pmdConfigFile Optional path to PMD configuration file * @returns Promise resolving to a map of file paths to review hints from PMD analysis * @throws Error if PMD analysis fails beyond expected violation exit codes */ private runPmdAnalysis; /** * Merge two file maps by combining hints for existing files and adding new file entries * * @param target Target map to merge results into (modified in place) * @param source Source map containing additional results to merge * @returns void - modifies target map in place * @example * ```typescript * this.mergeFileMaps(combinedFileMap, scaResults); * ``` */ private mergeFileMaps; /** * Find all supported files for analysis by scanning directory recursively * * @param repoDir Path to repository directory to scan * @returns Array of file paths that match supported extensions for analysis * @example * ```typescript * const supportedFiles = this.findAllSupportedFiles('/path/to/repo'); * console.log(`Found ${supportedFiles.length} files to analyze`); * ``` */ private findAllSupportedFiles; /** * Get the default SCA configuration file path from the built library directory * * @returns Path to the default SCA configuration file * @throws Error if the default configuration file is not found * @example * ```typescript * const configPath = this.getDefaultScaConfigFile(); * ``` */ private getDefaultScaConfigFile; /** * Get the default PMD configuration file path from the built library directory * * @returns Path to the default PMD configuration file * @throws Error if the default configuration file is not found * @example * ```typescript * const configPath = this.getDefaultPmdConfigFile(); * ``` */ private getDefaultPmdConfigFile; }