diff --git a/package-lock.json b/package-lock.json index f60579a..cbb3d4d 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,15 @@ "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==", + "license": "MIT", + "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 +1864,17 @@ } } }, + "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==", + "license": "MIT", + "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 +2135,36 @@ "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, + "license": "MIT", + "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, + "license": "MIT", + "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 +3534,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 +3552,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 +3749,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/context/language/python-parser.ts b/src/context/language/python-parser.ts index 845e90b..7a6ac01 100644 --- a/src/context/language/python-parser.ts +++ b/src/context/language/python-parser.ts @@ -1,15 +1,174 @@ import { AbstractParser, EnclosingContext } from "../../constants"; +import * as Parser from "tree-sitter"; +import * as Python from "tree-sitter-python"; + +interface TreeSitterNode { + startPosition: { row: number; column: number }; + endPosition: { row: number; column: number }; + type: string; + children: TreeSitterNode[]; +} + +// Add interface to match expected Node structure +interface LocationData { + line: number; + column: number; +} + +interface NodeLocation { + start: LocationData; + end: LocationData; +} + +interface ASTNode { + type: string; + loc: NodeLocation; + // Add other required properties from babel Node interface + leadingComments?: any[] | null; + innerComments?: any[] | null; + trailingComments?: any[] | null; + start?: number | null; + end?: number | null; + range?: [number, number]; + extra?: Record; +} + +const processNode = ( + node: TreeSitterNode, + lineStart: number, + lineEnd: number, + largestSize: number, + largestEnclosingContext: TreeSitterNode | null +) => { + const startLine = node.startPosition.row + 1; + const endLine = node.endPosition.row + 1; + + if (startLine <= lineStart && lineEnd <= endLine) { + const size = endLine - startLine; + if (size > largestSize) { + largestSize = size; + largestEnclosingContext = node; + } + } + return { largestSize, largestEnclosingContext }; +}; + export class PythonParser implements AbstractParser { + private parser: Parser; + + constructor() { + this.parser = new Parser(); + this.parser.setLanguage(Python); + } + findEnclosingContext( file: string, lineStart: number, lineEnd: number ): EnclosingContext { - // TODO: Implement this method for Python - return null; + try { + const tree = this.parser.parse(file); + let largestEnclosingContext: TreeSitterNode = null; + let largestSize = 0; + + const traverseNode = (node: TreeSitterNode) => { + if ( + [ + "function_definition", + "class_definition", + "if_statement", + "for_statement", + "while_statement", + "try_statement", + "with_statement", + "async_function_definition", + "async_for_statement", + ].includes(node.type) + ) { + ({ largestSize, largestEnclosingContext } = processNode( + node, + lineStart, + lineEnd, + largestSize, + largestEnclosingContext + )); + } + + if (node.children) { + node.children.forEach(traverseNode); + } + }; + + traverseNode(tree.rootNode as unknown as TreeSitterNode); + //ok + // Convert TreeSitterNode to expected Node format + const convertedNode: ASTNode | null = largestEnclosingContext + ? { + type: largestEnclosingContext.type, + loc: { + start: { + line: largestEnclosingContext.startPosition.row + 1, + column: largestEnclosingContext.startPosition.column, + }, + end: { + line: largestEnclosingContext.endPosition.row + 1, + column: largestEnclosingContext.endPosition.column, + }, + }, + // Add required properties + leadingComments: null, + innerComments: null, + trailingComments: null, + start: null, + end: null, + } + : null; + + return { + enclosingContext: convertedNode, + } as EnclosingContext; + } catch (error) { + console.error("Error parsing Python code:", error); + return { enclosingContext: null }; + } } + dryRun(file: string): { valid: boolean; error: string } { - // TODO: Implement this method for Python - return { valid: false, error: "Not implemented yet" }; + try { + const tree = this.parser.parse(file); + + if (this.hasErrors(tree.rootNode)) { + return { + valid: false, + error: "Syntax error in Python code", + }; + } + + return { + valid: true, + error: "", + }; + } catch (exc) { + return { + valid: false, + error: exc.toString(), + }; + } + } + + private hasErrors(node: any): boolean { + if (node.type === "ERROR") { + return true; + } + + if (node.children) { + for (const child of node.children) { + if (this.hasErrors(child)) { + return true; + } + } + } + + return false; } }