UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

618 lines (617 loc) 26.8 kB
import { BaseLanguageHandler } from './base.js'; import { getNodeText } from '../astAnalyzer.js'; import logger from '../../../logger.js'; import { ImportResolverFactory } from '../importResolvers/importResolverFactory.js'; export class PythonHandler extends BaseLanguageHandler { getFunctionQueryPatterns() { return [ 'function_definition', 'lambda' ]; } getClassQueryPatterns() { return [ 'class_definition' ]; } getImportQueryPatterns() { return [ 'import_statement', 'import_from_statement' ]; } extractFunctionName(node, sourceCode, _options) { try { if (node.type === 'function_definition') { const nameNode = node.childForFieldName('name'); if (nameNode) { const name = getNodeText(nameNode, sourceCode); if (name.startsWith('test_')) { return name; } if (name.startsWith('__') && name.endsWith('__')) { return name; } const decorators = this.extractDecorators(node, sourceCode); if (decorators.some(d => d.includes('route') || d.includes('get') || d.includes('post') || d.includes('put') || d.includes('delete'))) { const method = this.extractHttpMethod(decorators); return `${method}_handler_${name}`; } if (decorators.some(d => d.includes('login_required') || d.includes('permission_required'))) { return `view_${name}`; } if (decorators.includes('@property')) { return `property_${name}`; } if (decorators.includes('@staticmethod')) { return `static_${name}`; } if (decorators.includes('@classmethod')) { return `classmethod_${name}`; } return name; } } if (node.type === 'lambda') { if (node.parent?.type === 'assignment') { const targets = node.parent.childForFieldName('targets'); if (targets?.firstChild) { return getNodeText(targets.firstChild, sourceCode); } } if (node.parent?.type === 'argument_list' && node.parent.parent?.type === 'call') { const funcNode = node.parent.parent.childForFieldName('function'); if (funcNode) { const funcName = getNodeText(funcNode, sourceCode); if (['map', 'filter', 'reduce'].includes(funcName)) { return `${funcName}_lambda`; } } } return 'lambda'; } return 'anonymous'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Python function name'); return 'anonymous'; } } extractDecorators(node, sourceCode) { try { const decorators = []; if (node.parent?.type === 'decorated_definition') { const decoratorListNode = node.parent.childForFieldName('decorator_list'); if (decoratorListNode) { decoratorListNode.children.forEach(child => { if (child.type === 'decorator') { decorators.push(getNodeText(child, sourceCode)); } }); } } return decorators; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Python decorators'); return []; } } extractHttpMethod(decorators) { try { for (const decorator of decorators) { if (decorator.includes('get(')) return 'get'; if (decorator.includes('post(')) return 'post'; if (decorator.includes('put(')) return 'put'; if (decorator.includes('delete(')) return 'delete'; if (decorator.includes('patch(')) return 'patch'; } return 'route'; } catch (error) { logger.warn({ err: error }, 'Error extracting HTTP method from Python decorators'); return 'route'; } } extractClassName(node, sourceCode) { try { if (node.type === 'class_definition') { const nameNode = node.childForFieldName('name'); if (nameNode) { return getNodeText(nameNode, sourceCode); } } return 'AnonymousClass'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Python class name'); return 'AnonymousClass'; } } extractParentClass(node, sourceCode) { try { if (node.type === 'class_definition') { const argListNode = node.childForFieldName('argument_list'); if (argListNode?.firstChild?.type === 'identifier') { return getNodeText(argListNode.firstChild, sourceCode); } } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Python parent class'); return undefined; } } extractImportPath(node, sourceCode) { try { if (node.type === 'import_statement') { const nameNode = node.childForFieldName('name'); if (nameNode) { return getNodeText(nameNode, sourceCode); } } else if (node.type === 'import_from_statement') { const moduleNameNode = node.childForFieldName('module_name'); if (moduleNameNode) { return getNodeText(moduleNameNode, sourceCode); } } return 'unknown'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Python import path'); return 'unknown'; } } extractImportedItems(node, sourceCode) { try { if (node.type === 'import_from_statement') { const items = []; const moduleNode = node.childForFieldName('module_name'); const modulePath = moduleNode ? getNodeText(moduleNode, sourceCode) : ''; node.descendantsOfType(['dotted_name', 'identifier', 'aliased_import']).forEach(itemNode => { if (itemNode.parent?.type === 'import_from_statement' && itemNode.previousSibling?.text === 'import') { const name = getNodeText(itemNode, sourceCode); items.push({ name, path: modulePath, isDefault: false, isNamespace: false, nodeText: itemNode.text }); } else if (itemNode.type === 'aliased_import') { const nameNode = itemNode.childForFieldName('name'); const aliasNode = itemNode.childForFieldName('alias'); if (nameNode && aliasNode) { const name = getNodeText(nameNode, sourceCode); const alias = getNodeText(aliasNode, sourceCode); items.push({ name: `${name} as ${alias}`, path: modulePath, isDefault: false, isNamespace: false, nodeText: itemNode.text }); } } }); if (node.descendantsOfType('wildcard_import').length > 0) { items.push({ name: '*', path: modulePath, isDefault: false, isNamespace: true, nodeText: '*' }); } return items.length > 0 ? items : undefined; } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Python imported items'); return undefined; } } extractFunctionComment(node, sourceCode) { try { if (node.type === 'function_definition') { const bodyNode = node.childForFieldName('body'); if (bodyNode?.firstChild?.type === 'expression_statement' && bodyNode.firstChild.firstChild?.type === 'string') { const docstringNode = bodyNode.firstChild.firstChild; const docstring = getNodeText(docstringNode, sourceCode); return this.parseDocstring(docstring); } } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Python function comment'); return undefined; } } extractClassComment(node, sourceCode) { try { if (node.type === 'class_definition') { const bodyNode = node.childForFieldName('body'); if (bodyNode?.firstChild?.type === 'expression_statement' && bodyNode.firstChild.firstChild?.type === 'string') { const docstringNode = bodyNode.firstChild.firstChild; const docstring = getNodeText(docstringNode, sourceCode); return this.parseDocstring(docstring); } } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Python class comment'); return undefined; } } extractClassProperties(node, sourceCode) { const properties = []; try { const classBody = node.childForFieldName('body'); if (!classBody) return properties; classBody.children.forEach(childNode => { if (childNode.type === 'expression_statement' && childNode.firstChild?.type === 'assignment') { const assignment = childNode.firstChild; const leftNode = assignment.childForFieldName('left'); if (leftNode && leftNode.type === 'identifier') { const name = getNodeText(leftNode, sourceCode); if (name.startsWith('__') && name.endsWith('__')) { return; } const comment = this.extractPropertyComment(childNode, sourceCode); let type; const annotationNode = leftNode.nextSibling; if (annotationNode && annotationNode.type === 'type') { type = getNodeText(annotationNode, sourceCode); } let accessModifier; if (name.startsWith('__')) { accessModifier = 'private'; } else if (name.startsWith('_')) { accessModifier = 'protected'; } else { accessModifier = 'public'; } properties.push({ name, type, accessModifier, isStatic: true, comment, startLine: childNode.startPosition.row + 1, endLine: childNode.endPosition.row + 1 }); } } }); const initMethod = this.findInitMethod(classBody); if (initMethod) { const methodBody = initMethod.childForFieldName('body'); if (methodBody) { methodBody.descendantsOfType('assignment').forEach(assignment => { const leftNode = assignment.childForFieldName('left'); if (leftNode && leftNode.type === 'attribute' && leftNode.text.startsWith('self.')) { const propertyName = leftNode.text.substring(5); if (properties.some(p => p.name === propertyName)) { return; } const comment = this.extractPropertyComment(assignment.parent || assignment, sourceCode); let accessModifier; if (propertyName.startsWith('__')) { accessModifier = 'private'; } else if (propertyName.startsWith('_')) { accessModifier = 'protected'; } else { accessModifier = 'public'; } properties.push({ name: propertyName, accessModifier, isStatic: false, comment, startLine: assignment.startPosition.row + 1, endLine: assignment.endPosition.row + 1 }); } }); const parameters = initMethod.childForFieldName('parameters'); if (parameters) { parameters.descendantsOfType('typed_parameter').forEach(param => { const nameNode = param.childForFieldName('name'); const typeNode = param.childForFieldName('type'); if (nameNode && typeNode && nameNode.text !== 'self') { const name = getNodeText(nameNode, sourceCode); const type = getNodeText(typeNode, sourceCode); const selfAssignment = methodBody.descendantsOfType('assignment').find(assignment => { const leftNode = assignment.childForFieldName('left'); const rightNode = assignment.childForFieldName('right'); return leftNode?.text === `self.${name}` && rightNode?.text === name; }); if (selfAssignment) { if (properties.some(p => p.name === name)) { return; } let accessModifier; if (name.startsWith('__')) { accessModifier = 'private'; } else if (name.startsWith('_')) { accessModifier = 'protected'; } else { accessModifier = 'public'; } properties.push({ name, type, accessModifier, isStatic: false, startLine: selfAssignment.startPosition.row + 1, endLine: selfAssignment.endPosition.row + 1 }); } } }); } } } classBody.descendantsOfType('decorated_definition').forEach(decorated => { const decoratorList = decorated.childForFieldName('decorator_list'); const functionDef = decorated.childForFieldName('definition'); if (decoratorList && functionDef && functionDef.type === 'function_definition') { const hasPropertyDecorator = decoratorList.children.some(decorator => decorator.type === 'decorator' && decorator.text.includes('@property')); if (hasPropertyDecorator) { const nameNode = functionDef.childForFieldName('name'); if (nameNode) { const name = getNodeText(nameNode, sourceCode); if (properties.some(p => p.name === name)) { return; } const comment = this.extractFunctionComment(functionDef, sourceCode); let accessModifier; if (name.startsWith('__')) { accessModifier = 'private'; } else if (name.startsWith('_')) { accessModifier = 'protected'; } else { accessModifier = 'public'; } let type; const returnTypeNode = functionDef.childForFieldName('return_type'); if (returnTypeNode) { type = getNodeText(returnTypeNode, sourceCode); } properties.push({ name, type, accessModifier, isStatic: false, comment, startLine: decorated.startPosition.row + 1, endLine: decorated.endPosition.row + 1 }); } } } }); return properties; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Python class properties'); return properties; } } extractPropertyComment(node, sourceCode) { try { const lineStart = sourceCode.lastIndexOf('\n', node.startIndex) + 1; const textBeforeNode = sourceCode.substring(0, lineStart).trim(); const lines = textBeforeNode.split('\n'); const commentLines = []; for (let i = lines.length - 1; i >= 0; i--) { const line = lines[i].trim(); if (line.startsWith('#')) { commentLines.unshift(line.substring(1).trim()); } else if (line === '') { continue; } else { break; } } if (commentLines.length > 0) { return commentLines.join(' '); } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Python property comment'); return undefined; } } findInitMethod(classBody) { for (const child of classBody.children) { if (child.type === 'function_definition') { const nameNode = child.childForFieldName('name'); if (nameNode && getNodeText(nameNode, '') === '__init__') { return child; } } else if (child.type === 'decorated_definition') { const functionDef = child.childForFieldName('definition'); if (functionDef && functionDef.type === 'function_definition') { const nameNode = functionDef.childForFieldName('name'); if (nameNode && getNodeText(nameNode, '') === '__init__') { return functionDef; } } } } return undefined; } parseDocstring(docstring) { try { let text = docstring; if (text.startsWith('"""') && text.endsWith('"""')) { text = text.substring(3, text.length - 3); } else if (text.startsWith("'''") && text.endsWith("'''")) { text = text.substring(3, text.length - 3); } else if (text.startsWith('"') && text.endsWith('"')) { text = text.substring(1, text.length - 1); } else if (text.startsWith("'") && text.endsWith("'")) { text = text.substring(1, text.length - 1); } const lines = text.split('\n'); const trimmedLines = lines.map(line => line.trim()); const paragraphs = trimmedLines.join('\n').split('\n\n'); return paragraphs[0].replace(/\n/g, ' ').trim(); } catch (error) { logger.warn({ err: error }, 'Error parsing Python docstring'); return docstring; } } isGeneratorFunction(node, _sourceCode) { try { if (node.type === 'function_definition') { const bodyNode = node.childForFieldName('body'); if (bodyNode) { return bodyNode.descendantsOfType('yield').length > 0; } } return false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if Python function is a generator'); return false; } } isAsyncFunction(node, _sourceCode) { try { if (node.type === 'function_definition') { return node.text.startsWith('async '); } return false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if Python function is async'); return false; } } detectFramework(sourceCode) { try { if (sourceCode.includes('django') || sourceCode.includes('from django import') || sourceCode.includes('models.Model')) { return 'django'; } if (sourceCode.includes('flask') || sourceCode.includes('from flask import') || sourceCode.includes('Flask(__name__)')) { return 'flask'; } if (sourceCode.includes('fastapi') || sourceCode.includes('from fastapi import') || sourceCode.includes('FastAPI()')) { return 'fastapi'; } if (sourceCode.includes('pytest') || sourceCode.includes('from pytest import') || sourceCode.includes('@pytest.fixture')) { return 'pytest'; } return null; } catch (error) { logger.warn({ err: error }, 'Error detecting Python framework'); return null; } } async enhanceImportInfo(filePath, imports, options) { try { const factory = new ImportResolverFactory({ allowedDir: options.allowedDir, outputDir: options.outputDir, maxDepth: options.maxDepth || 3, pythonPath: options.pythonPath, pythonVersion: options.pythonVersion, venvPath: options.venvPath }); const resolver = factory.getImportResolver(filePath); if (!resolver) { return imports; } const enhancedImports = await resolver.analyzeImports(filePath, { pythonPath: options.pythonPath, pythonVersion: options.pythonVersion, venvPath: options.venvPath, maxDepth: options.maxDepth || 3 }); return this.mergeImportInfo(imports, enhancedImports); } catch (error) { logger.error({ err: error, filePath }, 'Error enhancing import info for Python'); return imports; } } mergeImportInfo(original, enhanced) { if (!enhanced || enhanced.length === 0) { return original; } const originalImportMap = new Map(); for (const imp of original) { originalImportMap.set(imp.path, imp); } const result = []; for (const enhancedImport of enhanced) { const originalImport = originalImportMap.get(enhancedImport.path); if (originalImport) { result.push({ ...originalImport, metadata: { ...originalImport.metadata, ...enhancedImport.metadata }, isCore: enhancedImport.isCore, isExternalPackage: enhancedImport.isExternalPackage }); originalImportMap.delete(enhancedImport.path); } else { result.push(enhancedImport); } } for (const [, remainingImport] of originalImportMap) { result.push(remainingImport); } return result; } }