UNPKG

review-copilot

Version:

ReviewCopilot - AI-powered code review assistant with customizable prompts

214 lines (205 loc) 8.42 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LineSpecificReviewService = void 0; const diff_parser_1 = require("../utils/diff-parser"); class LineSpecificReviewService { constructor(aiProvider, gitPlatformService) { this.aiProvider = aiProvider; this.gitPlatformService = gitPlatformService; } /** * Perform line-specific code review and post comments directly to the PR */ async performLineSpecificReview(files, commitId, owner, repo, pullNumber, reviewPrompt) { const fileContexts = []; const allSuggestions = []; const lineSpecificSuggestions = []; let commentsPosted = 0; // Parse diff for each file for (const file of files) { if (!file.changes) continue; const parsedDiff = (0, diff_parser_1.parseDiff)(file.changes, file.filename); fileContexts.push({ filePath: file.filename, diffContent: file.changes, parsedDiff, commitId, owner, repo, pullNumber, }); } // Get AI suggestions with line context if (this.aiProvider.reviewWithLineSpecificSuggestions) { // Use enhanced AI provider that can return structured suggestions const suggestions = await this.aiProvider.reviewWithLineSpecificSuggestions(reviewPrompt, this.prepareEnhancedReviewContent(fileContexts), fileContexts.map((ctx) => ({ filePath: ctx.filePath, parsedDiff: ctx.parsedDiff, }))); allSuggestions.push(...suggestions); } else { // Fallback to legacy AI provider and parse the response const aiResponse = await this.aiProvider.review(this.enhancePromptWithLineContext(reviewPrompt), this.prepareEnhancedReviewContent(fileContexts)); const parsedSuggestions = this.parseAISuggestionsWithLineInfo(aiResponse, fileContexts); allSuggestions.push(...parsedSuggestions); } // Separate general and line-specific suggestions const generalSuggestions = allSuggestions.filter((s) => s.reviewType === 'general'); const lineSpecific = allSuggestions.filter((s) => s.reviewType === 'line-specific'); // Post line-specific comments for (const suggestion of lineSpecific) { try { const fileContext = fileContexts.find((ctx) => ctx.filePath === suggestion.filename); if (!fileContext) continue; const position = (0, diff_parser_1.findDiffPosition)(fileContext.parsedDiff, suggestion.line); if (position === null) { console.warn(`Cannot find diff position for line ${suggestion.line} in ${suggestion.filename}`); continue; } const commentParams = { owner, repo, pullNumber: pullNumber, body: suggestion.message, commitId, path: suggestion.filename, line: suggestion.line, side: 'RIGHT', }; await this.gitPlatformService.createReviewComment(commentParams); commentsPosted++; console.log(`✅ Posted comment on ${suggestion.filename}:${suggestion.line}`); } catch (error) { console.error(`❌ Failed to post comment on ${suggestion.filename}:${suggestion.line}:`, error); } } lineSpecificSuggestions.push(...lineSpecific); return { generalSuggestions, lineSpecificSuggestions, commentsPosted, }; } /** * Enhance the prompt to encourage line-specific suggestions */ enhancePromptWithLineContext(originalPrompt) { return `${originalPrompt} IMPORTANT: When reviewing code, please provide suggestions in the following format for line-specific issues: FILE: [filename] LINE: [line_number] SUGGESTION: [your suggestion] SEVERITY: [info|warning|error] For general suggestions that don't apply to specific lines, use: GENERAL: [your general suggestion] Focus on: 1. Code quality and potential bugs 2. Performance improvements 3. Security vulnerabilities 4. Best practices violations 5. Code maintainability Be specific about which lines need attention and provide actionable suggestions.`; } /** * Prepare enhanced content for AI review with line numbers and context */ prepareEnhancedReviewContent(fileContexts) { return fileContexts .map((ctx) => { const reviewableLines = (0, diff_parser_1.getReviewableLines)(ctx.parsedDiff); const linesWithNumbers = reviewableLines .map((line) => `${line.line}: ${line.content}`) .join('\n'); return ` === FILE: ${ctx.filePath} === Total changes: +${ctx.parsedDiff.additions} -${ctx.parsedDiff.deletions} Reviewable lines with line numbers: ${linesWithNumbers} Full diff: ${ctx.diffContent} `; }) .join('\n\n'); } /** * Parse AI response to extract line-specific suggestions */ parseAISuggestionsWithLineInfo(aiResponse, fileContexts) { const suggestions = []; const lines = aiResponse.split('\n'); let currentFile = ''; let currentLine = 0; let currentSuggestion = ''; let currentSeverity = 'info'; for (const line of lines) { const trimmedLine = line.trim(); if (trimmedLine.startsWith('FILE:')) { currentFile = trimmedLine.substring(5).trim(); } else if (trimmedLine.startsWith('LINE:')) { const lineMatch = trimmedLine.match(/LINE:\s*(\d+)/); if (lineMatch) { currentLine = parseInt(lineMatch[1], 10); } } else if (trimmedLine.startsWith('SUGGESTION:')) { currentSuggestion = trimmedLine.substring(11).trim(); } else if (trimmedLine.startsWith('SEVERITY:')) { const severity = trimmedLine.substring(9).trim(); if (['info', 'warning', 'error'].includes(severity)) { currentSeverity = severity; } } else if (trimmedLine.startsWith('GENERAL:')) { // General suggestion suggestions.push({ message: trimmedLine.substring(8).trim(), reviewType: 'general', severity: 'info', }); } // If we have all required info for a line-specific suggestion, add it if (currentFile && currentLine > 0 && currentSuggestion) { const fileContext = fileContexts.find((ctx) => ctx.filePath === currentFile); if (fileContext && (0, diff_parser_1.isLineCommentable)(fileContext.parsedDiff, currentLine)) { suggestions.push({ message: currentSuggestion, filename: currentFile, line: currentLine, severity: currentSeverity, reviewType: 'line-specific', commitId: fileContext.commitId, diffContext: (0, diff_parser_1.getDiffContext)(fileContext.parsedDiff, currentLine), }); } // Reset for next suggestion currentFile = ''; currentLine = 0; currentSuggestion = ''; currentSeverity = 'info'; } } return suggestions; } /** * Format a review comment for posting */ formatReviewComment(suggestion) { const severityEmoji = { error: '🚨', warning: '⚠️', info: '💡', }; const severity = suggestion.severity || 'info'; return `${severityEmoji[severity]} **${severity.toUpperCase()}** ${suggestion.message} `; } } exports.LineSpecificReviewService = LineSpecificReviewService;