const { readFileSync, existsSync } = require('fs') const { resolve } = require('path') const { parse } = require('@vue/compiler-sfc') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default /** * 调用链追溯器 * 专门用于追溯method类型的触发源到最终的可视控件 */ class CallChainTracer { constructor() { this.debugMode = process.env.ROUTE_MAPPING_DEBUG === 'true' || process.argv.includes('--debug') } /** * 调试日志输出 * @param {string} message - 日志消息 * @param {any} data - 附加数据 */ debugLog(message, data = null) { if (this.debugMode) { const timestamp = new Date().toISOString() if (data) { console.log(`[${timestamp}] [CallChainTracer] ${message}`, data) } else { console.log(`[${timestamp}] [CallChainTracer] ${message}`) } } } /** * 追溯所有method类型的触发源到可视控件 * @param {Array} apiMappings - API映射数组 * @returns {Array} 增强的API映射数组 */ traceMethodTriggersToVisualComponents(apiMappings) { this.debugLog('开始追溯method类型的触发源到可视控件') this.debugLog('API映射数量', apiMappings.length) const enhancedApiMappings = apiMappings.map(module => { this.debugLog(`处理模块: ${module.module}`) const enhancedApiMappings = module.apiMappings.map(api => { if (!api.triggerSources || api.triggerSources.length === 0) { this.debugLog(`API ${api.apiMethodName} 没有触发源,跳过`) return api } this.debugLog(`处理API: ${api.apiMethodName},触发源数量: ${api.triggerSources.length}`) // 只处理triggerType为method的触发源 const enhancedTriggerSources = api.triggerSources.map(trigger => { if (trigger.triggerType === 'method') { this.debugLog(`追溯method类型触发源: ${trigger.triggerName} in ${trigger.component}`) const result = this.traceMethodToVisualComponent( trigger, module.module, module.serviceName, api.apiMethodName ) this.debugLog(`追溯结果:`, result) return result } return trigger }) return { ...api, triggerSources: enhancedTriggerSources } }) return { ...module, apiMappings: enhancedApiMappings } }) this.debugLog('追溯完成') return enhancedApiMappings } /** * 追溯单个method到可视控件 * @param {Object} trigger - 触发源对象 * @param {string} moduleName - 模块名称 * @param {string} serviceName - 服务名称 * @param {string} apiMethodName - API方法名称 * @returns {Object} 增强的触发源对象 */ traceMethodToVisualComponent(trigger, moduleName, serviceName, apiMethodName) { this.debugLog(`开始追溯method: ${trigger.triggerName} in ${trigger.component}`) try { // 查找组件文件 const componentPath = this.findComponentFile(trigger.component, moduleName) this.debugLog(`查找组件文件: ${trigger.component} in ${moduleName}`, componentPath) if (!componentPath) { this.debugLog(`未找到组件文件: ${trigger.component}`) return trigger } const content = readFileSync(componentPath, 'utf-8') this.debugLog(`读取组件文件成功,内容长度: ${content.length}`) // 解析Vue组件 const ast = this.parseVueComponent(content) this.debugLog(`解析Vue组件结果:`, ast ? '成功' : '失败') if (!ast) { this.debugLog(`Vue组件解析失败`) return trigger } // 建立函数调用关系映射 const functionCallMap = this.buildFunctionCallMap(ast) this.debugLog(`建立函数调用关系映射,映射数量: ${functionCallMap.size}`) // 追溯method到可视控件 const visualContext = this.traceToVisualComponent( trigger.triggerName, functionCallMap, componentPath, trigger.component ) this.debugLog(`追溯可视控件结果:`, visualContext) if (visualContext) { const result = { ...trigger, triggerName: visualContext.triggerName, triggerType: visualContext.triggerType } this.debugLog(`追溯成功,返回结果:`, result) return result } this.debugLog(`追溯失败,返回原始trigger`) return trigger } catch (error) { this.debugLog(`追溯过程中发生错误:`, error.message) return trigger } } /** * 查找组件文件 * @param {string} componentName - 组件名称 * @param {string} moduleName - 模块名称 * @returns {string|null} 组件文件路径 */ findComponentFile(componentName, moduleName) { this.debugLog(`查找组件文件: ${componentName} in ${moduleName}`) // 优先在指定模块中查找 if (moduleName) { // 检查views目录 const viewPath = resolve(__dirname, `../../src/renderer/modules/${moduleName}/views/${componentName}.vue`) this.debugLog(`检查views路径: ${viewPath}`, existsSync(viewPath)) if (existsSync(viewPath)) { this.debugLog(`找到组件文件: ${viewPath}`) return viewPath } // 检查components目录 const componentPath = resolve(__dirname, `../../src/renderer/modules/${moduleName}/components/${componentName}.vue`) this.debugLog(`检查components路径: ${componentPath}`, existsSync(componentPath)) if (existsSync(componentPath)) { this.debugLog(`找到组件文件: ${componentPath}`) return componentPath } } this.debugLog(`未找到组件文件: ${componentName}`) return null } /** * 解析Vue组件内容 * @param {string} content - 组件内容 * @returns {Object|null} AST对象 */ parseVueComponent(content) { try { // 提取script部分 const scriptMatch = content.match(/]*>([\s\S]*?)<\/script>/) this.debugLog(`提取script部分:`, scriptMatch ? '成功' : '失败') if (!scriptMatch) { this.debugLog(`未找到script标签`) return null } const scriptContent = scriptMatch[1] this.debugLog(`script内容长度: ${scriptContent.length}`) // 使用Babel解析JavaScript const ast = parser.parse(scriptContent, { sourceType: 'module', plugins: ['jsx', 'typescript'] }) this.debugLog(`Babel解析结果:`, ast ? '成功' : '失败') return ast } catch (error) { this.debugLog(`解析Vue组件时发生错误:`, error.message) return null } } /** * 建立函数调用关系映射 * @param {Object} ast - AST对象 * @returns {Map} 函数调用关系映射 */ buildFunctionCallMap(ast) { const functionCallMap = new Map() const self = this traverse(ast, { CallExpression(path) { const { node } = path if (node.callee && node.callee.type === 'Identifier') { const functionName = node.callee.name const parentFunction = self.findParentFunction(path) if (parentFunction) { if (!functionCallMap.has(functionName)) { functionCallMap.set(functionName, { callers: [] }) } const funcInfo = functionCallMap.get(functionName) if (!funcInfo.callers) funcInfo.callers = [] funcInfo.callers.push(parentFunction) } } } }) return functionCallMap } /** * 查找父级函数 * @param {Object} path - Babel路径对象 * @returns {string|null} 父级函数名 */ findParentFunction(path) { let currentPath = path.parentPath while (currentPath) { const { node } = currentPath if (node.type === 'FunctionDeclaration' && node.id) { return node.id.name } if (node.type === 'VariableDeclarator' && node.id && node.id.type === 'Identifier' && node.init && node.init.type === 'ArrowFunctionExpression') { return node.id.name } if (node.type === 'ObjectMethod' && node.key) { return node.key.name } currentPath = currentPath.parentPath } return null } /** * 追溯method到可视控件 * @param {string} methodName - 方法名称 * @param {Map} functionCallMap - 函数调用关系映射 * @param {string} filePath - 组件文件路径 * @param {string} componentName - 组件名称 * @returns {Object|null} 可视控件上下文 */ traceToVisualComponent(methodName, functionCallMap, filePath, componentName) { this.debugLog(`追溯method到可视控件: ${methodName}`) try { // 读取组件文件内容 const content = readFileSync(filePath, 'utf-8') // 分析模板中的事件绑定 this.debugLog(`分析模板中的事件绑定: ${methodName}`) const visualContext = this.analyzeTemplateForMethod(content, methodName, componentName, filePath) this.debugLog(`模板分析结果:`, visualContext) if (visualContext) { this.debugLog(`在模板中找到事件绑定`) return visualContext } // 如果模板中没有找到,检查是否有其他方法调用了这个方法 this.debugLog(`模板中未找到,检查方法调用关系`) const callerContext = this.findMethodCaller(methodName, functionCallMap) this.debugLog(`找到调用者:`, callerContext) if (callerContext) { // 检查调用者是否是生命周期钩子 if (this.isLifecycleHook(callerContext)) { this.debugLog(`调用者是生命周期钩子: ${callerContext}`) return { triggerName: '', triggerType: 'page' } } // 继续递归追溯 this.debugLog(`递归追溯调用者: ${callerContext}`) return this.traceToVisualComponent(callerContext, functionCallMap, filePath, componentName) } this.debugLog(`未找到调用者,追溯失败`) } catch (error) { this.debugLog(`追溯可视控件时发生错误:`, error.message) } return null } /** * 分析模板中的方法调用 * @param {string} content - 组件内容 * @param {string} methodName - 方法名称 * @param {string} componentName - 组件名称 * @param {string} filePath - 组件文件路径 * @returns {Object|null} 可视控件上下文 */ analyzeTemplateForMethod(content, methodName, componentName, filePath) { this.debugLog(`分析模板中的方法调用: ${methodName}`) try { // 使用Vue编译器解析单文件组件 const { descriptor } = parse(content) this.debugLog(`Vue编译器解析结果:`, descriptor ? '成功' : '失败') if (!descriptor.template) { this.debugLog(`未找到template部分`) return null } // 解析模板AST const templateAst = descriptor.template.ast this.debugLog(`模板AST解析结果:`, templateAst ? '成功' : '失败') if (!templateAst) { this.debugLog(`模板AST解析失败`) return null } // 遍历模板AST,查找事件绑定 this.debugLog(`开始遍历模板AST查找事件绑定`) const result = this.findEventBindingInTemplate(templateAst, methodName, componentName, filePath) this.debugLog(`模板AST遍历结果:`, result) return result } catch (error) { this.debugLog(`分析模板时发生错误:`, error.message) return null } } /** * 在模板AST中查找事件绑定 * @param {Object} node - AST节点 * @param {string} methodName - 方法名称 * @param {string} componentName - 组件名称 * @param {string} filePath - 组件文件路径 * @returns {Object|null} 可视组件上下文 */ findEventBindingInTemplate(node, methodName, componentName, filePath) { this.debugLog(`查找事件绑定: ${methodName} in ${node.tag || 'root'}`) if (!node) { this.debugLog(`节点为空`) return null } // 检查当前节点是否有事件绑定 if (node.props) { this.debugLog(`检查节点属性,属性数量: ${node.props.length}`) for (const prop of node.props) { if (prop.type === 7) { // DIRECTIVE类型 const eventName = prop.name this.debugLog(`检查指令: ${eventName}`) if (eventName === 'on' && prop.arg) { const eventType = prop.arg.content this.debugLog(`检查事件类型: ${eventType}`) if (['click', 'submit', 'change', 'input'].includes(eventType)) { this.debugLog(`匹配事件类型: ${eventType}`) // 检查事件处理函数 if (prop.exp && this.isMethodCall(prop.exp, methodName)) { this.debugLog(`找到匹配的方法调用: ${methodName}`) const context = this.createVisualContext(node, eventType, componentName, methodName, filePath) this.debugLog(`创建可视上下文:`, context) // 如果找到的是form,需要查找其中的submit按钮 if (context.triggerType === 'form') { this.debugLog(`找到form,查找submit按钮`) const submitButton = this.findSubmitButtonInForm(node, componentName, methodName, filePath) if (submitButton) { this.debugLog(`找到submit按钮:`, submitButton) return submitButton } } return context } else { this.debugLog(`事件处理函数不匹配: ${methodName}`) } } } } } } // 递归检查子节点 if (node.children) { this.debugLog(`递归检查子节点,子节点数量: ${node.children.length}`) for (const child of node.children) { if (child.type === 1) { // ELEMENT类型 const result = this.findEventBindingInTemplate(child, methodName, componentName, filePath) if (result) { this.debugLog(`在子节点中找到结果:`, result) return result } } } } this.debugLog(`未找到事件绑定`) return null } /** * 在form中查找submit按钮 * @param {Object} formNode - form AST节点 * @param {string} componentName - 组件名称 * @param {string} methodName - 方法名称 * @param {string} filePath - 组件文件路径 * @returns {Object|null} submit按钮上下文 */ findSubmitButtonInForm(formNode, componentName, methodName, filePath) { if (!formNode || !formNode.children) return null // 递归查找submit按钮 for (const child of formNode.children) { if (child.type === 1) { // ELEMENT类型 if (child.tag === 'button') { // 检查是否是submit按钮 const props = child.props || [] const attributes = {} props.forEach(prop => { if (prop.type === 6) { // ATTRIBUTE attributes[prop.name] = prop.value ? prop.value.content : '' } }) if (attributes.type === 'submit') { let triggerName = attributes.name if (!triggerName) { // 生成唯一的name属性:组件名+唯一尾缀 triggerName = `${componentName.toLowerCase()}-submit-${this.generateUniqueSuffix()}` } return { triggerName: triggerName, triggerType: 'button' } } } // 递归查找子元素 const result = this.findSubmitButtonInForm(child, componentName, methodName, filePath) if (result) return result } } return null } /** * 检查表达式是否是方法调用 * @param {Object} exp - 表达式节点 * @param {string} methodName - 方法名称 * @returns {boolean} 是否是方法调用 */ isMethodCall(exp, methodName) { this.debugLog(`检查表达式是否是方法调用: ${methodName}`) this.debugLog(`表达式类型: ${exp.type}`, exp) if (!exp) { this.debugLog(`表达式为空`) return false } // 简单的方法调用:methodName if (exp.type === 4 && exp.content === methodName) { // SIMPLE_EXPRESSION this.debugLog(`匹配简单方法调用: ${methodName}`) return true } // 带参数的方法调用:methodName(...) - 检查content是否以methodName开头 if (exp.type === 4 && exp.content && exp.content.startsWith(methodName + '(')) { this.debugLog(`匹配带参数的方法调用: ${methodName}`) return true } // 带参数的方法调用:methodName(...) if (exp.type === 8) { // COMPOUND_EXPRESSION this.debugLog(`检查复合表达式`) const children = exp.children if (children && children.length >= 1) { const firstChild = children[0] this.debugLog(`第一个子节点:`, firstChild) if (firstChild.type === 4 && firstChild.content === methodName) { this.debugLog(`匹配复合表达式中的方法调用: ${methodName}`) return true } } } // 检查AST中的方法调用 if (exp.ast) { this.debugLog(`检查AST中的方法调用`) const result = this.checkAstMethodCall(exp.ast, methodName) this.debugLog(`AST检查结果:`, result) return result } this.debugLog(`未匹配任何方法调用模式`) return false } /** * 检查AST中的方法调用 * @param {Object} ast - AST节点 * @param {string} methodName - 方法名称 * @returns {boolean} 是否是方法调用 */ checkAstMethodCall(ast, methodName) { if (!ast) return false // 简单标识符:methodName if (ast.type === 'Identifier' && ast.name === methodName) { return true } // 方法调用:methodName(...) if (ast.type === 'CallExpression') { if (ast.callee && ast.callee.type === 'Identifier' && ast.callee.name === methodName) { return true } } return false } /** * 创建可视组件上下文 * @param {Object} node - AST节点 * @param {string} eventType - 事件类型 * @param {string} componentName - 组件名称 * @param {string} methodName - 方法名称 * @param {string} filePath - 组件文件路径 * @returns {Object} 可视组件上下文 */ createVisualContext(node, eventType, componentName, methodName, filePath) { const tag = node.tag const props = node.props || [] // 提取元素属性 const attributes = {} props.forEach(prop => { if (prop.type === 6) { // ATTRIBUTE attributes[prop.name] = prop.value ? prop.value.content : '' } }) let triggerName = attributes.name || attributes.id || '' let triggerType = 'button' // 默认类型 // 根据标签类型确定触发源类型 if (tag === 'form') { triggerType = 'form' } else if (tag === 'button') { triggerType = 'button' } else if (tag === 'input') { triggerType = 'input' } else if (tag === 'select') { triggerType = 'select' } else if (tag === 'textarea') { triggerType = 'textarea' } else { // 其他元素类型 triggerType = 'element' } // 如果没有名称,生成一个 if (!triggerName) { triggerName = `${componentName.toLowerCase()}-${tag}-${this.generateUniqueSuffix()}` } return { triggerName: triggerName, triggerType: triggerType } } /** * 查找方法的调用者 * @param {string} methodName - 方法名称 * @param {Map} functionCallMap - 函数调用关系映射 * @returns {string|null} 调用者方法名 */ findMethodCaller(methodName, functionCallMap) { const funcInfo = functionCallMap.get(methodName) if (funcInfo && funcInfo.callers && funcInfo.callers.length > 0) { // 返回第一个调用者 return funcInfo.callers[0] } return null } /** * 检查方法名是否是生命周期钩子 * @param {string} methodName - 方法名称 * @returns {boolean} 是否是生命周期钩子 */ isLifecycleHook(methodName) { const lifecycleHooks = [ 'onMounted', 'onCreated', 'onBeforeMount', 'onBeforeCreate', 'onUpdated', 'onBeforeUpdate', 'onUnmounted', 'onBeforeUnmount', 'mounted', 'created', 'beforeMount', 'beforeCreate', 'updated', 'beforeUpdate', 'unmounted', 'beforeUnmount', 'setup' ] return lifecycleHooks.includes(methodName) } /** * 生成唯一后缀 * @returns {string} 唯一后缀 */ generateUniqueSuffix() { return Math.random().toString(36).substr(2, 6) } } module.exports = CallChainTracer