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.

429 lines (428 loc) 16.6 kB
import { BaseLanguageHandler } from './base.js'; import { getNodeText } from '../astAnalyzer.js'; import logger from '../../../logger.js'; import path from 'path'; export class VueHandler extends BaseLanguageHandler { options; getFunctionQueryPatterns() { return [ 'script_element', 'element', 'method_definition', 'function_declaration', 'arrow_function', 'property' ]; } getClassQueryPatterns() { return [ 'document', 'script_element', 'template_element', 'style_element' ]; } getImportQueryPatterns() { return [ 'import_statement', 'import_declaration', 'element' ]; } extractFunctionName(node, sourceCode, _options) { try { if (node.type === 'script_element') { return 'script_section'; } if (node.type === 'element' && this.isTemplateElement(node, sourceCode)) { return 'template_section'; } if (node.type === 'element' && this.isStyleElement(node, sourceCode)) { return 'style_section'; } if (node.type === 'method_definition') { const nameNode = node.childForFieldName('name'); if (nameNode) { const name = getNodeText(nameNode, sourceCode); if (this.isVueLifecycleHook(name)) { return `lifecycle_${name}`; } if (this.isInComputedSection(node, sourceCode)) { return `computed_${name}`; } if (this.isInWatchSection(node, sourceCode)) { return `watch_${name}`; } if (this.isInMethodsSection(node, sourceCode)) { return `method_${name}`; } return name; } } if (node.type === 'function_declaration') { const nameNode = node.childForFieldName('name'); if (nameNode) { return getNodeText(nameNode, sourceCode); } } if (node.type === 'arrow_function') { if (node.parent?.type === 'pair') { const keyNode = node.parent.childForFieldName('key'); if (keyNode) { const key = getNodeText(keyNode, sourceCode); if (this.isVueLifecycleHook(key)) { return `lifecycle_${key}`; } if (this.isInComputedSection(node.parent, sourceCode)) { return `computed_${key}`; } if (this.isInWatchSection(node.parent, sourceCode)) { return `watch_${key}`; } if (this.isInMethodsSection(node.parent, sourceCode)) { return `method_${key}`; } return key; } } return 'arrow_function'; } if (node.type === 'property') { const keyNode = node.childForFieldName('key'); if (keyNode) { const key = getNodeText(keyNode, sourceCode); if (['data', 'computed', 'methods', 'watch', 'props', 'components'].includes(key)) { return `vue_${key}`; } return key; } } return 'vue_element'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Vue function name'); return 'vue_element'; } } isTemplateElement(node, sourceCode) { try { const tagNameNode = node.childForFieldName('tag_name'); return tagNameNode ? getNodeText(tagNameNode, sourceCode) === 'template' : false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if node is a template element'); return false; } } isStyleElement(node, sourceCode) { try { const tagNameNode = node.childForFieldName('tag_name'); return tagNameNode ? getNodeText(tagNameNode, sourceCode) === 'style' : false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if node is a style element'); return false; } } isVueLifecycleHook(name) { const lifecycleHooks = [ 'beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'activated', 'deactivated', 'beforeDestroy', 'destroyed', 'beforeUnmount', 'unmounted', 'errorCaptured', 'renderTracked', 'renderTriggered', 'serverPrefetch' ]; return lifecycleHooks.includes(name); } isInComputedSection(node, sourceCode) { try { let current = node; while (current.parent) { current = current.parent; if (current.type === 'pair') { const keyNode = current.childForFieldName('key'); if (keyNode && getNodeText(keyNode, sourceCode) === 'computed') { return true; } } } return false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if node is in computed section'); return false; } } isInWatchSection(node, sourceCode) { try { let current = node; while (current.parent) { current = current.parent; if (current.type === 'pair') { const keyNode = current.childForFieldName('key'); if (keyNode && getNodeText(keyNode, sourceCode) === 'watch') { return true; } } } return false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if node is in watch section'); return false; } } isInMethodsSection(node, sourceCode) { try { let current = node; while (current.parent) { current = current.parent; if (current.type === 'pair') { const keyNode = current.childForFieldName('key'); if (keyNode && getNodeText(keyNode, sourceCode) === 'methods') { return true; } } } return false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if node is in methods section'); return false; } } extractClassName(node, sourceCode) { try { if (node.type === 'document') { const componentName = this.extractComponentName(node, sourceCode); if (componentName) { return `Vue_${componentName}`; } if (this.options?.filePath) { const filename = path.basename(this.options.filePath, path.extname(this.options.filePath)); return `Vue_${filename}`; } } else if (node.type === 'script_element') { return 'Vue_Script'; } else if (node.type === 'element') { if (this.isTemplateElement(node, sourceCode)) { return 'Vue_Template'; } else if (this.isStyleElement(node, sourceCode)) { return 'Vue_Style'; } } return 'Vue_Component'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Vue class name'); return 'Vue_Component'; } } extractComponentName(node, sourceCode) { try { let scriptNode = null; for (let i = 0; i < node.childCount; i++) { const child = node.child(i); if (child?.type === 'script_element') { scriptNode = child; break; } } if (scriptNode) { const scriptContent = getNodeText(scriptNode, sourceCode); const nameMatch = scriptContent.match(/name\s*:\s*['"]([^'"]+)['"]/); if (nameMatch && nameMatch[1]) { return nameMatch[1]; } const defineComponentMatch = scriptContent.match(/defineComponent\s*\(\s*\{\s*name\s*:\s*['"]([^'"]+)['"]/); if (defineComponentMatch && defineComponentMatch[1]) { return defineComponentMatch[1]; } const classMatch = scriptContent.match(/export\s+default\s+class\s+(\w+)/); if (classMatch && classMatch[1]) { return classMatch[1]; } } return null; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Vue component name'); return null; } } extractImportPath(node, sourceCode) { try { if (node.type === 'import_statement' || node.type === 'import_declaration') { const sourceNode = node.childForFieldName('source'); if (sourceNode) { return getNodeText(sourceNode, sourceCode).replace(/^["']|["']$/g, ''); } } else if (node.type === 'element') { if (this.isScriptElement(node, sourceCode) || this.isStyleElement(node, sourceCode)) { const startTagNode = node.childForFieldName('start_tag'); if (startTagNode) { const attributesNode = startTagNode.childForFieldName('attributes'); if (attributesNode) { for (let i = 0; i < attributesNode.childCount; i++) { const attr = attributesNode.child(i); if (attr?.type === 'attribute') { const nameNode = attr.childForFieldName('name'); if (nameNode && getNodeText(nameNode, sourceCode) === 'src') { const valueNode = attr.childForFieldName('value'); if (valueNode) { return getNodeText(valueNode, sourceCode).replace(/^["']|["']$/g, ''); } } } } } } } } return 'unknown'; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Vue import path'); return 'unknown'; } } isScriptElement(node, sourceCode) { try { const tagNameNode = node.childForFieldName('tag_name'); return tagNameNode ? getNodeText(tagNameNode, sourceCode) === 'script' : false; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error checking if node is a script element'); return false; } } findScriptNode(node, sourceCode) { try { for (let i = 0; i < node.childCount; i++) { const child = node.child(i); if (child && this.isScriptElement(child, sourceCode)) { return child; } } return null; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error finding script node'); return null; } } extractFunctionComment(node, _sourceCode) { try { const current = node; let prev = current.previousNamedSibling; while (prev && prev.type !== 'comment') { prev = prev.previousNamedSibling; } if (prev && prev.type === 'comment') { if (prev.text.startsWith('/**')) { return prev.text .replace(/^\/\*\*\s*|\s*\*\/$/g, '') .replace(/^\s*\*\s*/mg, '') .trim(); } return prev.text .replace(/^\/\/\s*/mg, '') .trim(); } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Vue function comment'); return undefined; } } extractClassComment(node, sourceCode) { try { if (node.type === 'document') { const scriptNode = this.findScriptNode(node, sourceCode); if (scriptNode) { const scriptContent = getNodeText(scriptNode, sourceCode); const lines = scriptContent.split('\n'); for (let i = 0; i < lines.length; i++) { if (lines[i].includes('export default')) { const commentLines = []; let j = i - 1; while (j >= 0 && lines[j].trim() === '') { j--; } while (j >= 0 && (lines[j].trim().startsWith('//') || lines[j].trim().startsWith('*'))) { const commentLine = lines[j].trim() .replace(/^\/\/\s*/, '') .replace(/^\*\s*/, '') .trim(); if (commentLine) { commentLines.unshift(commentLine); } j--; } if (commentLines.length > 0) { return commentLines.join(' '); } break; } } } } return undefined; } catch (error) { logger.warn({ err: error, nodeType: node.type }, 'Error extracting Vue class comment'); return undefined; } } detectFramework(sourceCode) { try { if (sourceCode.includes('defineComponent') || sourceCode.includes('setup()') || sourceCode.includes('ref(') || sourceCode.includes('reactive(')) { return 'vue3-composition'; } if (sourceCode.includes('Vue.createApp') || sourceCode.includes('createApp(')) { return 'vue3-options'; } if (sourceCode.includes('Vue.component') || sourceCode.includes('new Vue(') || sourceCode.includes('Vue.extend')) { return 'vue2'; } if (sourceCode.includes('Vuex') || sourceCode.includes('createStore') || sourceCode.includes('mapState') || sourceCode.includes('mapGetters')) { return 'vuex'; } if (sourceCode.includes('Vue-Router') || sourceCode.includes('createRouter') || sourceCode.includes('useRouter')) { return 'vue-router'; } return 'vue'; } catch (error) { logger.warn({ err: error }, 'Error detecting Vue framework'); return 'vue'; } } }