|
5 | 5 | * and provides a function to merge partial updates into the full graph. |
6 | 6 | */ |
7 | 7 |
|
| 8 | +import path from 'path'; |
| 9 | + |
8 | 10 | /** |
9 | 11 | * Compare two file hash maps to detect changes. |
10 | 12 | * |
@@ -111,8 +113,9 @@ export function mergeGraphUpdate(graph, updatedFiles, removedFiles) { |
111 | 113 | } |
112 | 114 | } |
113 | 115 |
|
114 | | - // Step 4: Recalculate summary |
| 116 | + // Step 4: Recalculate summary and rebuild module dependencies |
115 | 117 | recalculateSummary(graph); |
| 118 | + rebuildDependencies(graph); |
116 | 119 | } |
117 | 120 |
|
118 | 121 | /** |
@@ -140,4 +143,70 @@ function recalculateSummary(graph) { |
140 | 143 | graph.summary.totalClasses = totalClasses; |
141 | 144 | graph.summary.languages = languages; |
142 | 145 | graph.summary.modules = Object.keys(graph.modules).sort(); |
| 146 | + graph.summary.entryPoints = Object.entries(graph.files) |
| 147 | + .filter(([, f]) => f.isEntryPoint) |
| 148 | + .map(([relPath]) => relPath) |
| 149 | + .sort(); |
| 150 | + if (graph.config) { |
| 151 | + graph.config.languages = Object.keys(languages); |
| 152 | + } |
| 153 | +} |
| 154 | + |
| 155 | +/** |
| 156 | + * Rebuild module-level dependsOn / dependedBy from file-level import data. |
| 157 | + * |
| 158 | + * Iterates every file in the graph, resolves each relative import to a target |
| 159 | + * module via a normalised-path lookup (mirroring scanner.js logic), and |
| 160 | + * overwrites the dependency arrays on every module. |
| 161 | + * |
| 162 | + * @param {object} graph - The full code graph (mutated in place). |
| 163 | + */ |
| 164 | +function rebuildDependencies(graph) { |
| 165 | + // Build normalised relative-path → moduleName lookup (O(1) resolution) |
| 166 | + const pathLookup = new Map(); |
| 167 | + for (const [relPath, fileData] of Object.entries(graph.files)) { |
| 168 | + const norm = relPath.replace(/\\/g, '/'); |
| 169 | + pathLookup.set(norm, fileData.module); |
| 170 | + const withoutExt = norm.replace(/\.[^/.]+$/, ''); |
| 171 | + if (!pathLookup.has(withoutExt)) { |
| 172 | + pathLookup.set(withoutExt, fileData.module); |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | + // Collect dependencies using Sets |
| 177 | + const depSets = {}; |
| 178 | + for (const modName of Object.keys(graph.modules)) { |
| 179 | + depSets[modName] = { dependsOn: new Set(), dependedBy: new Set() }; |
| 180 | + } |
| 181 | + |
| 182 | + for (const [relPath, fileData] of Object.entries(graph.files)) { |
| 183 | + const moduleName = fileData.module; |
| 184 | + if (!depSets[moduleName]) continue; |
| 185 | + |
| 186 | + for (const imp of fileData.imports) { |
| 187 | + if (imp.isExternal) continue; |
| 188 | + if (!imp.source.startsWith('.')) continue; |
| 189 | + |
| 190 | + const importerDir = path.posix.dirname(relPath.replace(/\\/g, '/')); |
| 191 | + const resolved = path.posix.normalize(importerDir + '/' + imp.source); |
| 192 | + |
| 193 | + let targetModule = pathLookup.get(resolved); |
| 194 | + if (!targetModule) { |
| 195 | + targetModule = pathLookup.get(resolved + '/index'); |
| 196 | + } |
| 197 | + |
| 198 | + if (targetModule && targetModule !== moduleName && depSets[targetModule]) { |
| 199 | + depSets[moduleName].dependsOn.add(targetModule); |
| 200 | + depSets[targetModule].dependedBy.add(moduleName); |
| 201 | + } |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + // Apply rebuilt dependencies to graph |
| 206 | + for (const [modName, deps] of Object.entries(depSets)) { |
| 207 | + if (graph.modules[modName]) { |
| 208 | + graph.modules[modName].dependsOn = [...deps.dependsOn].sort(); |
| 209 | + graph.modules[modName].dependedBy = [...deps.dependedBy].sort(); |
| 210 | + } |
| 211 | + } |
143 | 212 | } |
0 commit comments