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.
 
 
 
 
 
 

450 lines
14 KiB

const { readFileSync, existsSync, readdirSync } = require('fs')
const { resolve } = require('path')
const { parse } = require('@vue/compiler-dom')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
/**
* 简化版Trigger分析器
* 从原有trigger-analyzer中提取核心逻辑
*/
class TriggerAnalyzerSimple {
constructor() {
}
/**
* 为API映射查找触发源
* 使用Babel插件追溯API调用控件,记录控件的类型和名称
* @param {Array} apiMappings - API映射数组
* @param {Array} routes - 路由数组
* @returns {Array} 增强的API映射数组
*/
findTriggerSourcesForApiMappings(apiMappings, routes) {
const enhancedApiMappings = apiMappings.map(api => {
// 查找该API的触发源
const triggerSources = this.findTriggerSourcesForApiMethod(
api.serviceName,
api.apiMethodName,
api.module
)
return {
...api,
triggerSources: triggerSources
}
})
return enhancedApiMappings
}
/**
* 查找API方法的触发源
* @param {string} serviceName - 服务名称
* @param {string} apiMethodName - API方法名称
* @param {string} moduleName - 模块名称
* @returns {Array} 触发源数组
*/
findTriggerSourcesForApiMethod(serviceName, apiMethodName, moduleName) {
const triggerSources = []
// 检查模块中的所有组件
const componentsInModule = this.getModuleComponents(moduleName)
componentsInModule.forEach(componentName => {
const componentTriggerSources = this.analyzeComponentForApiCalls(
componentName,
serviceName,
apiMethodName,
moduleName
)
triggerSources.push(...componentTriggerSources)
})
// 去重
return this.deduplicateTriggerSources(triggerSources)
}
/**
* 获取模块中的所有组件
* @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) {
console.warn(`获取模块组件时出错: ${error.message}`)
}
return components
}
/**
* 分析组件中的API调用
* @param {string} componentName - 组件名称
* @param {string} serviceName - 服务名称
* @param {string} apiMethodName - API方法名称
* @param {string} moduleName - 模块名称
* @returns {Array} 触发源数组
*/
analyzeComponentForApiCalls(componentName, serviceName, apiMethodName, moduleName) {
const triggerSources = []
try {
// 查找组件文件,优先在指定模块中查找
const componentPath = this.findComponentFile(componentName, moduleName)
if (!componentPath) {
return triggerSources
}
// 检查组件的authType,如果是public则跳过
const authType = this.getComponentAuthType(componentName, moduleName)
if (authType === 'public') {
return triggerSources
}
const content = readFileSync(componentPath, 'utf-8')
// 使用Babel解析Vue组件
const ast = this.parseVueComponent(content)
if (!ast) return triggerSources
// 查找API调用和函数调用关系
const apiCalls = this.findApiCallsWithContext(ast, serviceName, apiMethodName, componentPath, componentName)
// 为每个API调用创建触发源
apiCalls.forEach(call => {
triggerSources.push({
component: componentName,
module: moduleName, // 添加模块信息
triggerName: call.triggerName || null, // 没有名称则为null
triggerType: call.triggerType || 'function'
})
})
} catch (error) {
// 静默处理错误
}
return triggerSources
}
/**
* 查找组件文件
* @param {string} componentName - 组件名称
* @param {string} moduleName - 模块名称(可选,如果提供则优先在该模块中查找)
* @returns {string|null} 组件文件路径
*/
findComponentFile(componentName, moduleName = null) {
// 如果提供了模块名称,只在该模块中查找,不允许跨模块查找
if (moduleName) {
// 检查views目录
const viewPath = resolve(__dirname, `../../src/renderer/modules/${moduleName}/views/${componentName}.vue`)
if (existsSync(viewPath)) {
return viewPath
}
// 检查components目录
const componentPath = resolve(__dirname, `../../src/renderer/modules/${moduleName}/components/${componentName}.vue`)
if (existsSync(componentPath)) {
return componentPath
}
// 如果在指定模块中没有找到,返回null(不允许跨模块查找)
return null
}
// 如果没有提供模块名称,则在所有模块中查找
const modulesPath = resolve(__dirname, '../../src/renderer/modules')
if (existsSync(modulesPath)) {
const { readdirSync } = require('fs')
const moduleDirs = readdirSync(modulesPath, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name)
for (const moduleDir of moduleDirs) {
// 检查views目录
const viewPath = resolve(__dirname, `../../src/renderer/modules/${moduleDir}/views/${componentName}.vue`)
if (existsSync(viewPath)) {
return viewPath
}
// 检查components目录
const componentPath = resolve(__dirname, `../../src/renderer/modules/${moduleDir}/components/${componentName}.vue`)
if (existsSync(componentPath)) {
return componentPath
}
}
}
return null
}
/**
* 从Vue组件文件中获取authType属性
* @param {string} componentName - 组件名称
* @param {string} moduleName - 模块名称(可选)
* @returns {string|null} authType值
*/
getComponentAuthType(componentName, moduleName = null) {
try {
const componentPath = this.findComponentFile(componentName, moduleName)
if (componentPath) {
const content = readFileSync(componentPath, 'utf-8')
const authTypeMatch = content.match(/<template\s+authType\s*=\s*["']([^"']+)["']/)
if (authTypeMatch) {
return authTypeMatch[1]
}
}
} catch (error) {
// 静默处理错误
}
return null
}
/**
* 解析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', 'typescript']
})
return ast
} catch (error) {
return null
}
}
/**
* 查找API调用并分析上下文
* @param {Object} ast - AST对象
* @param {string} serviceName - 服务名称
* @param {string} apiMethodName - API方法名称
* @param {string} filePath - 文件路径
* @param {string} componentName - 组件名称
* @returns {Array} API调用数组
*/
findApiCallsWithContext(ast, serviceName, apiMethodName, filePath, componentName) {
const apiCalls = []
const self = this
// 第一遍:建立函数调用关系映射
const functionCallMap = new Map()
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)
}
}
}
})
// 第二遍:查找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.findCallContext(path)
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路径对象
* @returns {Object} 上下文信息
*/
findCallContext(path) {
let currentPath = path
let triggerName = null
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'
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 }
}
/**
* 去重触发源
* @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
})
}
}
module.exports = TriggerAnalyzerSimple