mirror of
https://github.com/freeedcom/ai-codereviewer.git
synced 2025-04-22 10:36:47 +00:00
Support push
trigger + plenty refactoring [review]
This commit is contained in:
parent
65cf847966
commit
e8074efc8b
3 changed files with 442 additions and 134 deletions
273
src/main.ts
273
src/main.ts
|
@ -59,6 +59,10 @@ const openai = new OpenAI({
|
|||
defaultHeaders: { "api-key": OPENAI_API_KEY },
|
||||
});
|
||||
|
||||
// The supported Github events that this Github action can handle.
|
||||
type GitHubEvent = "opened" | "synchronize" | "push";
|
||||
|
||||
// Data structure to host the details of a pull request.
|
||||
interface PRDetails {
|
||||
owner: string;
|
||||
repo: string;
|
||||
|
@ -67,18 +71,48 @@ interface PRDetails {
|
|||
description: string;
|
||||
}
|
||||
|
||||
async function getPRDetails(): Promise<PRDetails> {
|
||||
console.log("Fetching pull request details...");
|
||||
console.log("GITHUB_EVENT_PATH:", process.env.GITHUB_EVENT_PATH);
|
||||
console.log("GITHUB_EVENT_NAME:", process.env.GITHUB_EVENT_NAME);
|
||||
const eventData = JSON.parse(
|
||||
readFileSync(process.env.GITHUB_EVENT_PATH || "", "utf8")
|
||||
);
|
||||
console.log("Github event data:", eventData);
|
||||
/**
|
||||
* Retrieves pull request details based on the GitHub event type.
|
||||
*
|
||||
* This function reads the GitHub event data from the environment, logs it for debugging purposes,
|
||||
* and then determines the appropriate method to fetch pull request details based on the event type.
|
||||
* It supports the "opened", "synchronize", and "push" events.
|
||||
*
|
||||
* @param {GitHubEvent} event - The type of GitHub event ("opened", "synchronize", or "push").
|
||||
* @returns {Promise<PRDetails>} - A promise that resolves to the pull request details.
|
||||
*
|
||||
* @throws {Error} - Throws an error if the event type is unsupported.
|
||||
*
|
||||
* Example usage:
|
||||
* const prDetails = await getPrDetails("opened");
|
||||
* console.log(prDetails);
|
||||
*/
|
||||
async function getPrDetails(event: GitHubEvent): Promise<PRDetails> {
|
||||
const eventPath = process.env.GITHUB_EVENT_PATH || "";
|
||||
const eventData = JSON.parse(readFileSync(eventPath, "utf8"));
|
||||
|
||||
const { repository, number } = JSON.parse(
|
||||
readFileSync(process.env.GITHUB_EVENT_PATH || "", "utf8")
|
||||
);
|
||||
console.log("GitHub Event Data:", eventData); // Log the event data for debugging
|
||||
|
||||
switch (event) {
|
||||
case "opened":
|
||||
case "synchronize":
|
||||
return getPrFromEvent(eventData);
|
||||
case "push":
|
||||
return getPrFromApi(eventData);
|
||||
default:
|
||||
throw new Error(`Unsupported event: ${event}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves pull request details from the given event data.
|
||||
*
|
||||
* @param eventData - The event data containing repository and pull request number.
|
||||
* @returns A promise that resolves to the pull request details.
|
||||
* @throws Will throw an error if the event payload is missing the repository or number.
|
||||
*/
|
||||
async function getPrFromEvent(eventData: any): Promise<PRDetails> {
|
||||
const { repository, number } = eventData;
|
||||
if (!repository || !number) {
|
||||
throw new Error("Invalid event payload: missing repository or number");
|
||||
}
|
||||
|
@ -97,6 +131,50 @@ async function getPRDetails(): Promise<PRDetails> {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the pull request details associated with a given push event from the GitHub API.
|
||||
*
|
||||
* @param eventData - The event data containing information about the push event.
|
||||
* @returns A promise that resolves to the details of the associated pull request.
|
||||
* @throws Will throw an error if no associated pull request is found for the given push event.
|
||||
*/
|
||||
async function getPrFromApi(eventData: any): Promise<PRDetails> {
|
||||
const branchName = eventData.ref.replace("refs/heads/", "");
|
||||
const repoOwner = eventData.repository.owner.login;
|
||||
const repoName = eventData.repository.name;
|
||||
|
||||
const { data: pullRequests } = await octokit.pulls.list({
|
||||
owner: repoOwner,
|
||||
repo: repoName,
|
||||
state: "open",
|
||||
});
|
||||
|
||||
const pullRequest = pullRequests.find((pr) => pr.head.ref === branchName);
|
||||
|
||||
if (!pullRequest) {
|
||||
throw new Error("No associated pull request found for this push event.");
|
||||
}
|
||||
|
||||
return {
|
||||
owner: repoOwner,
|
||||
repo: repoName,
|
||||
pull_number: pullRequest.number,
|
||||
title: pullRequest.title,
|
||||
description: pullRequest.body ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the diff of a pull request from the GitHub repository.
|
||||
*
|
||||
* This function uses the GitHub API to retrieve the diff of a specified pull request.
|
||||
* The diff is returned as a string, or null if the request fails.
|
||||
*
|
||||
* @param {string} owner - The owner of the repository.
|
||||
* @param {string} repo - The name of the repository.
|
||||
* @param {number} pull_number - The number of the pull request.
|
||||
* @returns {Promise<string | null>} - A promise that resolves to the diff string or null if the request fails.
|
||||
*/
|
||||
async function getDiff(
|
||||
owner: string,
|
||||
repo: string,
|
||||
|
@ -112,6 +190,23 @@ async function getDiff(
|
|||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the parsed diff files and generates review comments using AI.
|
||||
*
|
||||
* This function iterates over the parsed diff files and their respective chunks,
|
||||
* identifies valid line numbers for changes, and generates a prompt for the AI to review the code.
|
||||
* It then filters the AI responses to ensure they correspond to valid line numbers and creates
|
||||
* review comments based on the AI responses.
|
||||
*
|
||||
* @param {File[]} parsedDiff - An array of parsed diff files to be analyzed.
|
||||
* @param {PRDetails} prDetails - Details of the pull request, including owner, repo, pull number, title, and description.
|
||||
* @returns {Promise<Array<{ body: string; path: string; line: number }>>} - A promise that resolves to an array of review comments.
|
||||
*
|
||||
* Example usage:
|
||||
* const parsedDiff = parseDiff(diff);
|
||||
* const prDetails = await getPRDetails();
|
||||
* const comments = await analyzeCode(parsedDiff, prDetails);
|
||||
*/
|
||||
async function analyzeCode(
|
||||
parsedDiff: File[],
|
||||
prDetails: PRDetails
|
||||
|
@ -158,6 +253,14 @@ async function analyzeCode(
|
|||
return comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a prompt string for reviewing pull requests based on the provided file, chunk, and PR details.
|
||||
*
|
||||
* @param {File} file - The file object containing information about the file being reviewed.
|
||||
* @param {Chunk} chunk - The chunk object containing the diff content and changes.
|
||||
* @param {PRDetails} prDetails - The pull request details including title and description.
|
||||
* @returns {string} The generated prompt string for the review task.
|
||||
*/
|
||||
function createPrompt(file: File, chunk: Chunk, prDetails: PRDetails): string {
|
||||
return `Your task is to review pull requests. Instructions:
|
||||
- Provide the response in following JSON format: {"reviews": [{"lineNumber": <line_number>, "reviewComment": "<review comment>"}]}
|
||||
|
@ -190,6 +293,16 @@ ${chunk.changes
|
|||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches AI-generated review comments for a given prompt.
|
||||
*
|
||||
* @param prompt - The input string to be sent to the AI model for generating responses.
|
||||
* @returns A promise that resolves to an array of review comments, each containing a line number and a review comment, or null if an error occurs.
|
||||
*
|
||||
* The function configures the query parameters for the AI model, sends the prompt to the model, and parses the response.
|
||||
* If the model supports JSON responses, it requests the response in JSON format.
|
||||
* In case of an error during the API call, it logs the error and returns null.
|
||||
*/
|
||||
async function getAIResponse(prompt: string): Promise<Array<{
|
||||
lineNumber: string;
|
||||
reviewComment: string;
|
||||
|
@ -227,6 +340,14 @@ async function getAIResponse(prompt: string): Promise<Array<{
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of review comments for a given file and chunk based on AI responses.
|
||||
*
|
||||
* @param file - The file object containing information about the file being reviewed.
|
||||
* @param chunk - The chunk object representing a portion of the file.
|
||||
* @param aiResponses - An array of AI response objects, each containing a line number and a review comment.
|
||||
* @returns An array of objects, each representing a review comment with a body, path, and line number.
|
||||
*/
|
||||
function createComment(
|
||||
file: File,
|
||||
chunk: Chunk,
|
||||
|
@ -247,6 +368,18 @@ function createComment(
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a review comment on a pull request.
|
||||
*
|
||||
* @param owner - The owner of the repository.
|
||||
* @param repo - The name of the repository.
|
||||
* @param pull_number - The number of the pull request.
|
||||
* @param comments - An array of comment objects, each containing:
|
||||
* - `body`: The text of the comment.
|
||||
* - `path`: The file path to which the comment applies.
|
||||
* - `line`: The line number in the file where the comment should be placed.
|
||||
* @returns A promise that resolves when the review comment has been created.
|
||||
*/
|
||||
async function createReviewComment(
|
||||
owner: string,
|
||||
repo: string,
|
||||
|
@ -262,55 +395,22 @@ async function createReviewComment(
|
|||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const prDetails = await getPRDetails();
|
||||
let diff: string | null;
|
||||
const eventData = JSON.parse(
|
||||
readFileSync(process.env.GITHUB_EVENT_PATH ?? "", "utf8")
|
||||
);
|
||||
|
||||
console.log("Github triggered event data:", eventData);
|
||||
if (eventData.action === "opened") {
|
||||
diff = await getDiff(
|
||||
prDetails.owner,
|
||||
prDetails.repo,
|
||||
prDetails.pull_number
|
||||
);
|
||||
} else if (eventData.action === "synchronize") {
|
||||
const newBaseSha = eventData.before;
|
||||
const newHeadSha = eventData.after;
|
||||
|
||||
const response = await octokit.repos.compareCommits({
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3.diff",
|
||||
},
|
||||
owner: prDetails.owner,
|
||||
repo: prDetails.repo,
|
||||
base: newBaseSha,
|
||||
head: newHeadSha,
|
||||
});
|
||||
|
||||
diff = String(response.data);
|
||||
} else if (eventData.action === "placeholder") {
|
||||
diff = await getDiff(
|
||||
prDetails.owner,
|
||||
prDetails.repo,
|
||||
prDetails.pull_number
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`Unsupported event: action=${eventData.action}, process.env.GITHUB_EVENT_NAME=${process.env.GITHUB_EVENT_NAME}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!diff) {
|
||||
console.log("No diff found");
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedDiff = parseDiff(diff);
|
||||
|
||||
/**
|
||||
* Filters the parsed diff files based on include and exclude patterns.
|
||||
*
|
||||
* This function reads the `exclude` and `include` patterns from the GitHub Action inputs,
|
||||
* trims and filters out any empty strings, and then applies these patterns to the parsed diff files.
|
||||
* Files that match the exclude patterns are excluded, and files that match the include patterns are included.
|
||||
* If both patterns are provided, the exclude patterns take precedence over the include patterns.
|
||||
*
|
||||
* @param {File[]} parsedDiff - An array of parsed diff files to be filtered.
|
||||
* @returns {File[]} - An array of filtered diff files.
|
||||
*
|
||||
* Example usage:
|
||||
* const parsedDiff = parseDiff(diff);
|
||||
* const filteredDiff = filterDiffs(parsedDiff);
|
||||
*/
|
||||
function filterDiffs(parsedDiff: File[]): File[] {
|
||||
const excludePatterns = core
|
||||
.getInput("exclude")
|
||||
.split(",")
|
||||
|
@ -336,6 +436,59 @@ async function main() {
|
|||
return !excluded && included;
|
||||
});
|
||||
|
||||
return filteredDiff;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const eventData = JSON.parse(
|
||||
readFileSync(process.env.GITHUB_EVENT_PATH ?? "", "utf8")
|
||||
);
|
||||
console.log("GitHub triggered event data:", eventData);
|
||||
|
||||
const prDetails = await getPrDetails(eventData);
|
||||
if (!prDetails) {
|
||||
console.log("No associated pull request found for this push event.");
|
||||
return;
|
||||
}
|
||||
|
||||
let diff: string | null;
|
||||
switch (eventData.action) {
|
||||
case "opened":
|
||||
case "push":
|
||||
diff = await getDiff(
|
||||
prDetails.owner,
|
||||
prDetails.repo,
|
||||
prDetails.pull_number
|
||||
);
|
||||
case "synchronize":
|
||||
const newBaseSha = eventData.before;
|
||||
const newHeadSha = eventData.after;
|
||||
|
||||
const response = await octokit.repos.compareCommits({
|
||||
headers: {
|
||||
accept: "application/vnd.github.v3.diff",
|
||||
},
|
||||
owner: prDetails.owner,
|
||||
repo: prDetails.repo,
|
||||
base: newBaseSha,
|
||||
head: newHeadSha,
|
||||
});
|
||||
|
||||
diff = String(response.data);
|
||||
default:
|
||||
console.log(
|
||||
`Unsupported event: action=${eventData.action}, process.env.GITHUB_EVENT_NAME=${process.env.GITHUB_EVENT_NAME}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!diff) {
|
||||
console.log("No diff found");
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedDiff = parseDiff(diff);
|
||||
const filteredDiff = filterDiffs(parsedDiff);
|
||||
|
||||
const comments = await analyzeCode(filteredDiff, prDetails);
|
||||
if (comments.length > 0) {
|
||||
await createReviewComment(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue