UNPKG

review-copilot

Version:

ReviewCopilot - AI-powered code review assistant with customizable prompts

185 lines (184 loc) 6.31 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseDiff = parseDiff; exports.findDiffPosition = findDiffPosition; exports.getDiffContext = getDiffContext; exports.getReviewableLines = getReviewableLines; exports.isLineCommentable = isLineCommentable; /** * Parse a unified diff string into structured data */ function parseDiff(diffString, filePath) { const lines = diffString.split('\n'); const hunks = []; let currentHunk = null; let position = 0; let oldFile = ''; let newFile = ''; let additions = 0; let deletions = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Parse file headers if (line.startsWith('--- ')) { oldFile = line.substring(4); continue; } if (line.startsWith('+++ ')) { newFile = line.substring(4); continue; } // Parse hunk header: @@ -oldStart,oldLines +newStart,newLines @@ if (line.startsWith('@@')) { if (currentHunk) { hunks.push(currentHunk); } const hunkMatch = line.match(/^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@/); if (hunkMatch) { const oldStart = parseInt(hunkMatch[1], 10); const oldLines = parseInt(hunkMatch[2] || '1', 10); const newStart = parseInt(hunkMatch[3], 10); const newLines = parseInt(hunkMatch[4] || '1', 10); currentHunk = { oldStart, oldLines, newStart, newLines, lines: [], header: line, }; } continue; } // Parse diff content lines if (currentHunk && (line.startsWith(' ') || line.startsWith('-') || line.startsWith('+'))) { const type = line.startsWith('+') ? 'add' : line.startsWith('-') ? 'remove' : 'context'; const content = line.substring(1); // Calculate line numbers let oldLineNumber; let newLineNumber; let lineNumber; if (type === 'remove') { oldLineNumber = currentHunk.oldStart + currentHunk.lines.filter((l) => l.type !== 'add').length; lineNumber = oldLineNumber; deletions++; } else if (type === 'add') { newLineNumber = currentHunk.newStart + currentHunk.lines.filter((l) => l.type !== 'remove').length; lineNumber = newLineNumber; additions++; position++; // Only increment position for additions and context lines } else { // context line oldLineNumber = currentHunk.oldStart + currentHunk.lines.filter((l) => l.type !== 'add').length; newLineNumber = currentHunk.newStart + currentHunk.lines.filter((l) => l.type !== 'remove').length; lineNumber = newLineNumber; position++; } const diffLine = { lineNumber, oldLineNumber, newLineNumber, content, type, position, }; currentHunk.lines.push(diffLine); } } // Add the last hunk if (currentHunk) { hunks.push(currentHunk); } return { filePath, oldFile, newFile, hunks, additions, deletions, }; } /** * Find the position in diff for a specific line number */ function findDiffPosition(parsedDiff, targetLine) { for (const hunk of parsedDiff.hunks) { for (const line of hunk.lines) { if (line.newLineNumber === targetLine && (line.type === 'add' || line.type === 'context')) { return line.position; } } } return null; } /** * Get context around a specific line in the diff */ function getDiffContext(parsedDiff, targetLine, contextLines = 3) { for (const hunk of parsedDiff.hunks) { const targetLineIndex = hunk.lines.findIndex((line) => line.newLineNumber === targetLine); if (targetLineIndex !== -1) { const startIndex = Math.max(0, targetLineIndex - contextLines); const endIndex = Math.min(hunk.lines.length - 1, targetLineIndex + contextLines); const contextLinesArray = hunk.lines.slice(startIndex, endIndex + 1); return contextLinesArray .map((line) => { const prefix = line.type === 'add' ? '+' : line.type === 'remove' ? '-' : ' '; return `${prefix}${line.content}`; }) .join('\n'); } } return ''; } /** * Extract all modified lines from a diff that could be candidates for review comments */ function getReviewableLines(parsedDiff) { const reviewableLines = []; for (const hunk of parsedDiff.hunks) { for (const line of hunk.lines) { // Only consider added lines and context lines for review if ((line.type === 'add' || line.type === 'context') && line.newLineNumber) { reviewableLines.push({ line: line.newLineNumber, content: line.content, position: line.position, context: getDiffContext(parsedDiff, line.newLineNumber, 2), }); } } } return reviewableLines; } /** * Check if a line number exists in the diff and can receive comments */ function isLineCommentable(parsedDiff, lineNumber) { for (const hunk of parsedDiff.hunks) { for (const line of hunk.lines) { if (line.newLineNumber === lineNumber && (line.type === 'add' || line.type === 'context')) { return true; } } } return false; }