UNPKG

autosnippet

Version:

Extract code patterns into a knowledge base for AI coding assistants

300 lines (299 loc) 12.7 kB
/** * LayerInferrer — 拓扑排序层级推断 * * 基于模块依赖图,通过去环 + 拓扑排序 + 最长路径法推断架构层级 (L0-Ln)。 * 底层 (L0) = Foundation/Core,顶层 = App/UI。 * * 当配置文件声明了明确的层级结构时(如 Boxfile 的 layer 定义), * 优先使用配置层级,仅对未覆盖的模块做拓扑推断。 * * @module LayerInferrer */ /* ═══ Constants ═══════════════════════════════════════════ */ /** 层级命名启发式 — 按优先级排列,匹配模块名(边界安全) */ const LAYER_NAME_HINTS = [ { pattern: /^(foundation|core|base|shared|common)$/i, name: 'Foundation', bias: -2 }, { pattern: /foundation/i, name: 'Foundation', bias: -2 }, { pattern: /^(model|entity|dto)$/i, name: 'Model', bias: -1 }, { pattern: /service|repository|manager|provider|store/i, name: 'Service', bias: 0 }, { pattern: /network|api|http/i, name: 'Networking', bias: 0 }, { pattern: /(?:^ui$|^ui[A-Z]|view|screen|component|widget)/i, name: 'UI', bias: 1 }, { pattern: /router|coordinator|navigation/i, name: 'Routing', bias: 1 }, { pattern: /^(app|main|launch|entry)$/i, name: 'Application', bias: 2 }, { pattern: /test|spec|mock/i, name: 'Test', bias: 3 }, ]; /* ═══ LayerInferrer Class ═════════════════════════════════ */ export class LayerInferrer { /** * 从模块依赖边推断架构层级 * @param edges - 模块间依赖边 (from depends_on/calls/data_flow to) * @param modules - 所有模块名 * @param cycles - 已检测到的循环依赖 * @param options - 可选配置:configLayers(配置声明的层级)和 moduleLayerMap(模块→层级映射) */ infer(edges, modules, cycles, options) { // 如果有配置文件声明的层级且覆盖了大部分模块,优先使用 if (options?.configLayers?.length && options.moduleLayerMap?.size) { const coverage = this.#computeConfigCoverage(modules, options.moduleLayerMap); if (coverage >= 0.5) { return this.#inferFromConfig(edges, modules, options.configLayers, options.moduleLayerMap); } } return this.#inferFromTopology(edges, modules, cycles); } /* ─── Config-based layer inference ──────────────── */ /** * 基于配置文件声明的层级直接分配 * 未被配置覆盖的模块附加到最近匹配的层级 */ #inferFromConfig(edges, modules, configLayers, moduleLayerMap) { // 按 order 排序层级 const sortedLayers = [...configLayers].sort((a, b) => a.order - b.order); // 构建层级名 → level 映射 (底层 = L0, 反序: order 最大的是底层) // 配置中 order=0 是最高层 (如 Accessories), order=N 是最底层 (如 Vendors) const maxOrder = sortedLayers.length > 0 ? sortedLayers[sortedLayers.length - 1].order : 0; const layerNameToLevel = new Map(); for (const cl of sortedLayers) { // 反转: 配置中 order 越大越底层 → level 越小 layerNameToLevel.set(cl.name, maxOrder - cl.order); } // 分配每个模块到层级 const moduleLevels = new Map(); const uncovered = []; for (const mod of modules) { const layerName = moduleLayerMap.get(mod); if (layerName && layerNameToLevel.has(layerName)) { moduleLevels.set(mod, layerNameToLevel.get(layerName)); } else { uncovered.push(mod); } } // 未覆盖的模块: 基于依赖关系推断最可能的层级 for (const mod of uncovered) { const depEdges = edges.filter((e) => e.from === mod); let bestLevel = maxOrder + 1; // 默认最高层 for (const e of depEdges) { const targetLevel = moduleLevels.get(e.to); if (targetLevel !== undefined) { bestLevel = Math.min(bestLevel, targetLevel + 1); } } // 如果没有依赖线索, 查看谁依赖它 if (bestLevel > maxOrder) { const reverseEdges = edges.filter((e) => e.to === mod); for (const e of reverseEdges) { const sourceLevel = moduleLevels.get(e.from); if (sourceLevel !== undefined) { bestLevel = Math.min(bestLevel, Math.max(0, sourceLevel - 1)); } } } // 兜底: 放到最高层 if (bestLevel > maxOrder) { bestLevel = maxOrder; } moduleLevels.set(mod, bestLevel); } // 聚合: 同层模块分组 const layerGroups = new Map(); for (const [mod, level] of moduleLevels) { if (!layerGroups.has(level)) { layerGroups.set(level, []); } layerGroups.get(level).push(mod); } // 构建 levelEntries, 使用配置中的层级名 const levelToConfigName = new Map(); for (const cl of sortedLayers) { levelToConfigName.set(maxOrder - cl.order, cl.name); } const sortedLevels = [...layerGroups.entries()].sort((a, b) => a[0] - b[0]); const levelEntries = sortedLevels.map(([level, mods]) => ({ level, name: levelToConfigName.get(level) ?? this.#inferLayerName(mods, level, sortedLevels.length), modules: mods.sort(), })); // 检测层级违规(基于 access 规则) const violations = []; for (const edge of edges) { const fromLevel = moduleLevels.get(edge.from); const toLevel = moduleLevels.get(edge.to); if (fromLevel !== undefined && toLevel !== undefined && fromLevel < toLevel) { violations.push({ from: edge.from, to: edge.to, fromLayer: fromLevel, toLayer: toLevel, relation: edge.relation, }); } } return { levels: levelEntries, violations, configBased: true }; } #computeConfigCoverage(modules, moduleLayerMap) { if (modules.length === 0) { return 0; } let covered = 0; for (const mod of modules) { if (moduleLayerMap.has(mod)) { covered++; } } return covered / modules.length; } /* ─── Topology-based layer inference ────────────── */ #inferFromTopology(edges, modules, cycles) { // 1. 建图 (邻接表: from → to[]) const adjacency = new Map(); const reverseAdj = new Map(); const allModules = new Set(modules); for (const mod of allModules) { adjacency.set(mod, new Set()); reverseAdj.set(mod, new Set()); } // 收集环中的节点 const cycleEdges = new Set(); for (const c of cycles) { for (let i = 0; i < c.cycle.length; i++) { const from = c.cycle[i]; const to = c.cycle[(i + 1) % c.cycle.length]; cycleEdges.add(`${from}→${to}`); } } // 2. 添加边 (跳过环边) const violations = []; for (const edge of edges) { if (!allModules.has(edge.from) || !allModules.has(edge.to) || edge.from === edge.to) { continue; } const edgeKey = `${edge.from}→${edge.to}`; if (cycleEdges.has(edgeKey)) { // 环边作为违规记录,不加入 DAG continue; } adjacency.get(edge.from).add(edge.to); reverseAdj.get(edge.to).add(edge.from); } // 3. 拓扑排序 (Kahn's algorithm) const inDegree = new Map(); for (const mod of allModules) { inDegree.set(mod, reverseAdj.get(mod)?.size ?? 0); } const queue = []; for (const [mod, deg] of inDegree) { if (deg === 0) { queue.push(mod); } } const order = []; while (queue.length > 0) { const node = queue.shift(); order.push(node); for (const neighbor of adjacency.get(node) ?? []) { const newDeg = (inDegree.get(neighbor) ?? 1) - 1; inDegree.set(neighbor, newDeg); if (newDeg === 0) { queue.push(neighbor); } } } // 未排入的节点 (仍在环中) 追加到末尾 for (const mod of allModules) { if (!order.includes(mod)) { order.push(mod); } } // 4. 分配层级: 最长路径法 // A 依赖 B → A 的 level ≥ B 的 level + 1 // reverseAdj: "B 被 A 依赖" → predecessors 是 reverseAdj(node) = {A} // 但我们要的是 "A 依赖 B" → A 的 predecessors = adjacency(A) 出度目标的 level // 换言之: level(A) = max(level(dep) for dep in adjacency(A)) + 1 // 底层 (无出度) = L0 const levels = new Map(); // 反向遍历: 从源头 (无出度) 开始 const reverseOrder = [...order].reverse(); for (const node of reverseOrder) { const deps = adjacency.get(node) ?? new Set(); if (deps.size === 0) { levels.set(node, 0); } else { let maxDepLevel = 0; for (const dep of deps) { maxDepLevel = Math.max(maxDepLevel, levels.get(dep) ?? 0); } levels.set(node, maxDepLevel + 1); } } // 5. 聚合: 同层模块分组 const layerGroups = new Map(); for (const [mod, level] of levels) { if (!layerGroups.has(level)) { layerGroups.set(level, []); } layerGroups.get(level).push(mod); } // 按 level 排序 const sortedLevels = [...layerGroups.entries()].sort((a, b) => a[0] - b[0]); // 6. 推断层级名 const levelEntries = sortedLevels.map(([level, mods]) => ({ level, name: this.#inferLayerName(mods, level, sortedLevels.length), modules: mods.sort(), })); // 7. 检测层级违规 (高层 → 低层调用正常;低层 → 高层调用为违规) for (const edge of edges) { const fromLevel = levels.get(edge.from); const toLevel = levels.get(edge.to); if (fromLevel !== undefined && toLevel !== undefined && fromLevel < toLevel) { violations.push({ from: edge.from, to: edge.to, fromLayer: fromLevel, toLayer: toLevel, relation: edge.relation, }); } } return { levels: levelEntries, violations }; } /* ─── Layer Naming ──────────────────────────────── */ #inferLayerName(modules, level, totalLevels) { // 投票: 每个匹配的 hint 累加权重,取最高分的名称 const votes = new Map(); for (const mod of modules) { for (const hint of LAYER_NAME_HINTS) { if (hint.pattern.test(mod)) { votes.set(hint.name, (votes.get(hint.name) ?? 0) + 1); break; // 每个模块只投一票(匹配第一个 hint) } } } if (votes.size > 0) { // 选最高票 let best = ''; let bestCount = 0; for (const [name, count] of votes) { if (count > bestCount) { best = name; bestCount = count; } } return best; } // 基于层级位置推断 const position = totalLevels > 1 ? level / (totalLevels - 1) : 0.5; if (position <= 0.2) { return 'Foundation'; } if (position <= 0.5) { return 'Service'; } if (position <= 0.8) { return 'Feature'; } return 'Application'; } }