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.

572 lines (571 loc) 24.6 kB
import { BaseLanguageHandler } from './base.js'; import { getNodeText } from '../astAnalyzer.js'; import logger from '../../../logger.js'; import { ImportResolverFactory } from '../importResolvers/importResolverFactory.js'; import * as path from 'path'; export class JavaScriptHandler extends BaseLanguageHandler { isJsx; constructor(isJsx = false) { super(); this.isJsx = isJsx; } getFunctionQueryPatterns() { return [ 'function_declaration', 'arrow_function', 'method_definition', 'function' ]; } getClassQueryPatterns() { return [ 'class_declaration', 'class', 'class_expression' ]; } getImportQueryPatterns() { return [ 'import_statement', 'import_specifier', 'import_clause' ]; } extractFunctionName(node, sourceCode, _options) { try { if (node.type === 'function_declaration') { const nameNode = node.childForFieldName('name'); return nameNode ? getNodeText(nameNode, sourceCode) : 'anonymous'; } if (node.type === 'arrow_function') { if (node.parent?.type === 'variable_declarator') { const nameNode = node.parent.childForFieldName('name'); if (nameNode) { const name = getNodeText(nameNode, sourceCode); if (name.startsWith('use') && name.length > 3 && name[3] === name[3].toUpperCase()) { return `${name}Hook`; } if (name.startsWith('handle') || name.startsWith('on')) { return `${name}Handler`; } return name; } } if (node.parent?.type === 'pair') { const keyNode = node.parent.childForFieldName('key'); if (keyNode) { const name = getNodeText(keyNode, sourceCode); if (name.startsWith('on') && name.length > 2 && name[2] === name[2].toUpperCase()) { return `${name}Handler`; } return name; } } if (this.isJsx && this.isReactComponent(node, sourceCode)) { if (node.parent?.type === 'variable_declarator') { const nameNode = node.parent.childForFieldName('name'); if (nameNode) { const name = getNodeText(nameNode, sourceCode); if (name[0] === name[0].toUpperCase()) { return `${name}Component`; } } } return 'ReactComponent'; } if (node.parent?.type === 'arguments' && node.parent.parent?.type === 'call_expression') { const callExpr = node.parent.parent; const funcNode = callExpr.childForFieldName('function'); if (funcNode?.type === 'member_expression') { const propertyNode = funcNode.childForFieldName('property'); if (propertyNode) { const methodName = getNodeText(propertyNode, sourceCode); if (['map', 'filter', 'reduce', 'forEach', 'find'].includes(methodName)) { return `${methodName}Callback`; } if (methodName === 'addEventListener') { const args = callExpr.childForFieldName('arguments'); if (args?.firstChild?.type === 'string') { const eventType = getNodeText(args.firstChild, sourceCode).replace(/['"]/g, ''); return `${eventType}EventHandler`; } return 'eventHandler'; } if (['then', 'catch', 'finally'].includes(methodName)) { return `promise${methodName.charAt(0).toUpperCase() + methodName.slice(1)}Callback`; } } } if (funcNode?.type === 'identifier') { const hookName = getNodeText(funcNode, sourceCode); if (hookName === 'useEffect' || hookName === 'useLayoutEffect') { return `${hookName}Callback`; } } } } if (node.type === 'method_definition') { const nameNode = node.childForFieldName('name'); if (nameNode) { const name = getNodeText(nameNode, sourceCode); if (name.startsWith('#')) { return `private_${name.substring(1)}`; } if (this.isReactLifecycleMethod(name)) { return `lifecycle_${name}`; } return name; } } if (node.type === 'function') { if (node.parent?.type === 'variable_declarator') { const nameNode = node.parent.childForFieldName('name'); return nameNode ? getNodeText(nameNode, sourceCode) : 'anonymous'; } if (node.parent?.type === 'parenthesized_expression' && node.parent.parent?.type === 'call_expression') { return 'iife'; } } return 'anonymous'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting JavaScript function name'); return 'anonymous'; } } extractClassName(node, sourceCode) { try { const nameNode = node.childForFieldName('name'); if (nameNode) { return getNodeText(nameNode, sourceCode); } if (node.type === 'class_expression' && node.parent?.type === 'variable_declarator') { const parentNameNode = node.parent.childForFieldName('name'); if (parentNameNode) { return getNodeText(parentNameNode, sourceCode); } } return 'AnonymousClass'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting JavaScript class name'); return 'AnonymousClass'; } } extractImportPath(node, sourceCode) { try { if (node.type === 'import_statement') { const sourceNode = node.childForFieldName('source'); if (sourceNode) { const path = getNodeText(sourceNode, sourceCode); return path.replace(/^['"]|['"]$/g, ''); } } return 'unknown'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting JavaScript import path'); return 'unknown'; } } extractImportedItems(node, sourceCode) { try { if (node.type === 'import_statement') { const items = []; const sourceNode = node.childForFieldName('source'); const sourcePath = sourceNode ? getNodeText(sourceNode, sourceCode).replace(/['"]/g, '') : ''; const clauseNode = node.childForFieldName('import_clause'); if (clauseNode) { const defaultImport = clauseNode.childForFieldName('default'); if (defaultImport) { const name = getNodeText(defaultImport, sourceCode); items.push({ name, path: sourcePath, isDefault: true, isNamespace: false, nodeText: node.text }); } const namedImportsNode = clauseNode.childForFieldName('named_imports'); if (namedImportsNode) { namedImportsNode.descendantsOfType('import_specifier').forEach(specifier => { const nameNode = specifier.childForFieldName('name'); if (nameNode) { const name = getNodeText(nameNode, sourceCode); items.push({ name, path: sourcePath, isDefault: false, isNamespace: false, nodeText: specifier.text }); } }); } const namespaceImportNode = clauseNode.childForFieldName('namespace_import'); if (namespaceImportNode) { const nameNode = namespaceImportNode.childForFieldName('name'); if (nameNode) { const name = getNodeText(nameNode, sourceCode); items.push({ name: `* as ${name}`, path: sourcePath, isDefault: false, isNamespace: true, nodeText: namespaceImportNode.text }); } } } return items.length > 0 ? items : undefined; } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting JavaScript imported items'); return undefined; } } isDefaultImport(node, _sourceCode) { try { if (node.type === 'import_statement') { const clauseNode = node.childForFieldName('import_clause'); if (clauseNode) { const defaultImport = clauseNode.childForFieldName('default'); return !!defaultImport; } } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if JavaScript import is default'); return undefined; } } extractImportAlias(node, sourceCode) { try { if (node.type === 'import_statement') { const clauseNode = node.childForFieldName('import_clause'); if (clauseNode) { const namespaceImportNode = clauseNode.childForFieldName('namespace_import'); if (namespaceImportNode) { const nameNode = namespaceImportNode.childForFieldName('name'); if (nameNode) { return getNodeText(nameNode, sourceCode); } } const namedImportsNode = clauseNode.childForFieldName('named_imports'); if (namedImportsNode) { const specifiers = namedImportsNode.descendantsOfType('import_specifier'); for (const specifier of specifiers) { const aliasNode = specifier.childForFieldName('alias'); if (aliasNode) { return getNodeText(aliasNode, sourceCode); } } } } } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting JavaScript import alias'); return undefined; } } extractFunctionComment(node, _sourceCode) { try { let current = node; if (node.type === 'arrow_function' || node.type === 'function') { if (node.parent?.type === 'variable_declarator') { current = node.parent; if (current.parent?.type === 'variable_declaration') { current = current.parent; } } } const lineStart = _sourceCode.lastIndexOf('\n', current.startIndex) + 1; const textBeforeNode = _sourceCode.substring(0, lineStart).trim(); const jsdocEnd = textBeforeNode.lastIndexOf('*/'); if (jsdocEnd !== -1) { const jsdocStart = textBeforeNode.lastIndexOf('/**', jsdocEnd); if (jsdocStart !== -1) { const comment = textBeforeNode.substring(jsdocStart + 3, jsdocEnd).trim(); const lines = comment.split('\n'); const description = lines .map(line => line.trim().replace(/^\* ?/, '')) .filter(line => !line.startsWith('@')) .join(' ') .trim(); return description; } } 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(2).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 JavaScript function comment'); return undefined; } } isReactComponent(node, sourceCode) { try { const bodyNode = node.childForFieldName('body'); if (bodyNode) { const bodyText = getNodeText(bodyNode, sourceCode); return bodyText.includes('<') && bodyText.includes('/>'); } return false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if function is a React component'); return false; } } isReactLifecycleMethod(name) { const lifecycleMethods = [ 'componentDidMount', 'componentDidUpdate', 'componentWillUnmount', 'shouldComponentUpdate', 'getSnapshotBeforeUpdate', 'componentDidCatch', 'render' ]; return lifecycleMethods.includes(name); } extractClassProperties(node, sourceCode) { const properties = []; try { const classBody = node.childForFieldName('body'); if (!classBody) return properties; classBody.children.forEach(childNode => { if (childNode.type === 'property_definition' || childNode.type === 'field_definition' || childNode.type === 'class_field_definition' || childNode.type === 'public_field_definition' || childNode.type === 'private_field_definition') { const nameNode = childNode.childForFieldName('name'); if (!nameNode) return; const name = getNodeText(nameNode, sourceCode); let type; const typeNode = childNode.childForFieldName('type'); if (typeNode) { type = getNodeText(typeNode, sourceCode); } let accessModifier; const nodeText = childNode.text; if (nodeText.includes('private ') || name.startsWith('#') || childNode.type === 'private_field_definition') { accessModifier = 'private'; } else if (nodeText.includes('protected ')) { accessModifier = 'protected'; } else if (nodeText.includes('public ') || childNode.type === 'public_field_definition') { accessModifier = 'public'; } const isStatic = nodeText.includes('static '); const comment = this.extractPropertyComment(childNode, sourceCode); properties.push({ name: name.startsWith('#') ? name.substring(1) : name, type, accessModifier, isStatic, comment, startLine: childNode.startPosition.row + 1, endLine: childNode.endPosition.row + 1 }); } }); const constructorMethod = this.findConstructorMethod(classBody); if (constructorMethod) { const constructorBody = constructorMethod.childForFieldName('body'); if (constructorBody) { constructorBody.descendantsOfType('assignment_expression').forEach(assignment => { const leftNode = assignment.childForFieldName('left'); if (leftNode && leftNode.text.startsWith('this.')) { const propertyName = leftNode.text.substring(5); if (properties.some(p => p.name === propertyName)) { return; } const comment = this.extractPropertyComment(assignment, sourceCode); properties.push({ name: propertyName, accessModifier: 'public', isStatic: false, comment, startLine: assignment.startPosition.row + 1, endLine: assignment.endPosition.row + 1 }); } }); } } return properties; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting JavaScript class properties'); return properties; } } extractPropertyComment(node, sourceCode) { try { const lineStart = sourceCode.lastIndexOf('\n', node.startIndex) + 1; const textBeforeNode = sourceCode.substring(0, lineStart).trim(); const jsdocEnd = textBeforeNode.lastIndexOf('*/'); if (jsdocEnd !== -1) { const jsdocStart = textBeforeNode.lastIndexOf('/**', jsdocEnd); if (jsdocStart !== -1) { const comment = textBeforeNode.substring(jsdocStart + 3, jsdocEnd).trim(); const lines = comment.split('\n'); const description = lines .map(line => line.trim().replace(/^\* ?/, '')) .filter(line => !line.startsWith('@')) .join(' ') .trim(); return description; } } 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(2).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 JavaScript property comment'); return undefined; } } findConstructorMethod(classBody) { for (const child of classBody.children) { if (child.type === 'method_definition') { const nameNode = child.childForFieldName('name'); if (nameNode && getNodeText(nameNode, '') === 'constructor') { return child; } } } return undefined; } detectFramework(sourceCode) { try { if (sourceCode.includes('React') || sourceCode.includes('react') || sourceCode.includes('jsx') || sourceCode.includes('</>')) { return 'react'; } if (sourceCode.includes('Angular') || sourceCode.includes('@Component') || sourceCode.includes('@NgModule')) { return 'angular'; } if (sourceCode.includes('Vue') || sourceCode.includes('createApp') || sourceCode.includes('<template>')) { return 'vue'; } if (sourceCode.includes('express') || sourceCode.includes('app.get(') || sourceCode.includes('app.post(')) { return 'express'; } return null; } catch (error) { logger.warn({ err: error }, 'Error detecting JavaScript framework'); return null; } } async enhanceImportInfo(filePath, imports, options) { try { const factory = new ImportResolverFactory({ allowedDir: options.allowedDir, outputDir: options.outputDir, maxDepth: options.maxDepth || 3, tsConfig: options.tsConfig }); const resolver = factory.getImportResolver(filePath); if (!resolver) { return imports; } const enhancedImports = await resolver.analyzeImports(filePath, { baseDir: path.dirname(filePath), maxDepth: options.maxDepth || 3, tsConfig: options.tsConfig }); return this.mergeImportInfo(imports, enhancedImports); } catch (error) { logger.error({ err: error, filePath }, 'Error enhancing import info for JavaScript'); 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, isDynamic: enhancedImport.isDynamic, moduleSystem: enhancedImport.moduleSystem || originalImport.moduleSystem }); originalImportMap.delete(enhancedImport.path); } else { result.push(enhancedImport); } } for (const remainingImport of originalImportMap.values()) { result.push(remainingImport); } return result; } }