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.
449 lines
14 KiB
449 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) { |
|
} |
|
|
|
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
|
|
|