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
JavaScript
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;
}
}