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
TypeScript
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;
}