mirror of
				https://github.com/freeedcom/ai-codereviewer.git
				synced 2025-10-31 06:00:53 +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
				
			
		
							
								
								
									
										301
									
								
								dist/index.js
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										301
									
								
								dist/index.js
									
										
									
									
										vendored
									
									
								
							|  | @ -157,19 +157,51 @@ require("./sourcemap-register.js"); | |||
|         defaultQuery: { "api-version": OPENAI_API_VERSION }, | ||||
|         defaultHeaders: { "api-key": OPENAI_API_KEY }, | ||||
|       }); | ||||
|       function getPRDetails() { | ||||
|       /** | ||||
|        * 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); | ||||
|        */ | ||||
|       function getPrDetails(event) { | ||||
|         return __awaiter(this, void 0, void 0, function* () { | ||||
|           const eventPath = process.env.GITHUB_EVENT_PATH || ""; | ||||
|           const eventData = JSON.parse( | ||||
|             (0, fs_1.readFileSync)(eventPath, "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. | ||||
|        */ | ||||
|       function getPrFromEvent(eventData) { | ||||
|         var _a, _b; | ||||
|         return __awaiter(this, void 0, void 0, function* () { | ||||
|           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( | ||||
|             (0, fs_1.readFileSync)(process.env.GITHUB_EVENT_PATH || "", "utf8") | ||||
|           ); | ||||
|           console.log("Github event data:", eventData); | ||||
|           const { repository, number } = JSON.parse( | ||||
|             (0, fs_1.readFileSync)(process.env.GITHUB_EVENT_PATH || "", "utf8") | ||||
|           ); | ||||
|           const { repository, number } = eventData; | ||||
|           if (!repository || !number) { | ||||
|             throw new Error( | ||||
|               "Invalid event payload: missing repository or number" | ||||
|  | @ -191,6 +223,53 @@ require("./sourcemap-register.js"); | |||
|           }; | ||||
|         }); | ||||
|       } | ||||
|       /** | ||||
|        * 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. | ||||
|        */ | ||||
|       function getPrFromApi(eventData) { | ||||
|         var _a; | ||||
|         return __awaiter(this, void 0, void 0, function* () { | ||||
|           const branchName = eventData.ref.replace("refs/heads/", ""); | ||||
|           const repoOwner = eventData.repository.owner.login; | ||||
|           const repoName = eventData.repository.name; | ||||
|           const { data: pullRequests } = yield 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: | ||||
|               (_a = pullRequest.body) !== null && _a !== void 0 ? _a : "", | ||||
|           }; | ||||
|         }); | ||||
|       } | ||||
|       /** | ||||
|        * 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. | ||||
|        */ | ||||
|       function getDiff(owner, repo, pull_number) { | ||||
|         return __awaiter(this, void 0, void 0, function* () { | ||||
|           const response = yield octokit.pulls.get({ | ||||
|  | @ -203,6 +282,23 @@ require("./sourcemap-register.js"); | |||
|           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); | ||||
|        */ | ||||
|       function analyzeCode(parsedDiff, prDetails) { | ||||
|         return __awaiter(this, void 0, void 0, function* () { | ||||
|           const comments = []; | ||||
|  | @ -255,6 +351,14 @@ require("./sourcemap-register.js"); | |||
|           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, chunk, prDetails) { | ||||
|         return `Your task is to review pull requests. Instructions:
 | ||||
| - Provide the response in following JSON format:  {"reviews": [{"lineNumber":  <line_number>, "reviewComment": "<review comment>"}]} | ||||
|  | @ -286,6 +390,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. | ||||
|        */ | ||||
|       function getAIResponse(prompt) { | ||||
|         var _a, _b; | ||||
|         return __awaiter(this, void 0, void 0, function* () { | ||||
|  | @ -331,6 +445,14 @@ ${chunk.changes | |||
|           } | ||||
|         }); | ||||
|       } | ||||
|       /** | ||||
|        * 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, chunk, aiResponses) { | ||||
|         return aiResponses.flatMap((aiResponse) => { | ||||
|           if (!file.to) { | ||||
|  | @ -343,6 +465,18 @@ ${chunk.changes | |||
|           }; | ||||
|         }); | ||||
|       } | ||||
|       /** | ||||
|        * 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. | ||||
|        */ | ||||
|       function createReviewComment(owner, repo, pull_number, comments) { | ||||
|         return __awaiter(this, void 0, void 0, function* () { | ||||
|           yield octokit.pulls.createReview({ | ||||
|  | @ -354,11 +488,59 @@ ${chunk.changes | |||
|           }); | ||||
|         }); | ||||
|       } | ||||
|       /** | ||||
|        * 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) { | ||||
|         const excludePatterns = core | ||||
|           .getInput("exclude") | ||||
|           .split(",") | ||||
|           .map((s) => s.trim()) | ||||
|           .filter((s) => s.length > 0); // Filter out empty strings;
 | ||||
|         const includePatterns = core | ||||
|           .getInput("include") | ||||
|           .split(",") | ||||
|           .map((s) => s.trim()) | ||||
|           .filter((s) => s.length > 0); // Filter out empty strings;
 | ||||
|         const filteredDiff = parsedDiff.filter((file) => { | ||||
|           const excluded = | ||||
|             excludePatterns.length > 0 && | ||||
|             excludePatterns.some((pattern) => { | ||||
|               var _a; | ||||
|               return (0, minimatch_1.default)( | ||||
|                 (_a = file.to) !== null && _a !== void 0 ? _a : "", | ||||
|                 pattern | ||||
|               ); | ||||
|             }); | ||||
|           const included = | ||||
|             includePatterns.length === 0 || | ||||
|             includePatterns.some((pattern) => { | ||||
|               var _a; | ||||
|               return (0, minimatch_1.default)( | ||||
|                 (_a = file.to) !== null && _a !== void 0 ? _a : "", | ||||
|                 pattern | ||||
|               ); | ||||
|             }); | ||||
|           // Excluded patterns take precedence over included patterns.
 | ||||
|           return !excluded && included; | ||||
|         }); | ||||
|         return filteredDiff; | ||||
|       } | ||||
|       function main() { | ||||
|         var _a; | ||||
|         return __awaiter(this, void 0, void 0, function* () { | ||||
|           const prDetails = yield getPRDetails(); | ||||
|           let diff; | ||||
|           const eventData = JSON.parse( | ||||
|             (0, fs_1.readFileSync)( | ||||
|               (_a = process.env.GITHUB_EVENT_PATH) !== null && _a !== void 0 | ||||
|  | @ -367,75 +549,48 @@ ${chunk.changes | |||
|               "utf8" | ||||
|             ) | ||||
|           ); | ||||
|           console.log("Github triggered event data:", eventData); | ||||
|           if (eventData.action === "opened") { | ||||
|             diff = yield getDiff( | ||||
|               prDetails.owner, | ||||
|               prDetails.repo, | ||||
|               prDetails.pull_number | ||||
|             ); | ||||
|           } else if (eventData.action === "synchronize") { | ||||
|             const newBaseSha = eventData.before; | ||||
|             const newHeadSha = eventData.after; | ||||
|             const response = yield 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 = yield getDiff( | ||||
|               prDetails.owner, | ||||
|               prDetails.repo, | ||||
|               prDetails.pull_number | ||||
|             ); | ||||
|           } else { | ||||
|           console.log("GitHub triggered event data:", eventData); | ||||
|           const prDetails = yield getPrDetails(eventData); | ||||
|           if (!prDetails) { | ||||
|             console.log( | ||||
|               `Unsupported event: action=${eventData.action}, process.env.GITHUB_EVENT_NAME=${process.env.GITHUB_EVENT_NAME}` | ||||
|               "No associated pull request found for this push event." | ||||
|             ); | ||||
|             return; | ||||
|           } | ||||
|           let diff; | ||||
|           switch (eventData.action) { | ||||
|             case "opened": | ||||
|             case "push": | ||||
|               diff = yield getDiff( | ||||
|                 prDetails.owner, | ||||
|                 prDetails.repo, | ||||
|                 prDetails.pull_number | ||||
|               ); | ||||
|             case "synchronize": | ||||
|               const newBaseSha = eventData.before; | ||||
|               const newHeadSha = eventData.after; | ||||
|               const response = yield 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 = (0, parse_diff_1.default)(diff); | ||||
|           const excludePatterns = core | ||||
|             .getInput("exclude") | ||||
|             .split(",") | ||||
|             .map((s) => s.trim()) | ||||
|             .filter((s) => s.length > 0); // Filter out empty strings;
 | ||||
|           const includePatterns = core | ||||
|             .getInput("include") | ||||
|             .split(",") | ||||
|             .map((s) => s.trim()) | ||||
|             .filter((s) => s.length > 0); // Filter out empty strings;
 | ||||
|           const filteredDiff = parsedDiff.filter((file) => { | ||||
|             const excluded = | ||||
|               excludePatterns.length > 0 && | ||||
|               excludePatterns.some((pattern) => { | ||||
|                 var _a; | ||||
|                 return (0, minimatch_1.default)( | ||||
|                   (_a = file.to) !== null && _a !== void 0 ? _a : "", | ||||
|                   pattern | ||||
|                 ); | ||||
|               }); | ||||
|             const included = | ||||
|               includePatterns.length === 0 || | ||||
|               includePatterns.some((pattern) => { | ||||
|                 var _a; | ||||
|                 return (0, minimatch_1.default)( | ||||
|                   (_a = file.to) !== null && _a !== void 0 ? _a : "", | ||||
|                   pattern | ||||
|                 ); | ||||
|               }); | ||||
|             // Excluded patterns take precedence over included patterns.
 | ||||
|             return !excluded && included; | ||||
|           }); | ||||
|           const filteredDiff = filterDiffs(parsedDiff); | ||||
|           const comments = yield analyzeCode(filteredDiff, prDetails); | ||||
|           if (comments.length > 0) { | ||||
|             yield createReviewComment( | ||||
|  |  | |||
							
								
								
									
										2
									
								
								dist/index.js.map
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/index.js.map
									
										
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										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