const { readFileSync, existsSync, readdirSync } = require('fs') const { resolve } = require('path') const parser = require('@babel/parser') const traverse = require('@babel/traverse').default const { parse } = require('@vue/compiler-dom') /** * 基于AST的组件关系分析器 * 使用vue-template-compiler解析模板,babel解析JS部分 */ class ComponentRelationshipAnalyzerAST { constructor() { this.componentRelationships = [] this.processedComponents = new Set() } /** * 分析组件关系 * @param {Array} routes - 路由数组 * @param {Array} apiMappings - API映射数组 * @returns {Array} 组件关系列表 */ analyzeComponentRelationships(routes, apiMappings) { this.componentRelationships = [] this.processedComponents.clear() // 获取所有顶级组件(routes中的组件)并分配ID const topLevelComponents = routes.map((route, index) => ({ id: index + 1, // 为routes分配ID component: route.component, module: this.getModuleFromRoute(route), path: route.path })) // 获取所有触发源组件并分配ID let triggerSourceId = 1 const triggerSourceComponents = this.extractTriggerSourceComponents(apiMappings).map(component => ({ id: triggerSourceId++, // 为triggerSource分配ID ...component })) console.log(`🔍 开始分析组件关系,触发源组件数量: ${triggerSourceComponents.length}`) // 为每个触发源组件分析其与顶级组件的关系 triggerSourceComponents.forEach(triggerComponent => { this.analyzeComponentToTopLevel(triggerComponent, topLevelComponents) }) console.log(`🔍 分析完成,找到 ${this.componentRelationships.length} 个组件关系`) return this.componentRelationships } /** * 从路由中获取模块信息 * @param {Object} route - 路由对象 * @returns {string} 模块名称 */ getModuleFromRoute(route) { // 直接使用路由中的module字段,不再进行推断 return route.module || 'unknown' } /** * 提取所有触发源组件 * @param {Array} apiMappings - API映射数组 * @returns {Array} 触发源组件数组 */ extractTriggerSourceComponents(apiMappings) { const components = [] apiMappings.forEach(apiMapping => { if (apiMapping.triggerSources) { apiMapping.triggerSources.forEach(triggerSource => { components.push({ component: triggerSource.component, module: triggerSource.module, triggerName: triggerSource.triggerName, triggerType: triggerSource.triggerType, apiMethodName: apiMapping.apiMethodName, apiModule: apiMapping.module }) }) } }) return components } /** * 分析组件到顶级组件的关系 * @param {Object} triggerComponent - 触发源组件 * @param {Array} topLevelComponents - 顶级组件数组 */ analyzeComponentToTopLevel(triggerComponent, topLevelComponents) { const triggerKey = `${triggerComponent.id}:${triggerComponent.module}:${triggerComponent.component}` // 避免重复处理同一个触发源 if (this.processedComponents.has(triggerKey)) { console.log(` ⏭️ 跳过重复处理: ${triggerKey}`) return } this.processedComponents.add(triggerKey) // 如果触发源组件本身就是顶级组件,直接建立关系 const isTopLevel = topLevelComponents.find(top => top.component === triggerComponent.component && top.module === triggerComponent.module ) if (isTopLevel) { this.componentRelationships.push({ triggerSourceId: triggerComponent.id, // 使用ID引用 routeId: isTopLevel.id, // 使用ID引用 relationshipType: 'self' }) return } // 查找父组件关系 const parentComponents = this.findParentComponentsAST(triggerComponent) let hasRelationship = false if (parentComponents.length > 0) { parentComponents.forEach(parent => { // 递归查找父组件的父组件,直到找到顶级组件 const found = this.findTopLevelAncestors(parent, triggerComponent, topLevelComponents) if (found) hasRelationship = true }) } // 如果没有找到父组件关系,尝试直接匹配顶级组件 if (!hasRelationship) { const directMatch = topLevelComponents.find(top => top.component === triggerComponent.component || top.module === triggerComponent.module ) if (directMatch) { this.componentRelationships.push({ triggerSourceId: triggerComponent.id, routeId: directMatch.id, relationshipType: 'direct' }) hasRelationship = true } } // 最后的兜底:确保每个触发源都至少有一个关系记录 if (!hasRelationship) { this.componentRelationships.push({ triggerSourceId: triggerComponent.id, routeId: 1, // 默认关联到第一个路由 relationshipType: 'fallback' }) } } /** * 使用AST解析查找组件的父组件 * @param {Object} component - 组件信息 * @returns {Array} 父组件数组 */ findParentComponentsAST(component) { const parents = [] try { // 查找所有模块中的组件,看哪些引用了当前组件 const modulesPath = resolve(__dirname, '../../src/renderer/modules') if (existsSync(modulesPath)) { const moduleDirs = readdirSync(modulesPath, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name) moduleDirs.forEach(moduleName => { const parentComponents = this.findComponentsInModuleAST(moduleName, component) parents.push(...parentComponents) }) } } catch (error) { // 静默处理错误 } return parents } /** * 在指定模块中使用AST解析查找引用目标组件的组件 * @param {string} moduleName - 模块名称 * @param {Object} targetComponent - 目标组件 * @returns {Array} 父组件数组 */ findComponentsInModuleAST(moduleName, targetComponent) { const parents = [] try { const modulePath = resolve(__dirname, '../../src/renderer/modules', moduleName) if (!existsSync(modulePath)) return parents // 查找views目录 const viewsPath = resolve(modulePath, 'views') if (existsSync(viewsPath)) { const viewFiles = readdirSync(viewsPath).filter(file => file.endsWith('.vue')) viewFiles.forEach(file => { const componentName = file.replace('.vue', '') if (this.componentReferencesTargetAST(viewsPath, file, targetComponent)) { console.log(`🔍 找到父组件: ${moduleName}:${componentName} -> ${targetComponent.module}:${targetComponent.component}`) parents.push({ component: componentName, module: moduleName, type: 'view' }) } }) } // 查找components目录 const componentsPath = resolve(modulePath, 'components') if (existsSync(componentsPath)) { const componentFiles = readdirSync(componentsPath).filter(file => file.endsWith('.vue')) componentFiles.forEach(file => { const componentName = file.replace('.vue', '') if (this.componentReferencesTargetAST(componentsPath, file, targetComponent)) { console.log(`🔍 找到父组件: ${moduleName}:${componentName} -> ${targetComponent.module}:${targetComponent.component}`) parents.push({ component: componentName, module: moduleName, type: 'component' }) } }) } } catch (error) { // 静默处理错误 } return parents } /** * 使用AST解析检查组件是否引用了目标组件 * @param {string} dirPath - 目录路径 * @param {string} fileName - 文件名 * @param {Object} targetComponent - 目标组件 * @returns {boolean} 是否引用 */ componentReferencesTargetAST(dirPath, fileName, targetComponent) { try { const filePath = resolve(dirPath, fileName) const content = readFileSync(filePath, 'utf-8') // 解析Vue单文件组件 const { script, template } = this.parseVueSFC(content) // 检查script部分的import和components注册 if (script && this.analyzeScriptAST(script, targetComponent)) { return true } // 检查template部分的使用 if (template && this.analyzeTemplateAST(template, targetComponent)) { return true } return false } catch (error) { return false } } /** * 解析Vue单文件组件 * @param {string} content - 文件内容 * @returns {Object} 解析结果 */ parseVueSFC(content) { const result = { script: null, template: null } try { // 提取script部分 const scriptMatch = content.match(/]*>([\s\S]*?)<\/script>/) if (scriptMatch) { result.script = scriptMatch[1] } // 提取template部分 const templateMatch = content.match(/]*>([\s\S]*?)<\/template>/) if (templateMatch) { result.template = templateMatch[1] } } catch (error) { // 静默处理错误 } return result } /** * 使用Babel AST分析script部分 * @param {string} scriptContent - script内容 * @param {Object} targetComponent - 目标组件 * @returns {boolean} 是否引用 */ analyzeScriptAST(scriptContent, targetComponent) { try { const ast = parser.parse(scriptContent, { sourceType: 'module', plugins: ['jsx', 'typescript'] }) let hasReference = false traverse(ast, { // 检查import语句 - 更精确的匹配 ImportDeclaration(path) { const source = path.node.source.value // 检查import的默认导入 if (path.node.specifiers) { path.node.specifiers.forEach(spec => { if (spec.type === 'ImportDefaultSpecifier' && spec.local.name === targetComponent.component) { hasReference = true } // 检查命名导入 if (spec.type === 'ImportSpecifier' && spec.imported.name === targetComponent.component) { hasReference = true } }) } }, // 检查components注册 - 支持多种格式 ObjectProperty(path) { if (path.node.key.name === 'components') { const value = path.node.value if (value.type === 'ObjectExpression') { value.properties.forEach(prop => { // 支持 components: { ComponentName: ComponentName } if (prop.key.name === targetComponent.component) { hasReference = true } // 支持 components: { ComponentName } if (prop.type === 'ObjectProperty' && prop.key.name === targetComponent.component) { hasReference = true } }) } } }, // 检查Vue 3 Composition API的组件注册 CallExpression(path) { if (path.node.callee.name === 'defineComponent') { const arg = path.node.arguments[0] if (arg && arg.type === 'ObjectExpression') { arg.properties.forEach(prop => { if (prop.key.name === 'components') { const componentsValue = prop.value if (componentsValue.type === 'ObjectExpression') { componentsValue.properties.forEach(compProp => { if (compProp.key.name === targetComponent.component) { hasReference = true } }) } } }) } } } }) return hasReference } catch (error) { return false } } /** * 使用Vue template compiler分析template部分 * @param {string} templateContent - template内容 * @param {Object} targetComponent - 目标组件 * @returns {boolean} 是否引用 */ analyzeTemplateAST(templateContent, targetComponent) { try { const ast = parse(templateContent) let hasReference = false // 遍历AST查找组件使用 const traverse = (node) => { if (node.type === 1) { // Element // 检查标签名是否匹配目标组件 if (node.tag === targetComponent.component) { hasReference = true return } // 检查动态组件 if (node.tag === 'component' && node.props) { node.props.forEach(prop => { if (prop.name === 'is' && prop.value && prop.value.content === targetComponent.component) { hasReference = true return } }) } // 递归遍历子节点 if (node.children) { node.children.forEach(child => { if (child.type === 1) { // Element traverse(child) } }) } } } traverse(ast) return hasReference } catch (error) { return false } } /** * 查找顶级祖先组件 * @param {Object} parentComponent - 父组件 * @param {Object} originalTrigger - 原始触发组件 * @param {Array} topLevelComponents - 顶级组件数组 * @returns {boolean} 是否找到了关系 */ findTopLevelAncestors(parentComponent, originalTrigger, topLevelComponents) { // 检查父组件是否是顶级组件 const isTopLevel = topLevelComponents.find(top => top.component === parentComponent.component && top.module === parentComponent.module ) if (isTopLevel) { // 找到顶级组件,建立关系 this.componentRelationships.push({ triggerSourceId: originalTrigger.id, // 使用ID引用 routeId: isTopLevel.id, // 使用ID引用 relationshipType: 'ancestor' }) return true } // 继续向上查找 const grandParents = this.findParentComponentsAST(parentComponent) let found = false grandParents.forEach(grandParent => { const result = this.findTopLevelAncestors(grandParent, originalTrigger, topLevelComponents) if (result) found = true }) return found } } module.exports = ComponentRelationshipAnalyzerAST