* Index all files in the project * * Uses a mutex to prevent concurrent indexing operations.
(options: IndexOptions = {})
| 426 | * Uses a mutex to prevent concurrent indexing operations. |
| 427 | */ |
| 428 | async indexAll(options: IndexOptions = {}): Promise<IndexResult> { |
| 429 | return this.indexMutex.withLock(async () => { |
| 430 | try { |
| 431 | this.fileLock.acquire(); |
| 432 | } catch { |
| 433 | return { success: false, filesIndexed: 0, filesSkipped: 0, filesErrored: 0, nodesCreated: 0, edgesCreated: 0, errors: [{ message: 'Could not acquire file lock - another process may be indexing', severity: 'error' as const }], durationMs: 0 }; |
| 434 | } |
| 435 | try { |
| 436 | const before = this.queries.getNodeAndEdgeCount(); |
| 437 | const result = await this.orchestrator.indexAll(options.onProgress, options.signal, options.verbose); |
| 438 | |
| 439 | // Re-detect frameworks now that the index is populated. The resolver |
| 440 | // is constructed with createResolver() before any files exist, so |
| 441 | // framework resolvers whose detect() consults the indexed file list |
| 442 | // (e.g. UIKit/SwiftUI scanning for imports, swift-objc-bridge looking |
| 443 | // for both Swift and ObjC files) all return false on that initial pass |
| 444 | // and silently drop themselves. Re-initializing here gives them a |
| 445 | // chance to see the actual project before resolution runs. |
| 446 | if (result.success && result.filesIndexed > 0) { |
| 447 | this.resolver.initialize(); |
| 448 | // Cross-file finalization (e.g. NestJS RouterModule prefixes). Runs |
| 449 | // before resolution so updated names show up in subsequent reads. |
| 450 | this.resolver.runPostExtract(); |
| 451 | } |
| 452 | |
| 453 | // Resolve references to create call/import/extends edges |
| 454 | if (result.success && result.filesIndexed > 0) { |
| 455 | // Get count without loading all refs into memory |
| 456 | const unresolvedCount = this.queries.getUnresolvedReferencesCount(); |
| 457 | |
| 458 | options.onProgress?.({ |
| 459 | phase: 'resolving', |
| 460 | current: 0, |
| 461 | total: unresolvedCount, |
| 462 | }); |
| 463 | |
| 464 | await this.resolveReferencesBatched((current, total) => { |
| 465 | options.onProgress?.({ |
| 466 | phase: 'resolving', |
| 467 | current, |
| 468 | total, |
| 469 | }); |
| 470 | }); |
| 471 | |
| 472 | // Second pass: chained calls whose method lives on a supertype the |
| 473 | // receiver conforms to (protocol-extension / inherited / default- |
| 474 | // interface). Needs the implements/extends edges the main pass just |
| 475 | // built, so it runs after resolution (#750). |
| 476 | await this.resolver.resolveChainedCallsViaConformance(); |
| 477 | // Same lifecycle for `this.<member>` callback registrations whose |
| 478 | // member is inherited from a supertype (#808). |
| 479 | await this.resolver.resolveDeferredThisMemberRefs(); |
| 480 | } |
| 481 | |
| 482 | // Refresh planner stats + checkpoint the WAL after bulk writes. |
| 483 | // Cheap and non-blocking; never load-bearing for correctness. |
| 484 | if (result.success && result.filesIndexed > 0) { |
| 485 | this.db.runMaintenance(); |