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.

466 lines
15 KiB

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(/<script[^>]*>([\s\S]*?)<\/script>/)
if (scriptMatch) {
result.script = scriptMatch[1]
}
// 提取template部分
const templateMatch = content.match(/<template[^>]*>([\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