Skip to content

Commit b36a09d

Browse files
committed
perf(builder): scope barrel iteration to newly-merged paths
Iteration 2+ of the JS barrel-discovery loop was re-querying the DB for every key in fileSymbols rather than just barrels newly merged in the previous pass, ballooning the work each iteration. The Rust orchestrator already does this correctly via the `&newly_added` slice. Wire the `string[]` returned by reparseBarrelFiles through the loop so each pass only walks newly-merged paths' imports, and gate the reexport-from DB query behind a `firstPass` flag — re-parsed barrels haven't changed content, so they can't surface new reexport-from candidates anyway. Matches the Rust seed-only `collect_reexport_from_barrels` call. No behavior change beyond perf; the regression test for #1174 (issue-1174-chained-barrel-incremental.test.ts) still passes on both engines and full=1371 / incremental=1371 imports edges on the dogfooded repo. Addresses Greptile feedback on #1179.
1 parent 246bce3 commit b36a09d

1 file changed

Lines changed: 40 additions & 23 deletions

File tree

src/domain/graph/builder/stages/resolve-imports.ts

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,23 @@ function buildReexportMap(ctx: PipelineContext): void {
3333
}
3434

3535
/**
36-
* Find barrel files related to changed files for scoped re-parsing.
37-
* For small incremental builds (<=smallFilesThreshold files), only barrels that re-export from
38-
* or are imported by the changed files. For larger changes, all barrels.
36+
* Find barrel files related to `fromRelPaths` for scoped re-parsing.
37+
* For small frontiers (<=smallFilesThreshold files), only barrels that re-export from
38+
* or are imported by `fromRelPaths`. For larger frontiers, all barrels.
39+
*
40+
* `firstPass` gates the reexport-from DB scan: re-parsed barrels haven't
41+
* changed content, so subsequent passes can't surface new reexport-from
42+
* candidates and only need to follow imports of newly-merged barrels
43+
* (mirrors the Rust orchestrator's seed-only `collect_reexport_from_barrels`).
3944
*/
40-
function findBarrelCandidates(ctx: PipelineContext): Array<{ file: string }> {
45+
function findBarrelCandidates(
46+
ctx: PipelineContext,
47+
fromRelPaths: readonly string[],
48+
firstPass: boolean,
49+
): Array<{ file: string }> {
4150
const { db, fileSymbols, rootDir, aliases } = ctx;
42-
const changedRelPaths = new Set<string>(fileSymbols.keys());
4351

44-
if (changedRelPaths.size <= ctx.config.build.smallFilesThreshold) {
52+
if (fromRelPaths.length <= ctx.config.build.smallFilesThreshold) {
4553
const allBarrelFiles = new Set(
4654
(
4755
db
@@ -56,9 +64,9 @@ function findBarrelCandidates(ctx: PipelineContext): Array<{ file: string }> {
5664

5765
const barrels = new Set<string>();
5866

59-
// Find barrels imported by changed files using parsed import data
67+
// Find barrels imported by `fromRelPaths` using parsed import data
6068
// (can't query DB edges -- they were purged for the changed files).
61-
for (const relPath of changedRelPaths) {
69+
for (const relPath of fromRelPaths) {
6270
const symbols = fileSymbols.get(relPath);
6371
if (!symbols) continue;
6472
for (const imp of symbols.imports) {
@@ -71,16 +79,17 @@ function findBarrelCandidates(ctx: PipelineContext): Array<{ file: string }> {
7179
}
7280
}
7381

74-
// Also find barrels that re-export from the changed files
75-
const reexportSourceStmt = db.prepare(
76-
`SELECT DISTINCT n1.file FROM edges e
77-
JOIN nodes n1 ON e.source_id = n1.id
78-
JOIN nodes n2 ON e.target_id = n2.id
79-
WHERE e.kind = 'reexports' AND n1.kind = 'file' AND n2.file = ?`,
80-
);
81-
for (const relPath of changedRelPaths) {
82-
for (const row of reexportSourceStmt.all(relPath) as Array<{ file: string }>) {
83-
barrels.add(row.file);
82+
if (firstPass) {
83+
const reexportSourceStmt = db.prepare(
84+
`SELECT DISTINCT n1.file FROM edges e
85+
JOIN nodes n1 ON e.source_id = n1.id
86+
JOIN nodes n2 ON e.target_id = n2.id
87+
WHERE e.kind = 'reexports' AND n1.kind = 'file' AND n2.file = ?`,
88+
);
89+
for (const relPath of fromRelPaths) {
90+
for (const row of reexportSourceStmt.all(relPath) as Array<{ file: string }>) {
91+
barrels.add(row.file);
92+
}
8493
}
8594
}
8695
return [...barrels].map((file) => ({ file }));
@@ -189,11 +198,19 @@ export async function resolveImports(ctx: PipelineContext): Promise<void> {
189198
// Convergence is guaranteed because fileSymbols grows monotonically and
190199
// is bounded by the set of barrel files in the project — each iteration
191200
// either adds a previously-unseen barrel or terminates.
192-
while (true) {
193-
const before = fileSymbols.size;
194-
const barrelCandidates = findBarrelCandidates(ctx);
195-
await reparseBarrelFiles(ctx, barrelCandidates);
196-
if (fileSymbols.size === before) break;
201+
//
202+
// Subsequent passes only walk newly-merged barrels' imports (`frontier`
203+
// = paths returned by reparseBarrelFiles), matching the Rust
204+
// orchestrator's `&newly_added` slice. Without this, every pass would
205+
// re-query the DB for every key in `fileSymbols`.
206+
let frontier: readonly string[] = [...fileSymbols.keys()];
207+
let firstPass = true;
208+
while (frontier.length > 0) {
209+
const barrelCandidates = findBarrelCandidates(ctx, frontier, firstPass);
210+
const added = await reparseBarrelFiles(ctx, barrelCandidates);
211+
if (added.length === 0) break;
212+
frontier = added;
213+
firstPass = false;
197214
}
198215
}
199216
}

0 commit comments

Comments
 (0)