review-copilot
Version:
ReviewCopilot - AI-powered code review assistant with customizable prompts
225 lines (224 loc) ⢠9.68 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.printSingleReviewResult = printSingleReviewResult;
exports.formatReviewResultsAsMarkdown = formatReviewResultsAsMarkdown;
exports.outputReviewResults = outputReviewResults;
exports.outputToCIPlatform = outputToCIPlatform;
exports.outputToTerminal = outputToTerminal;
exports.printReviewResultsToTerminal = printReviewResultsToTerminal;
const chalk_1 = __importDefault(require("chalk"));
const environment_helpers_1 = __importDefault(require("../../utils/environment-helpers"));
const logger_1 = require("../../utils/logger");
function printSingleReviewResult(result) {
const hasIssues = result.suggestions.length > 0 || result.error !== undefined;
if (!hasIssues && result.success) {
return;
}
if (result.suggestions.length > 0) {
result.suggestions.forEach((suggestion) => {
console.log(`${chalk_1.default.blue(suggestion.message)}`);
});
}
if (result.error) {
console.log(` šØ ${chalk_1.default.red(result.error.message)}`);
}
console.log('');
}
function formatReviewResultsAsMarkdown(results, context) {
if (!Array.isArray(results) || results.length === 0) {
return 'ā
No issues found in this review.\n';
}
let comment = '';
if (context?.type === 'branchName' && context.branchName) {
comment += `**Branch:** \`${context.branchName}\`\n\n`;
}
if (context?.type === 'commitMessages' &&
context.commits &&
context.prDetails) {
context.commits.forEach((commit) => {
const sha = commit.hash;
const url = `https://github.com/${context.prDetails.owner}/${context.prDetails.repo}/commit/${sha}`;
comment += `- [${sha.slice(0, 7)}](${url}): ${commit.message}\n`;
});
comment += '\n';
}
const resultsWithIssues = results.filter((result) => result.suggestions.length > 0 ||
result.error !== undefined ||
!result.success);
if (resultsWithIssues.length > 0) {
resultsWithIssues.forEach((result) => {
if (result.suggestions.length > 0) {
result.suggestions.forEach((suggestion) => {
comment += `${suggestion.message}\n`;
});
}
if (result.error) {
comment += `šØ ${result.error.message}\n`;
}
comment += '\n';
});
}
return comment;
}
async function outputReviewResults(context, results, extraContext) {
context.spinner.text = 'Processing results...';
if (environment_helpers_1.default.isCI) {
await outputToCIPlatform(context, results, extraContext);
}
else {
await outputToTerminal(results, extraContext);
}
}
async function outputToCIPlatform(context, results, extraContext) {
context.spinner.text = 'Posting review comments...';
if (!context.gitService || !context.prDetails) {
throw new Error('Git service or PR details not found');
}
if (results.branchName) {
const body = formatReviewResultsAsMarkdown(results.branchName, {
type: 'branchName',
branchName: extraContext?.branchName,
});
await context.gitService.createIssueComment({
owner: context.prDetails.owner,
repo: context.prDetails.repo,
issue_number: context.prDetails.pullNumber,
body,
});
}
if (results.commitMessages) {
const body = formatReviewResultsAsMarkdown(results.commitMessages, {
type: 'commitMessages',
commits: extraContext?.commits,
prDetails: context.prDetails,
});
await context.gitService.createIssueComment({
owner: context.prDetails.owner,
repo: context.prDetails.repo,
issue_number: context.prDetails.pullNumber,
body,
});
}
if (results.codeChanges) {
// Skip creating review comment if there are no actual code changes to review
const hasActualCodeChanges = results.codeChanges.some((result) => result.suggestions.some((suggestion) => suggestion.filename && (suggestion.line ?? 0) > 0));
if (!hasActualCodeChanges) {
// Log the message but don't create a review comment
console.log(chalk_1.default.gray('\nš No code changes to review. Skipping review comment creation.'));
return;
}
// Separate general and line-specific suggestions
const generalSuggestions = results.codeChanges.filter((result) => result.suggestions.some((suggestion) => suggestion.reviewType !== 'line-specific'));
const lineSpecificSuggestions = results.codeChanges.filter((result) => result.suggestions.some((suggestion) => suggestion.reviewType === 'line-specific'));
// Create general review comment if there are general suggestions
if (generalSuggestions.length > 0) {
const body = formatReviewResultsAsMarkdown(generalSuggestions);
const reviewCommentParams = {
owner: context.prDetails.owner,
repo: context.prDetails.repo,
pullNumber: context.prDetails.pullNumber,
body,
commitId: context.prDetails.commitId,
path: context.prDetails.path,
};
await context.gitService.createReviewComment(reviewCommentParams);
}
// Create line-specific review comments
for (const result of lineSpecificSuggestions) {
for (const suggestion of result.suggestions) {
if (suggestion.reviewType === 'line-specific' &&
suggestion.line &&
suggestion.filename) {
const body = formatReviewResultsAsMarkdown([
{
...result,
suggestions: [suggestion],
},
]);
// Base parameters for all review comments
const reviewCommentParams = {
owner: context.prDetails.owner,
repo: context.prDetails.repo,
pullNumber: context.prDetails.pullNumber,
body,
commitId: context.prDetails.commitId,
path: suggestion.filename,
line: suggestion.line,
side: 'RIGHT',
};
// Add multi-line parameters if startLine is provided and different from line
if (suggestion.startLine &&
suggestion.startLine !== suggestion.line) {
// Ensure startLine is less than line for multi-line comments
const startLine = Math.min(suggestion.startLine, suggestion.line);
const endLine = Math.max(suggestion.startLine, suggestion.line);
Object.assign(reviewCommentParams, {
startLine,
startSide: 'RIGHT',
line: endLine,
});
}
await context.gitService.createReviewComment(reviewCommentParams);
}
}
}
}
}
async function outputToTerminal(results, extraContext) {
let hasAnyResults = false;
if (results.branchName) {
logger_1.Logger.info('\nšæ Branch Name Review Results:');
logger_1.Logger.divider();
printReviewResultsToTerminal(results.branchName, {
type: 'branchName',
branchName: extraContext?.branchName,
});
hasAnyResults = true;
}
if (results.commitMessages && results.commitMessages.length > 0) {
logger_1.Logger.info('\nš Commit Messages Review Results:');
logger_1.Logger.divider();
printReviewResultsToTerminal(results.commitMessages, {
type: 'commitMessages',
commits: extraContext?.commits,
prDetails: extraContext?.prDetails,
});
hasAnyResults = true;
}
if (results.codeChanges) {
logger_1.Logger.info('\nš Code Changes Review Results:');
logger_1.Logger.divider();
printReviewResultsToTerminal(results.codeChanges);
hasAnyResults = true;
}
if (!hasAnyResults) {
logger_1.Logger.gray('\nš No review results to display.');
}
}
function printReviewResultsToTerminal(results, context) {
if (!Array.isArray(results) || results.length === 0) {
logger_1.Logger.gray('\nš No review results to display.\n');
return;
}
if (context?.type === 'branchName' && context.branchName) {
logger_1.Logger.success(`Branch: ${context.branchName}`);
}
if (context?.type === 'commitMessages' &&
context.commits &&
context.prDetails) {
context.commits.forEach((commit) => {
const sha = commit.hash;
const url = `https://github.com/${context.prDetails.owner}/${context.prDetails.repo}/commit/${sha}`;
logger_1.Logger.yellow(`[${sha.slice(0, 7)}] ${commit.message}`);
logger_1.Logger.gray(url);
});
logger_1.Logger.white('');
}
logger_1.Logger.info('\nš Review Results:\n');
results.forEach((result) => {
printSingleReviewResult(result);
});
}