diff --git a/package-lock.json b/package-lock.json index f60579a..52c8e04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,8 @@ "gpt-tokenizer": "^2.1.2", "groq-sdk": "^0.8.0", "octokit": "^3.1.1", + "tree-sitter": "^0.21.1", + "tree-sitter-python": "^0.23.4", "xml2js": "^0.6.2" }, "devDependencies": { @@ -1816,6 +1818,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/node-addon-api": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.2.2.tgz", + "integrity": "sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -1853,6 +1863,16 @@ } } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -2113,6 +2133,34 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/tree-sitter": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz", + "integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==", + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + } + }, + "node_modules/tree-sitter-python": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/tree-sitter-python/-/tree-sitter-python-0.23.4.tgz", + "integrity": "sha512-MbmUAl7y5UCUWqHscHke7DdRDwQnVNMNKQYQc4Gq2p09j+fgPxaU8JVsuOI/0HD3BSEEe5k9j3xmdtIWbDtDgw==", + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^8.2.1", + "node-gyp-build": "^4.8.2" + }, + "peerDependencies": { + "tree-sitter": "^0.21.1" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, "node_modules/tsx": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", @@ -3482,6 +3530,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node-addon-api": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.2.2.tgz", + "integrity": "sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==" + }, "node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -3495,6 +3548,11 @@ "whatwg-url": "^5.0.0" } }, + "node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==" + }, "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -3687,6 +3745,24 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "tree-sitter": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz", + "integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==", + "requires": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + } + }, + "tree-sitter-python": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/tree-sitter-python/-/tree-sitter-python-0.23.4.tgz", + "integrity": "sha512-MbmUAl7y5UCUWqHscHke7DdRDwQnVNMNKQYQc4Gq2p09j+fgPxaU8JVsuOI/0HD3BSEEe5k9j3xmdtIWbDtDgw==", + "requires": { + "node-addon-api": "^8.2.1", + "node-gyp-build": "^4.8.2" + } + }, "tsx": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", diff --git a/package.json b/package.json index fb08515..4de8068 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "gpt-tokenizer": "^2.1.2", "groq-sdk": "^0.8.0", "octokit": "^3.1.1", + "tree-sitter": "^0.21.1", + "tree-sitter-python": "^0.23.4", "xml2js": "^0.6.2" }, "devDependencies": { diff --git a/src/constants.ts b/src/constants.ts index 14c7de1..46b6d9f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,6 +1,7 @@ import { Node } from "@babel/traverse"; import { JavascriptParser } from "./context/language/javascript-parser"; import { ChatCompletionMessageParam } from "groq-sdk/resources/chat/completions"; +import { PythonParser } from "./context/language/python-parser"; export interface PRFile { sha: string; @@ -107,6 +108,7 @@ const EXTENSIONS_TO_PARSERS: Map = new Map([ ["tsx", new JavascriptParser()], ["js", new JavascriptParser()], ["jsx", new JavascriptParser()], + ["py", new PythonParser()], ]); export const getParserForExtension = (filename: string) => { diff --git a/src/context/language/python-parser.ts b/src/context/language/python-parser.ts index 845e90b..3e70ebf 100644 --- a/src/context/language/python-parser.ts +++ b/src/context/language/python-parser.ts @@ -1,15 +1,78 @@ import { AbstractParser, EnclosingContext } from "../../constants"; -export class PythonParser implements AbstractParser { - findEnclosingContext( - file: string, +import traverse, { NodePath, Node } from "@babel/traverse"; +import { SyntaxNode } from "tree-sitter"; + +const Parser = require("tree-sitter"); +const Python = require("tree-sitter-python"); + +const parser = new Parser(); +parser.setLanguage(Python); + +const processNode = ( + node: SyntaxNode, lineStart: number, - lineEnd: number - ): EnclosingContext { - // TODO: Implement this method for Python - return null; - } - dryRun(file: string): { valid: boolean; error: string } { - // TODO: Implement this method for Python - return { valid: false, error: "Not implemented yet" }; - } + lineEnd: number, + largestSize: number, + largestEnclosingContext: SyntaxNode | null +) => { + const start = node.startPosition; + const end = node.endPosition; + + if (start.row <= lineStart && lineEnd <= end.row) { + const size = end.row - start.row; + if (size > largestSize) { + largestSize = size; + largestEnclosingContext = node; + } + } + + return { largestSize, largestEnclosingContext }; +}; + +export class PythonParser implements AbstractParser { + findEnclosingContext( + file: string, + lineStart: number, + lineEnd: number + ): EnclosingContext { + const tree = parser.parse(file); + let largestEnclosingContext: SyntaxNode | null = null; + let largestSize = 0; + + const cursor = tree.walk(); + let first = true + while (first || cursor.gotoNextSibling() || cursor.gotoParent()) + { + const node = cursor.currentNode; + const node_types = ["function_definition", "class_definition", "module"]; + if ( + node_types.includes(node.type) + ) { + ({ largestSize, largestEnclosingContext } = processNode( + node, + lineStart, + lineEnd, + largestSize, + largestEnclosingContext, + )); + } + first = false + } + return { enclosingContext: largestEnclosingContext } as EnclosingContext; + } + + dryRun(file: string): { valid: boolean; error: string } { + try { + const tree = parser.parse(file); + return { + valid: true, + error: "", + }; + } catch (exc) { + return { + valid: false, + error: exc, + }; + } + } }