@optave/codegraph 3.8.1 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +11 -7
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +43 -0
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  6. package/dist/ast-analysis/visitors/complexity-visitor.js +50 -1
  7. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  8. package/dist/cli/commands/branch-compare.d.ts.map +1 -1
  9. package/dist/cli/commands/branch-compare.js +4 -0
  10. package/dist/cli/commands/branch-compare.js.map +1 -1
  11. package/dist/cli/commands/diff-impact.d.ts.map +1 -1
  12. package/dist/cli/commands/diff-impact.js +2 -1
  13. package/dist/cli/commands/diff-impact.js.map +1 -1
  14. package/dist/cli/commands/info.d.ts.map +1 -1
  15. package/dist/cli/commands/info.js +3 -2
  16. package/dist/cli/commands/info.js.map +1 -1
  17. package/dist/db/repository/base.d.ts +6 -0
  18. package/dist/db/repository/base.d.ts.map +1 -1
  19. package/dist/db/repository/base.js +14 -0
  20. package/dist/db/repository/base.js.map +1 -1
  21. package/dist/db/repository/native-repository.d.ts +1 -0
  22. package/dist/db/repository/native-repository.d.ts.map +1 -1
  23. package/dist/db/repository/native-repository.js +23 -0
  24. package/dist/db/repository/native-repository.js.map +1 -1
  25. package/dist/db/repository/sqlite-repository.d.ts +1 -0
  26. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  27. package/dist/db/repository/sqlite-repository.js +25 -0
  28. package/dist/db/repository/sqlite-repository.js.map +1 -1
  29. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  30. package/dist/domain/analysis/dependencies.js +12 -8
  31. package/dist/domain/analysis/dependencies.js.map +1 -1
  32. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  33. package/dist/domain/graph/builder/pipeline.js +154 -59
  34. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  35. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  36. package/dist/domain/graph/builder/stages/build-edges.js +27 -1
  37. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  38. package/dist/domain/parser.d.ts +4 -0
  39. package/dist/domain/parser.d.ts.map +1 -1
  40. package/dist/domain/parser.js +128 -61
  41. package/dist/domain/parser.js.map +1 -1
  42. package/dist/domain/search/models.d.ts.map +1 -1
  43. package/dist/domain/search/models.js +7 -5
  44. package/dist/domain/search/models.js.map +1 -1
  45. package/dist/extractors/javascript.js +19 -9
  46. package/dist/extractors/javascript.js.map +1 -1
  47. package/dist/types.d.ts +2 -1
  48. package/dist/types.d.ts.map +1 -1
  49. package/grammars/tree-sitter-erlang.wasm +0 -0
  50. package/grammars/tree-sitter-gleam.wasm +0 -0
  51. package/package.json +9 -9
  52. package/src/ast-analysis/engine.ts +51 -0
  53. package/src/ast-analysis/visitors/complexity-visitor.ts +55 -1
  54. package/src/cli/commands/branch-compare.ts +4 -0
  55. package/src/cli/commands/diff-impact.ts +2 -1
  56. package/src/cli/commands/info.ts +3 -2
  57. package/src/db/repository/base.ts +14 -0
  58. package/src/db/repository/native-repository.ts +25 -0
  59. package/src/db/repository/sqlite-repository.ts +26 -0
  60. package/src/domain/analysis/dependencies.ts +11 -6
  61. package/src/domain/graph/builder/pipeline.ts +185 -64
  62. package/src/domain/graph/builder/stages/build-edges.ts +23 -1
  63. package/src/domain/parser.ts +129 -63
  64. package/src/domain/search/models.ts +11 -5
  65. package/src/extractors/javascript.ts +21 -8
  66. package/src/types.ts +2 -1
@@ -75,14 +75,20 @@ function buildTransitiveCallers(
75
75
  let frontier = callers;
76
76
 
77
77
  for (let d = 2; d <= depth; d++) {
78
+ // Collect unvisited frontier IDs for a single batched query per depth
79
+ const unvisited = frontier.filter((f) => !visited.has(f.id));
80
+ for (const f of unvisited) visited.add(f.id);
81
+ if (unvisited.length === 0) break;
82
+
83
+ const batchCallers = repo.findCallersBatch(unvisited.map((f) => f.id));
78
84
  const nextFrontier: typeof frontier = [];
79
- for (const f of frontier) {
80
- if (visited.has(f.id)) continue;
81
- visited.add(f.id);
82
- const upstream = repo.findCallers(f.id) as RelatedNodeRow[];
85
+ const nextFrontierIds = new Set<number>();
86
+ for (const f of unvisited) {
87
+ const upstream = batchCallers.get(f.id) || [];
83
88
  for (const u of upstream) {
84
89
  if (noTests && isTestFile(u.file)) continue;
85
- if (!visited.has(u.id)) {
90
+ if (!visited.has(u.id) && !nextFrontierIds.has(u.id)) {
91
+ nextFrontierIds.add(u.id);
86
92
  nextFrontier.push(u);
87
93
  }
88
94
  }
@@ -96,7 +102,6 @@ function buildTransitiveCallers(
96
102
  }));
97
103
  }
98
104
  frontier = nextFrontier;
99
- if (frontier.length === 0) break;
100
105
  }
101
106
 
102
107
  return transitiveCallers;
@@ -134,7 +134,9 @@ function setupPipeline(ctx: PipelineContext): void {
134
134
  // Use NativeDatabase for schema init when native engine is available (Phase 6.13).
135
135
  // better-sqlite3 (ctx.db) is still always opened — needed for queries and stages
136
136
  // that haven't been migrated to rusqlite yet.
137
- const native = loadNative();
137
+ // Skip native DB entirely when user explicitly requested --engine wasm.
138
+ const enginePref = ctx.opts.engine || 'auto';
139
+ const native = enginePref !== 'wasm' ? loadNative() : null;
138
140
  if (native?.NativeDatabase) {
139
141
  try {
140
142
  ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
@@ -216,6 +218,7 @@ function closeNativeDb(ctx: PipelineContext, label: string): void {
216
218
 
217
219
  /** Try to reopen the native connection for a given pipeline phase. */
218
220
  function reopenNativeDb(ctx: PipelineContext, label: string): void {
221
+ if ((ctx.opts.engine ?? 'auto') === 'wasm') return;
219
222
  const native = loadNative();
220
223
  if (!native?.NativeDatabase) return;
221
224
  try {
@@ -336,10 +339,14 @@ export async function buildGraph(
336
339
  // napi crossings (eliminates WAL dual-connection dance). Falls back
337
340
  // to the JS pipeline on failure or when native is unavailable.
338
341
  //
339
- // Native addon 3.8.0 has a path bug: file_symbols keys are absolute
342
+ // Native addon 3.8.0 has a path bug: file_symbols keys are absolute
340
343
  // paths but known_files are relative, causing zero import/call edges.
344
+ // Native addon ≤3.8.1 has an incremental barrel bug: the Rust pipeline
345
+ // doesn't re-parse barrel files that are imported by changed files,
346
+ // causing missing barrel import edges and lost analysis data for
347
+ // reverse-dep files during incremental builds.
341
348
  // Skip the orchestrator for affected versions (fixed in 3.9.0+).
342
- const orchestratorBuggy = !!ctx.engineVersion && semverCompare(ctx.engineVersion, '3.8.0') <= 0;
349
+ const orchestratorBuggy = !!ctx.engineVersion && semverCompare(ctx.engineVersion, '3.8.1') <= 0;
343
350
  const forceJs =
344
351
  process.env.CODEGRAPH_FORCE_JS_PIPELINE === '1' ||
345
352
  ctx.forceFullRebuild ||
@@ -413,18 +420,32 @@ export async function buildGraph(
413
420
  `Native build orchestrator completed: ${result.nodeCount ?? 0} nodes, ${result.edgeCount ?? 0} edges, ${result.fileCount ?? 0} files`,
414
421
  );
415
422
 
416
- // ── Run analysis phases (AST, complexity, CFG, dataflow) ──────
417
- // Not yet ported to Rust. After the native orchestrator finishes,
418
- // reconstruct a minimal fileSymbols map from the DB and run analyses
419
- // via the JS engine (native standalone functions + WASM fallback).
423
+ // ── Run structure + analysis phases after native orchestrator ──
424
+ // Structure (directory nodes, contains edges, metrics) is not fully
425
+ // ported to Rust the native pipeline only handles the small
426
+ // incremental fast path (≤5 changed files). For full builds and
427
+ // larger incremental builds, run JS buildStructure() to fill the gap.
428
+ // Analysis phases (AST, complexity, CFG, dataflow) are also not yet
429
+ // ported; run via JS engine after reconstructing fileSymbols from DB.
420
430
  let analysisTiming = { astMs: 0, complexityMs: 0, cfgMs: 0, dataflowMs: 0 };
431
+ let structurePatchMs = 0;
421
432
  const needsAnalysis =
422
433
  opts.ast !== false ||
423
434
  opts.complexity !== false ||
424
435
  opts.cfg !== false ||
425
436
  opts.dataflow !== false;
426
437
 
427
- if (needsAnalysis) {
438
+ // The native fast path only runs structure for small incremental
439
+ // builds: !isFullBuild && changedCount <= 5 && existingFileCount > 20.
440
+ // For all other cases (full builds, large incrementals), we must
441
+ // run JS buildStructure() to create directory nodes + contains edges (#804).
442
+ // Always run JS structure — the native fast-path has an additional
443
+ // existingFileCount > 20 guard that isn't reflected in the result JSON,
444
+ // so we can't reliably detect whether native actually ran structure.
445
+ const nativeHandledStructure = false;
446
+ const needsStructure = !nativeHandledStructure;
447
+
448
+ if (needsAnalysis || needsStructure) {
428
449
  // WAL handoff: checkpoint through rusqlite, close nativeDb,
429
450
  // reopen better-sqlite3 with a fresh page cache (#715, #736).
430
451
  try {
@@ -447,10 +468,8 @@ export async function buildGraph(
447
468
  try {
448
469
  ctx.db = openDb(ctx.dbPath);
449
470
  } catch (reopenErr) {
450
- warn(
451
- `Failed to reopen DB for analysis after native build: ${(reopenErr as Error).message}`,
452
- );
453
- // Native build succeeded but we can't run analyses — return partial result
471
+ warn(`Failed to reopen DB after native build: ${(reopenErr as Error).message}`);
472
+ // Native build succeeded but we can't run post-processing return partial result
454
473
  return {
455
474
  phases: {
456
475
  setupMs: +((p.setupMs ?? 0) + (p.collectMs ?? 0) + (p.detectMs ?? 0)).toFixed(1),
@@ -469,22 +488,14 @@ export async function buildGraph(
469
488
  };
470
489
  }
471
490
 
472
- // Reconstruct minimal fileSymbols from DB for analysis visitors.
473
- // Each entry needs definitions with name/kind/line/endLine so the
474
- // engine can match complexity/CFG results to the right functions.
475
- // For incremental builds, scope to only the files that were parsed
476
- // in this cycle (matching the JS pipeline's behaviour in run-analyses.ts).
477
- const changedFiles = result.changedFiles;
478
- let query =
479
- 'SELECT file, name, kind, line, end_line as endLine FROM nodes WHERE file IS NOT NULL';
480
- const params: string[] = [];
481
- if (changedFiles && changedFiles.length > 0) {
482
- const placeholders = changedFiles.map(() => '?').join(',');
483
- query += ` AND file IN (${placeholders})`;
484
- params.push(...changedFiles);
485
- }
486
- query += ' ORDER BY file, line';
487
- const rows = ctx.db.prepare(query).all(...params) as {
491
+ // Reconstruct fileSymbols from DB. For structure we need ALL files
492
+ // (to build complete directory tree); for analysis we scope to
493
+ // changed files only. Load all files, then scope analysis later.
494
+ const allFileRows = ctx.db
495
+ .prepare(
496
+ 'SELECT file, name, kind, line, end_line as endLine FROM nodes WHERE file IS NOT NULL ORDER BY file, line',
497
+ )
498
+ .all() as {
488
499
  file: string;
489
500
  name: string;
490
501
  kind: string;
@@ -492,9 +503,9 @@ export async function buildGraph(
492
503
  endLine: number | null;
493
504
  }[];
494
505
 
495
- const fileSymbols = new Map<string, ExtractorOutput>();
496
- for (const row of rows) {
497
- let entry = fileSymbols.get(row.file);
506
+ const allFileSymbols = new Map<string, ExtractorOutput>();
507
+ for (const row of allFileRows) {
508
+ let entry = allFileSymbols.get(row.file);
498
509
  if (!entry) {
499
510
  entry = {
500
511
  definitions: [],
@@ -504,7 +515,7 @@ export async function buildGraph(
504
515
  exports: [],
505
516
  typeMap: new Map(),
506
517
  };
507
- fileSymbols.set(row.file, entry);
518
+ allFileSymbols.set(row.file, entry);
508
519
  }
509
520
  entry.definitions.push({
510
521
  name: row.name,
@@ -514,45 +525,155 @@ export async function buildGraph(
514
525
  });
515
526
  }
516
527
 
517
- // Reopen nativeDb for analysis features (suspend/resume WAL pattern).
518
- const native = loadNative();
519
- if (native?.NativeDatabase) {
528
+ // Populate import/export counts from DB edges so buildStructure
529
+ // computes correct import_count/export_count in node_metrics.
530
+ // The extractor arrays aren't persisted to the DB, so we derive
531
+ // counts from edge data instead (#804).
532
+ const importCountRows = ctx.db
533
+ .prepare(
534
+ `SELECT n.file, COUNT(*) AS cnt
535
+ FROM edges e JOIN nodes n ON e.source_id = n.id
536
+ WHERE e.kind IN ('imports', 'imports-type', 'dynamic-imports')
537
+ AND n.file IS NOT NULL
538
+ GROUP BY n.file`,
539
+ )
540
+ .all() as { file: string; cnt: number }[];
541
+ for (const row of importCountRows) {
542
+ const entry = allFileSymbols.get(row.file);
543
+ if (entry) entry.imports = new Array(row.cnt) as ExtractorOutput['imports'];
544
+ }
545
+ // Export count: definitions in this file that are imported by other files
546
+ const exportCountRows = ctx.db
547
+ .prepare(
548
+ `SELECT n_tgt.file, COUNT(DISTINCT n_tgt.id) AS cnt
549
+ FROM edges e
550
+ JOIN nodes n_tgt ON e.target_id = n_tgt.id
551
+ JOIN nodes n_src ON e.source_id = n_src.id
552
+ WHERE e.kind IN ('imports', 'imports-type', 'reexports')
553
+ AND n_tgt.file IS NOT NULL
554
+ AND n_src.file != n_tgt.file
555
+ GROUP BY n_tgt.file`,
556
+ )
557
+ .all() as { file: string; cnt: number }[];
558
+ for (const row of exportCountRows) {
559
+ const entry = allFileSymbols.get(row.file);
560
+ if (entry) entry.exports = new Array(row.cnt) as ExtractorOutput['exports'];
561
+ }
562
+
563
+ // ── Structure phase: directory nodes + contains edges (#804) ──
564
+ if (needsStructure) {
565
+ const structureStart = performance.now();
520
566
  try {
521
- ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
522
- if (ctx.engineOpts) ctx.engineOpts.nativeDb = ctx.nativeDb;
523
- } catch {
524
- ctx.nativeDb = undefined;
525
- if (ctx.engineOpts) ctx.engineOpts.nativeDb = undefined;
567
+ // Derive directories from file paths
568
+ const directories = new Set<string>();
569
+ for (const relPath of allFileSymbols.keys()) {
570
+ const parts = relPath.split('/');
571
+ for (let i = 1; i < parts.length; i++) {
572
+ directories.add(parts.slice(0, i).join('/'));
573
+ }
574
+ }
575
+
576
+ // Build line count map from DB metrics or file content
577
+ const lineCountMap = new Map<string, number>();
578
+ const cachedLineCounts = ctx.db
579
+ .prepare(
580
+ `SELECT n.name AS file, m.line_count
581
+ FROM node_metrics m JOIN nodes n ON m.node_id = n.id
582
+ WHERE n.kind = 'file'`,
583
+ )
584
+ .all() as Array<{ file: string; line_count: number }>;
585
+ for (const row of cachedLineCounts) {
586
+ lineCountMap.set(row.file, row.line_count);
587
+ }
588
+
589
+ // Native ran no structure at all — always do a full rebuild so
590
+ // every directory gets nodes + contains edges (#804).
591
+ const changedFilePaths = null;
592
+
593
+ const { buildStructure: buildStructureFn } = (await import(
594
+ '../../../features/structure.js'
595
+ )) as {
596
+ buildStructure: (
597
+ db: typeof ctx.db,
598
+ fileSymbols: Map<string, ExtractorOutput>,
599
+ rootDir: string,
600
+ lineCountMap: Map<string, number>,
601
+ directories: Set<string>,
602
+ changedFiles: string[] | null,
603
+ ) => void;
604
+ };
605
+ buildStructureFn(
606
+ ctx.db,
607
+ allFileSymbols,
608
+ ctx.rootDir,
609
+ lineCountMap,
610
+ directories,
611
+ changedFilePaths,
612
+ );
613
+ debug('Structure phase completed after native orchestrator');
614
+ } catch (err) {
615
+ warn(`Structure phase failed after native build: ${toErrorMessage(err)}`);
526
616
  }
617
+ structurePatchMs = performance.now() - structureStart;
527
618
  }
528
619
 
529
- try {
530
- const { runAnalyses: runAnalysesFn } = await import('../../../ast-analysis/engine.js');
531
- analysisTiming = await runAnalysesFn(
532
- ctx.db,
533
- fileSymbols,
534
- ctx.rootDir,
535
- opts,
536
- ctx.engineOpts,
537
- );
538
- } catch (err) {
539
- warn(`Analysis phases failed after native build: ${toErrorMessage(err)}`);
540
- }
620
+ // ── Analysis phase ──
621
+ if (needsAnalysis) {
622
+ // Scope analysis fileSymbols to changed files only
623
+ const changedFiles = result.changedFiles;
624
+ let analysisFileSymbols: Map<string, ExtractorOutput>;
625
+ if (changedFiles && changedFiles.length > 0) {
626
+ analysisFileSymbols = new Map();
627
+ for (const f of changedFiles) {
628
+ const entry = allFileSymbols.get(f);
629
+ if (entry) analysisFileSymbols.set(f, entry);
630
+ }
631
+ } else {
632
+ analysisFileSymbols = allFileSymbols;
633
+ }
541
634
 
542
- // Close nativeDb after analyses
543
- if (ctx.nativeDb) {
544
- try {
545
- ctx.nativeDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
546
- } catch {
547
- /* ignore checkpoint errors */
635
+ // Reopen nativeDb for analysis features (suspend/resume WAL pattern).
636
+ const native = loadNative();
637
+ if (native?.NativeDatabase) {
638
+ try {
639
+ ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
640
+ if (ctx.engineOpts) ctx.engineOpts.nativeDb = ctx.nativeDb;
641
+ } catch {
642
+ ctx.nativeDb = undefined;
643
+ if (ctx.engineOpts) ctx.engineOpts.nativeDb = undefined;
644
+ }
548
645
  }
646
+
549
647
  try {
550
- ctx.nativeDb.close();
551
- } catch {
552
- /* ignore close errors */
648
+ const { runAnalyses: runAnalysesFn } = await import(
649
+ '../../../ast-analysis/engine.js'
650
+ );
651
+ analysisTiming = await runAnalysesFn(
652
+ ctx.db,
653
+ analysisFileSymbols,
654
+ ctx.rootDir,
655
+ opts,
656
+ ctx.engineOpts,
657
+ );
658
+ } catch (err) {
659
+ warn(`Analysis phases failed after native build: ${toErrorMessage(err)}`);
660
+ }
661
+
662
+ // Close nativeDb after analyses
663
+ if (ctx.nativeDb) {
664
+ try {
665
+ ctx.nativeDb.exec('PRAGMA wal_checkpoint(TRUNCATE)');
666
+ } catch {
667
+ /* ignore checkpoint errors */
668
+ }
669
+ try {
670
+ ctx.nativeDb.close();
671
+ } catch {
672
+ /* ignore close errors */
673
+ }
674
+ ctx.nativeDb = undefined;
675
+ if (ctx.engineOpts) ctx.engineOpts.nativeDb = undefined;
553
676
  }
554
- ctx.nativeDb = undefined;
555
- if (ctx.engineOpts) ctx.engineOpts.nativeDb = undefined;
556
677
  }
557
678
  }
558
679
 
@@ -564,7 +685,7 @@ export async function buildGraph(
564
685
  insertMs: +(p.insertMs ?? 0).toFixed(1),
565
686
  resolveMs: +(p.resolveMs ?? 0).toFixed(1),
566
687
  edgesMs: +(p.edgesMs ?? 0).toFixed(1),
567
- structureMs: +(p.structureMs ?? 0).toFixed(1),
688
+ structureMs: +((p.structureMs ?? 0) + structurePatchMs).toFixed(1),
568
689
  rolesMs: +(p.rolesMs ?? 0).toFixed(1),
569
690
  astMs: +(analysisTiming.astMs ?? 0).toFixed(1),
570
691
  complexityMs: +(analysisTiming.complexityMs ?? 0).toFixed(1),
@@ -354,7 +354,10 @@ function buildImportedNamesForNative(
354
354
  rootDir: string,
355
355
  ): Array<{ name: string; file: string }> {
356
356
  const importedNames: Array<{ name: string; file: string }> = [];
357
- for (const imp of symbols.imports) {
357
+ // Process dynamic imports first (lower priority), then static imports
358
+ // (higher priority). Rust HashMap::collect keeps the last entry per key,
359
+ // so static imports win when both contribute the same name.
360
+ const addImports = (imp: (typeof symbols.imports)[number]) => {
358
361
  const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
359
362
  for (const name of imp.names) {
360
363
  const cleanName = name.replace(/^\*\s+as\s+/, '');
@@ -365,6 +368,12 @@ function buildImportedNamesForNative(
365
368
  }
366
369
  importedNames.push({ name: cleanName, file: targetFile });
367
370
  }
371
+ };
372
+ for (const imp of symbols.imports) {
373
+ if (imp.dynamicImport) addImports(imp);
374
+ }
375
+ for (const imp of symbols.imports) {
376
+ if (!imp.dynamicImport) addImports(imp);
368
377
  }
369
378
  return importedNames;
370
379
  }
@@ -409,12 +418,25 @@ function buildImportedNamesMap(
409
418
  rootDir: string,
410
419
  ): Map<string, string> {
411
420
  const importedNames = new Map<string, string>();
421
+ // Process dynamic imports first (lower priority), then static imports
422
+ // (higher priority). Static imports represent direct bindings while dynamic
423
+ // imports often use aliased destructuring (`{ foo: bar } = await import(…)`).
424
+ // When both contribute the same name, the static binding is authoritative.
412
425
  for (const imp of symbols.imports) {
426
+ if (!imp.dynamicImport) continue;
413
427
  const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
414
428
  for (const name of imp.names) {
415
429
  importedNames.set(name.replace(/^\*\s+as\s+/, ''), resolvedPath);
416
430
  }
417
431
  }
432
+ for (const imp of symbols.imports) {
433
+ if (imp.dynamicImport) continue;
434
+ const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
435
+ for (const name of imp.names) {
436
+ const cleanName = name.replace(/^\*\s+as\s+/, '');
437
+ importedNames.set(cleanName, resolvedPath);
438
+ }
439
+ }
418
440
  return importedNames;
419
441
  }
420
442