|
|
|
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(/<script[^>]*>([\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
|