You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

809 lines
26 KiB

const { readFileSync, existsSync, readdirSync } = require('fs')
const { resolve } = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const AstAnalyzer = require('./ast-analyzer')
/**
* 触发器分析模块
* 负责分析组件中的按钮和事件触发器
*/
class TriggerAnalyzer {
constructor() {
this.astAnalyzer = new AstAnalyzer()
}
/**
* 查找API方法的触发器源 - 第一步:第一层触发源分析
* @param {Array} apiMappings - API映射数组
* @returns {Array} 增强的API映射数组
*/
findTriggerSourcesForApiMappings(apiMappings) {
const enhancedApiMappings = []
apiMappings.forEach(moduleMapping => {
const enhancedModuleMapping = {
...moduleMapping,
apiMappings: []
}
moduleMapping.apiMappings.forEach(apiMapping => {
// 第一步:查找第一层触发源
const triggerSources = this.findFirstLevelTriggerSources(
moduleMapping.serviceName, // 使用第一层的serviceName
apiMapping.apiMethodName,
moduleMapping.module
)
// 为所有API映射添加triggerSources字段(即使为空)
enhancedModuleMapping.apiMappings.push({
...apiMapping,
triggerSources: triggerSources || []
})
})
// 返回所有模块映射(包括没有触发源的)
enhancedApiMappings.push(enhancedModuleMapping)
})
return enhancedApiMappings
}
/**
* 查找第一层触发源 - 使用Babel分析真正的调用关系
* @param {string} serviceName - 服务名称
* @param {string} apiMethodName - API方法名称
* @param {string} moduleName - 模块名称
* @returns {Array} 触发器源数组
*/
findFirstLevelTriggerSources(serviceName, apiMethodName, moduleName) {
const triggerSources = []
try {
// 1. 遍历模块中的所有Vue组件文件
const modulePath = resolve(__dirname, `../../src/renderer/modules/${moduleName}`)
if (existsSync(modulePath)) {
const files = readdirSync(modulePath, { recursive: true })
files.forEach(file => {
if (typeof file === 'string' && file.endsWith('.vue')) {
const filePath = resolve(modulePath, file)
const componentName = this.extractComponentNameFromPath(file)
// 2. 使用Babel分析Vue组件中的API调用
const componentTriggerSources = this.analyzeComponentForApiCalls(
filePath,
serviceName,
apiMethodName,
componentName
)
triggerSources.push(...componentTriggerSources)
}
})
}
} catch (error) {
console.warn(`分析模块 ${moduleName} 时出错:`, error.message)
}
// 去重
const uniqueTriggerSources = this.deduplicateTriggerSources(triggerSources)
return uniqueTriggerSources
}
/**
* 查找API方法的触发器源(保留原方法用于后续扩展)
* @param {string} serviceName - 服务名称
* @param {string} apiMethodName - API方法名称
* @param {string} moduleName - 模块名称
* @returns {Array} 触发器源数组
*/
findTriggerSourcesForApiMethod(serviceName, apiMethodName, moduleName) {
const triggerSources = []
// 1. 检查路由组件
const routeComponents = this.getRouteComponents(moduleName)
routeComponents.forEach(route => {
const triggerAnalysis = this.analyzeComponentWithAST(
route.component,
serviceName,
apiMethodName
)
if (triggerAnalysis.triggerSources.length > 0) {
triggerSources.push(...triggerAnalysis.triggerSources)
}
})
// 2. 检查模块中的所有组件
const componentsInModule = this.getModuleComponents(moduleName)
componentsInModule.forEach(componentName => {
const triggerAnalysis = this.analyzeComponentWithAST(
componentName,
serviceName,
apiMethodName
)
if (triggerAnalysis.triggerSources.length > 0) {
triggerSources.push(...triggerAnalysis.triggerSources)
}
})
// 去重
const uniqueTriggerSources = this.deduplicateTriggerSources(triggerSources)
return uniqueTriggerSources
}
/**
* 获取路由组件
* @param {string} moduleName - 模块名称
* @returns {Array} 路由组件数组
*/
getRouteComponents(moduleName) {
const routeComponents = []
try {
const routeConfigPath = resolve(__dirname, '../../src/renderer/router/index.js')
if (existsSync(routeConfigPath)) {
const routeContent = readFileSync(routeConfigPath, 'utf-8')
// 根据模块名称映射到对应的组件名称
const moduleToComponentMap = {
'role-management': 'RoleManagement',
'user-management': 'UserManagement',
'system-settings': 'Settings',
'user-profile': 'UserProfile'
}
const componentName = moduleToComponentMap[moduleName]
if (componentName) {
// 查找包含该组件的路由
const routeMatches = routeContent.match(new RegExp(`component:\\s*${componentName}`, 'g'))
if (routeMatches) {
routeComponents.push({
component: componentName
})
}
}
}
} catch (error) {
// 静默处理错误
}
return routeComponents
}
/**
* 获取模块中的所有组件
* @param {string} moduleName - 模块名称
* @returns {Array} 组件名称数组
*/
getModuleComponents(moduleName) {
const components = []
try {
const modulePath = resolve(__dirname, '../../src/renderer/modules', moduleName)
if (existsSync(modulePath)) {
// 查找views目录
const viewsPath = resolve(modulePath, 'views')
if (existsSync(viewsPath)) {
const viewFiles = readdirSync(viewsPath).filter(file => file.endsWith('.vue'))
viewFiles.forEach(file => {
components.push(file.replace('.vue', ''))
})
}
// 查找components目录
const componentsPath = resolve(modulePath, 'components')
if (existsSync(componentsPath)) {
const componentFiles = readdirSync(componentsPath).filter(file => file.endsWith('.vue'))
componentFiles.forEach(file => {
components.push(file.replace('.vue', ''))
})
}
}
} catch (error) {
// 静默处理错误
}
return components
}
/**
* 使用AST分析组件
* @param {string} componentName - 组件名称
* @param {string} serviceName - 服务名称
* @param {string} apiMethodName - API方法名称
* @returns {Object} 触发器分析结果
*/
analyzeComponentWithAST(componentName, serviceName, apiMethodName) {
const triggerSources = []
try {
const filePath = this.findComponentFile(componentName)
if (!filePath) {
return { triggerSources: [] }
}
const content = readFileSync(filePath, 'utf-8')
// 1. 检查组件的authType属性,如果是public则跳过
const authTypeMatch = content.match(/authType\s*[=:]\s*["']([^"']+)["']/)
if (authTypeMatch && authTypeMatch[1] === 'public') {
return { triggerSources: [] }
}
// 2. 检查组件是否包含目标API调用
if (!content.includes(`${serviceName}.${apiMethodName}`)) {
return { triggerSources: [] }
}
// 3. 使用Babel AST分析找到调用该API的方法
const callingMethods = this.astAnalyzer.findMethodsCallingServiceWithBabel(content, serviceName, apiMethodName)
if (callingMethods.length > 0) {
// 4. 分析每个调用方法的触发源
const methodTriggers = this.analyzeMethodTriggerSources(content, componentName, callingMethods, filePath)
triggerSources.push(...methodTriggers)
} else {
// 如果没有找到按钮触发器,记录页面级触发器
triggerSources.push({
component: componentName,
triggerName: null,
triggerType: 'page'
})
}
} catch (error) {
// 静默处理错误
}
return { triggerSources }
}
/**
* 查找组件文件
* @param {string} componentName - 组件名称
* @returns {string|null} 组件文件路径
*/
findComponentFile(componentName) {
const possiblePaths = [
resolve(__dirname, '../../src/renderer/modules', componentName, 'views', `${componentName}.vue`),
resolve(__dirname, '../../src/renderer/modules', componentName, 'components', `${componentName}.vue`),
resolve(__dirname, '../../src/renderer/components', `${componentName}.vue`),
resolve(__dirname, '../../src/renderer/views', `${componentName}.vue`)
]
for (const path of possiblePaths) {
if (existsSync(path)) {
return path
}
}
return null
}
/**
* 分析方法的触发源
* @param {string} content - 组件内容
* @param {string} componentName - 组件名称
* @param {Array} methodNames - 方法名数组
* @param {string} filePath - 文件路径
* @returns {Array} 触发器源数组
*/
analyzeMethodTriggerSources(content, componentName, methodNames, filePath = null) {
const triggerSources = []
// 提取模板部分
const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/)
if (!templateMatch) {
return triggerSources
}
const templateContent = templateMatch[1]
// 使用正则表达式查找调用这些方法的按钮
methodNames.forEach(methodName => {
// 1. 查找@click="methodName"或@click="methodName("
const clickPattern = new RegExp(`@click\\s*=\\s*["']${methodName}\\s*\\([^)]*\\)["']`, 'g')
const clickMatches = templateContent.match(clickPattern)
if (clickMatches) {
clickMatches.forEach((match, index) => {
// 查找包含这个@click的button标签
const buttonMatch = this.findButtonContainingClick(templateContent, match)
if (buttonMatch) {
const buttonName = this.extractButtonName(buttonMatch)
const triggerType = buttonName ? 'button' : 'button'
triggerSources.push({
component: componentName,
triggerName: buttonName || this.astAnalyzer.generateUniqueButtonName(componentName, methodName),
triggerType: triggerType
})
}
})
}
// 2. 查找@submit.prevent="methodName"的表单提交按钮
const submitPattern = new RegExp(`@submit\\.prevent\\s*=\\s*["']${methodName}["']`, 'g')
const submitMatches = templateContent.match(submitPattern)
if (submitMatches) {
submitMatches.forEach((match, index) => {
// 查找包含这个@submit.prevent的form标签
const formMatch = this.findFormContainingSubmit(templateContent, match)
if (formMatch) {
// 查找表单中的type="submit"按钮
const submitButtonMatch = this.findSubmitButtonInForm(formMatch)
if (submitButtonMatch) {
const buttonName = this.extractButtonName(submitButtonMatch)
const triggerType = buttonName ? 'button' : 'button'
triggerSources.push({
component: componentName,
triggerName: buttonName || this.astAnalyzer.generateUniqueButtonName(componentName, methodName),
triggerType: triggerType
})
} else {
// 如果没有找到具体的提交按钮,记录为form类型
triggerSources.push({
component: componentName,
triggerName: null,
triggerType: 'form'
})
}
}
})
}
})
return triggerSources
}
/**
* 查找包含指定@click的button标签
* @param {string} templateContent - 模板内容
* @param {string} clickMatch - @click匹配
* @returns {string|null} 按钮HTML
*/
findButtonContainingClick(templateContent, clickMatch) {
const clickIndex = templateContent.indexOf(clickMatch)
if (clickIndex === -1) return null
// 从@click位置往前查找最近的<button标签
const beforeClick = templateContent.substring(0, clickIndex)
const lastButtonIndex = beforeClick.lastIndexOf('<button')
if (lastButtonIndex === -1) return null
// 从<button开始往后查找对应的</button>
const afterButton = templateContent.substring(lastButtonIndex)
const buttonEndIndex = afterButton.indexOf('</button>')
if (buttonEndIndex === -1) return null
return afterButton.substring(0, buttonEndIndex + 9) // 包含</button>
}
/**
* 提取按钮名称
* @param {string} buttonHtml - 按钮HTML
* @returns {string|null} 按钮名称
*/
extractButtonName(buttonHtml) {
const nameMatch = buttonHtml.match(/name\s*=\s*["']([^"']+)["']/)
return nameMatch ? nameMatch[1] : null
}
/**
* 查找包含指定@submit.prevent的form标签
* @param {string} templateContent - 模板内容
* @param {string} submitMatch - @submit.prevent匹配
* @returns {string|null} 表单HTML
*/
findFormContainingSubmit(templateContent, submitMatch) {
const submitIndex = templateContent.indexOf(submitMatch)
if (submitIndex === -1) return null
// 从@submit.prevent位置往前查找最近的<form标签
const beforeSubmit = templateContent.substring(0, submitIndex)
const lastFormIndex = beforeSubmit.lastIndexOf('<form')
if (lastFormIndex === -1) return null
// 从<form开始往后查找对应的</form>
const afterForm = templateContent.substring(lastFormIndex)
const formEndIndex = afterForm.indexOf('</form>')
if (formEndIndex === -1) return null
return afterForm.substring(0, formEndIndex + 7) // 包含</form>
}
/**
* 查找表单中的type="submit"按钮
* @param {string} formHtml - 表单HTML
* @returns {string|null} 提交按钮HTML
*/
findSubmitButtonInForm(formHtml) {
const submitButtonPattern = /<button[^>]*type\s*=\s*["']submit["'][^>]*>[\s\S]*?<\/button>/g
const match = submitButtonPattern.exec(formHtml)
return match ? match[0] : null
}
/**
* 去重触发器源
* @param {Array} triggerSources - 触发器源数组
* @returns {Array} 去重后的触发器源数组
*/
deduplicateTriggerSources(triggerSources) {
const seen = new Set()
return triggerSources.filter(trigger => {
const key = `${trigger.component}-${trigger.triggerName}-${trigger.triggerType}`
if (seen.has(key)) {
return false
}
seen.add(key)
return true
})
}
/**
* 从文件路径提取组件名称
* @param {string} filePath - 文件路径
* @returns {string} 组件名称
*/
extractComponentNameFromPath(filePath) {
const fileName = filePath.split('/').pop().split('\\').pop()
return fileName.replace('.vue', '')
}
/**
* 分析Vue组件中的API调用
* @param {string} filePath - 组件文件路径
* @param {string} serviceName - 服务名称
* @param {string} apiMethodName - API方法名称
* @param {string} componentName - 组件名称
* @returns {Array} 触发源数组
*/
analyzeComponentForApiCalls(filePath, serviceName, apiMethodName, componentName) {
const triggerSources = []
try {
const content = readFileSync(filePath, 'utf-8')
// 使用Babel解析Vue组件
const ast = this.parseVueComponent(content)
if (!ast) return triggerSources
// 查找API调用和函数调用关系
const apiCalls = this.findApiCallsWithContext(ast, serviceName, apiMethodName)
// 为每个API调用创建触发源
apiCalls.forEach(call => {
triggerSources.push({
component: componentName,
triggerName: call.triggerName || '',
triggerType: call.triggerType || 'function'
})
})
} catch (error) {
console.warn(`分析组件 ${filePath} 时出错:`, error.message)
}
return triggerSources
}
/**
* 解析Vue组件内容
* @param {string} content - 组件内容
* @returns {Object|null} AST对象
*/
parseVueComponent(content) {
try {
// 提取<script>部分
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/)
if (!scriptMatch) return null
const scriptContent = scriptMatch[1]
// 使用Babel解析JavaScript代码
const ast = parser.parse(scriptContent, {
sourceType: 'module',
plugins: ['jsx', 'decorators-legacy', 'classProperties', 'objectRestSpread']
})
return ast
} catch (error) {
console.warn('解析Vue组件脚本时出错:', error.message)
return null
}
}
/**
* 在AST中查找API调用和函数调用关系
* @param {Object} ast - AST对象
* @param {string} serviceName - 服务名称
* @param {string} apiMethodName - API方法名称
* @returns {Array} API调用信息数组
*/
findApiCallsWithContext(ast, serviceName, apiMethodName) {
const apiCalls = []
const self = this
const functionCallMap = new Map() // 存储函数调用关系
// 第一遍:收集所有函数定义和调用关系
traverse(ast, {
// 收集函数定义
FunctionDeclaration(path) {
if (path.node.id && path.node.id.name) {
functionCallMap.set(path.node.id.name, {
type: 'function',
path: path
})
}
},
// 收集箭头函数和const函数
VariableDeclarator(path) {
if (path.node.id && path.node.id.type === 'Identifier' &&
path.node.init && path.node.init.type === 'ArrowFunctionExpression') {
functionCallMap.set(path.node.id.name, {
type: 'arrowFunction',
path: path
})
}
},
// 收集函数调用关系
CallExpression(path) {
const { node } = path
// 记录函数调用
if (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)
}
}
}
})
// 第二遍:查找API调用并分析调用链
traverse(ast, {
CallExpression(path) {
const { node } = path
// 查找 serviceName.apiMethodName() 调用
if (node.callee.type === 'MemberExpression' &&
node.callee.object.name === serviceName &&
node.callee.property.name === apiMethodName) {
// 查找调用上下文
const context = self.findCallContextWithChain(path, functionCallMap)
apiCalls.push({
triggerName: context.triggerName,
triggerType: context.triggerType
})
}
}
})
return apiCalls
}
/**
* 查找父级函数
* @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
}
/**
* 查找API调用的上下文(包含调用链分析)
* @param {Object} path - Babel路径对象
* @param {Map} functionCallMap - 函数调用关系映射
* @returns {Object} 上下文信息
*/
findCallContextWithChain(path, functionCallMap) {
let currentPath = path
let triggerName = ''
let triggerType = 'function'
// 向上遍历AST,查找触发源
while (currentPath) {
const { node } = currentPath
// 检查是否在Vue 3 Composition API生命周期钩子中
if (node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
['onMounted', 'onCreated', 'onBeforeMount', 'onBeforeCreate', 'onUpdated', 'onBeforeUpdate', 'onUnmounted', 'onBeforeUnmount'].includes(node.callee.name)) {
triggerName = node.callee.name
triggerType = 'lifecycle'
break
}
// 检查是否在Vue 3 Composition API的setup方法中
if (node.type === 'ObjectMethod' && node.key && node.key.name === 'setup') {
triggerName = 'setup'
triggerType = 'lifecycle' // setup是Vue 3的生命周期钩子
break
}
// 检查是否在方法定义中
if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
if (node.id && node.id.name) {
triggerName = node.id.name
triggerType = 'function'
break
}
}
// 检查是否在对象方法中(非setup)
if (node.type === 'ObjectMethod') {
if (node.key && node.key.name && node.key.name !== 'setup') {
triggerName = node.key.name
triggerType = 'method'
break
}
}
// 检查是否在箭头函数中
if (node.type === 'ArrowFunctionExpression') {
// 查找父级的属性名
const parent = currentPath.parent
if (parent && parent.type === 'ObjectProperty' && parent.key) {
const methodName = parent.key.name || ''
if (methodName === 'setup') {
triggerName = 'setup'
triggerType = 'lifecycle'
} else {
triggerName = methodName
triggerType = 'method'
}
break
}
// 检查是否在const声明的箭头函数中
if (parent && parent.type === 'VariableDeclarator' && parent.id) {
const functionName = parent.id.name || ''
triggerName = functionName
triggerType = 'method'
break
}
}
// 检查是否在Vue 2生命周期钩子中
if (node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.object.name === 'this' &&
['mounted', 'created', 'beforeMount', 'beforeCreate'].includes(node.callee.property.name)) {
triggerName = node.callee.property.name
triggerType = 'lifecycle'
break
}
currentPath = currentPath.parentPath
}
return { triggerName, triggerType }
}
/**
* 查找API调用的上下文
* @param {Object} path - Babel路径对象
* @returns {Object} 上下文信息
*/
findCallContext(path) {
let currentPath = path
let triggerName = ''
let triggerType = 'function'
// 向上遍历AST,查找触发源
while (currentPath) {
const { node } = currentPath
// 检查是否在Vue 3 Composition API生命周期钩子中
if (node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
['onMounted', 'onCreated', 'onBeforeMount', 'onBeforeCreate', 'onUpdated', 'onBeforeUpdate', 'onUnmounted', 'onBeforeUnmount'].includes(node.callee.name)) {
triggerName = node.callee.name
triggerType = 'lifecycle'
break
}
// 检查是否在Vue 3 Composition API的setup方法中
if (node.type === 'ObjectMethod' && node.key && node.key.name === 'setup') {
triggerName = 'setup'
triggerType = 'lifecycle' // setup是Vue 3的生命周期钩子
break
}
// 检查是否在方法定义中
if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
if (node.id && node.id.name) {
triggerName = node.id.name
triggerType = 'function'
break
}
}
// 检查是否在对象方法中(非setup)
if (node.type === 'ObjectMethod') {
if (node.key && node.key.name && node.key.name !== 'setup') {
triggerName = node.key.name
triggerType = 'method'
break
}
}
// 检查是否在箭头函数中
if (node.type === 'ArrowFunctionExpression') {
// 查找父级的属性名
const parent = currentPath.parent
if (parent && parent.type === 'ObjectProperty' && parent.key) {
const methodName = parent.key.name || ''
if (methodName === 'setup') {
triggerName = 'setup'
triggerType = 'lifecycle'
} else {
triggerName = methodName
triggerType = 'method'
}
break
}
}
// 检查是否在Vue 2生命周期钩子中
if (node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.object.name === 'this' &&
['mounted', 'created', 'beforeMount', 'beforeCreate'].includes(node.callee.property.name)) {
triggerName = node.callee.property.name
triggerType = 'lifecycle'
break
}
currentPath = currentPath.parentPath
}
return { triggerName, triggerType }
}
}
module.exports = TriggerAnalyzer