diff --git a/package.json b/package.json index 5ec57c8..fdd35e9 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "ts-node": "^10.9.1" }, "devDependencies": { + "@types/minimatch": "^5.1.2", "@types/node": "^18.15.5", "@vercel/ncc": "^0.36.1", "prettier": "^2.8.6", diff --git a/src/main.ts b/src/main.ts index 13677ae..dcbb9ee 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,12 +2,13 @@ import { readFileSync } from "fs"; import * as core from "@actions/core"; import OpenAI from "openai"; import { Octokit } from "@octokit/rest"; -import parseDiff, { Chunk, File } from "parse-diff"; +import parseDiff, { Chunk, File, Change } from "parse-diff"; import minimatch from "minimatch"; const GITHUB_TOKEN: string = core.getInput("GITHUB_TOKEN"); const OPENAI_API_KEY: string = core.getInput("OPENAI_API_KEY"); const OPENAI_API_MODEL: string = core.getInput("OPENAI_API_MODEL"); +const MAX_FILES: number = 25; const octokit = new Octokit({ auth: GITHUB_TOKEN }); @@ -46,14 +47,36 @@ async function getDiff( repo: string, pull_number: number ): Promise { - const response = await octokit.pulls.get({ - owner, - repo, - pull_number, - mediaType: { format: "diff" }, - }); - // @ts-expect-error - response.data is a string - return response.data; + try { + const response = await octokit.request('GET /repos/{owner}/{repo}/pulls/{pull_number}', { + owner, + repo, + pull_number, + mediaType: { + format: 'diff', + }, + }); + return response.data as unknown as string; + } catch (error) { + console.error('Error fetching diff:', error); + return null; + } +} + +function getLanguageContext(filename: string): string { + if (filename.endsWith('.cs')) { + return `This is a C# file using .NET 8 features. Consider the following when reviewing: +- C# 12 features like primary constructors, collection expressions, and inline arrays +- .NET 8 features including native AOT compilation considerations +- Performance implications and best practices +- Dependency injection patterns +- Async/await usage +- SOLID principles +- Nullable reference types +- Record types and pattern matching +- Memory management and disposable resources`; + } + return ''; } async function analyzeCode( @@ -62,7 +85,16 @@ async function analyzeCode( ): Promise> { const comments: Array<{ body: string; path: string; line: number }> = []; - for (const file of parsedDiff) { + // If there are more than MAX_FILES, only process the first MAX_FILES + const filesToProcess = parsedDiff.length > MAX_FILES + ? parsedDiff.slice(0, MAX_FILES) + : parsedDiff; + + if (parsedDiff.length > MAX_FILES) { + console.log(`Pull request contains ${parsedDiff.length} files. Processing only the first ${MAX_FILES} files.`); + } + + for (const file of filesToProcess) { if (file.to === "/dev/null") continue; // Ignore deleted files for (const chunk of file.chunks) { const prompt = createPrompt(file, chunk, prDetails); @@ -79,6 +111,8 @@ async function analyzeCode( } function createPrompt(file: File, chunk: Chunk, prDetails: PRDetails): string { + const languageContext = getLanguageContext(file.to || ''); + return `Your task is to review pull requests. Instructions: - Provide the response in following JSON format: {"reviews": [{"lineNumber": , "reviewComment": ""}]} - Do not give positive comments or compliments. @@ -87,6 +121,8 @@ function createPrompt(file: File, chunk: Chunk, prDetails: PRDetails): string { - Use the given description only for the overall context and only comment the code. - IMPORTANT: NEVER suggest adding comments to the code. +${languageContext} + Review the following code diff in the file "${ file.to }" and take the pull request title and description into account when writing the response. @@ -103,8 +139,7 @@ Git diff to review: \`\`\`diff ${chunk.content} ${chunk.changes - // @ts-expect-error - ln and ln2 exists where needed - .map((c) => `${c.ln ? c.ln : c.ln2} ${c.content}`) + .map((c: Change) => `${c.ln ? c.ln : c.ln2} ${c.content}`) .join("\n")} \`\`\` `; @@ -224,10 +259,10 @@ async function main() { const excludePatterns = core .getInput("exclude") .split(",") - .map((s) => s.trim()); + .map((s: string) => s.trim()); - const filteredDiff = parsedDiff.filter((file) => { - return !excludePatterns.some((pattern) => + const filteredDiff = parsedDiff.filter((file: File) => { + return !excludePatterns.some((pattern: string) => minimatch(file.to ?? "", pattern) ); }); diff --git a/src/types/parse-diff.d.ts b/src/types/parse-diff.d.ts new file mode 100644 index 0000000..02e8c7e --- /dev/null +++ b/src/types/parse-diff.d.ts @@ -0,0 +1,34 @@ +declare module 'parse-diff' { + export interface Change { + type: string; + content: string; + ln?: number; + ln2?: number; + normal?: boolean; + add?: boolean; + del?: boolean; + } + + export interface Chunk { + content: string; + changes: Change[]; + oldStart: number; + oldLines: number; + newStart: number; + newLines: number; + } + + export interface File { + chunks: Chunk[]; + deletions: number; + additions: number; + from: string; + to: string; + index: string[]; + binary?: boolean; + new?: boolean; + deleted?: boolean; + } + + export default function parseDiff(diff: string): File[]; +} diff --git a/yarn.lock b/yarn.lock index 0998992..7bcd4e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -163,6 +163,11 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== +"@types/minimatch@^5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" + integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== + "@types/node-fetch@^2.6.4": version "2.6.9" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.9.tgz#15f529d247f1ede1824f7e7acdaa192d5f28071e"