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