From b5591cacd4803020ed696e555518af5a83de5904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20O=E2=80=99Brien?= Date: Wed, 11 Mar 2026 13:56:31 +1000 Subject: [PATCH] [CHORE] Minor doc and impl fixes with index store status related thigns --- README.md | 8 +++---- Sources/IndexStore/IndexStore.swift | 10 +++++++-- .../Status/IndexStatusController.swift | 15 ++++++------- .../IndexStatusControllerTests.swift | 21 ++++++++----------- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index a157e72..16290e4 100644 --- a/README.md +++ b/README.md @@ -192,8 +192,8 @@ class MyDelegate: IndexStoreDelegate { print("Out-of-date unit detected: \(unit.unitName) — \(unit.triggerHintDescription)") } - func indexStore(_ store: IndexStore, didUpdateTrackedUnitStatus trackedUnit: TrackedUnit) { - print("Unit '\(trackedUnit.unit.unitName)' status changed to \(trackedUnit.status)") + func indexStore(_ store: IndexStore, didProcessOutOfDateUnit trackedUnit: TrackedUnit) { + print("Unit '\(trackedUnit.unit.unitName)' processed — status: \(trackedUnit.status)") } } @@ -201,7 +201,7 @@ let delegate = MyDelegate() indexStore.delegate = delegate ``` -All delegate methods are optional except `didUpdatePendingUnitCount` and `didDetectOutOfDateUnit`. The `didUpdateTrackedUnitStatus` callback has a default empty implementation so existing adopters are not broken. +All delegate methods are optional except `didUpdatePendingUnitCount` and `didDetectOutOfDateUnit`. The `didProcessOutOfDateUnit` callback has a default empty implementation so existing adopters are not broken. **Inspecting out-of-date units:** @@ -229,7 +229,7 @@ let selected = staleUnits.filter { $0.unit.unitName.contains("MyModule") } indexStore.processOutOfDateUnits(selected) ``` -Each unit transitions through `.processing` (while being re-imported) and then `.processed` once complete. The delegate is notified at each transition. +Each unit transitions through `.processing` (while being re-imported) and then `.processed` once complete. The delegate's `didProcessOutOfDateUnit` callback fires when the unit reaches `.processed`. **Clearing processed history:** diff --git a/Sources/IndexStore/IndexStore.swift b/Sources/IndexStore/IndexStore.swift index de1ad93..0dcda63 100644 --- a/Sources/IndexStore/IndexStore.swift +++ b/Sources/IndexStore/IndexStore.swift @@ -21,7 +21,9 @@ public protocol IndexStoreDelegate: AnyObject { /// - pendingUnitCount: The current computed pending count (never negative). func indexStore(_ store: IndexStore, didUpdatePendingUnitCount pendingUnitCount: Int) - /// Called when indexstore-db detects an out-of-date unit (only if out-of-date watching is enabled). + /// Called when indexstore-db detects an out-of-date unit. + /// + /// This callback requires ``IndexStore/Configuration/enableOutOfDateFileWatching`` to be `true` (the default). /// /// - Parameters: /// - store: The ``IndexStore`` instance that detected the out-of-date unit. @@ -32,6 +34,7 @@ public protocol IndexStoreDelegate: AnyObject { /// /// This fires once per unit at the end of the ``IndexStore/processOutOfDateUnits(_:)`` lifecycle, /// after the unit's status has transitioned to ``UnitInfo/Status/processed``. + /// Requires ``IndexStore/Configuration/enableOutOfDateFileWatching`` to be `true` (the default). /// /// - Parameters: /// - store: The ``IndexStore`` instance that processed the unit. @@ -244,6 +247,7 @@ public final class IndexStore { /// Returns `true` when there is at least one tracked unit with ``UnitInfo/Status/outOfDate`` status. /// + /// Requires ``Configuration/enableOutOfDateFileWatching`` to be `true` (the default) for units to be tracked. /// This is a lightweight check that avoids copying the full array — prefer this over /// `outOfDateUnits.isEmpty` when you only need a boolean signal. public var hasOutOfDateUnits: Bool { @@ -269,12 +273,14 @@ public final class IndexStore { /// Processes specific out-of-date units by re-importing their data into the index store. /// + /// Requires ``Configuration/enableOutOfDateFileWatching`` to be `true` (the default) for units to be tracked. + /// /// The lifecycle for each unit is: /// 1. Status transitions to ``UnitInfo/Status/processing`` /// 2. The unit's `mainFilePath` is passed to the underlying `processUnitsForOutputPathsAndWait` /// 3. Status transitions to ``UnitInfo/Status/processed`` /// - /// The ``IndexStoreDelegate/indexStore(_:didUpdateTrackedUnitStatus:)`` callback fires at each transition. + /// The ``IndexStoreDelegate/indexStore(_:didProcessOutOfDateUnit:)`` callback fires when a unit reaches ``UnitInfo/Status/processed``. /// /// - Parameter units: The ``TrackedUnit`` values to process. Typically retrieved from ``outOfDateUnits``. public func processOutOfDateUnits(_ units: [TrackedUnit]) { diff --git a/Sources/IndexStore/Internal/Status/IndexStatusController.swift b/Sources/IndexStore/Internal/Status/IndexStatusController.swift index eed361c..d062359 100644 --- a/Sources/IndexStore/Internal/Status/IndexStatusController.swift +++ b/Sources/IndexStore/Internal/Status/IndexStatusController.swift @@ -190,10 +190,10 @@ class IndexStatusController: IndexDelegate { // MARK: - Helpers: Tracked Unit Lifecycle - /// Transitions the given unit names from ``UnitInfo/Status/outOfDate`` to ``UnitInfo/Status/processing``. + /// Transitions the given unit names back to ``UnitInfo/Status/outOfDate``. /// - /// Only units whose current status is `.outOfDate` will be transitioned. Units in any other - /// status are silently ignored. + /// Only units that are already tracked and not currently `.outOfDate` will be transitioned. + /// Units that are already `.outOfDate` or not tracked are silently ignored. /// /// - Parameter unitNames: The set of `unitName` identifiers to transition. /// - Returns: The ``TrackedUnit`` values that were actually transitioned. Empty if none matched. @@ -203,7 +203,7 @@ class IndexStatusController: IndexDelegate { countQueue.sync { for name in unitNames { guard let existing = state.trackedUnits[name], existing.status != .outOfDate else { continue } - let updated = existing.withStatus(.processing) + let updated = existing.withStatus(.outOfDate) state.trackedUnits[name] = updated transitioned.append(updated) } @@ -229,9 +229,6 @@ class IndexStatusController: IndexDelegate { transitioned.append(updated) } } - for unit in transitioned { - notifyProcessedOutOfDateUnit(unit) - } return transitioned } @@ -275,8 +272,6 @@ class IndexStatusController: IndexDelegate { } } - // MARK: - Notifications (internal) - /// Clears the ``lastOutOfDateUnit`` and ``lastOutOfDateTimestamp`` values. /// /// This does **not** affect the ``trackedUnits`` collection. @@ -287,6 +282,8 @@ class IndexStatusController: IndexDelegate { } } + // MARK: - Notifications (internal) + /// Forwards the current pending unit count to the public ``IndexStoreDelegate``. func notifyPendingCountChanged() { guard let store else { return } diff --git a/Tests/IndexStoreTests/IndexStore/IndexStatusControllerTests.swift b/Tests/IndexStoreTests/IndexStore/IndexStatusControllerTests.swift index 4cf518b..f803ba7 100644 --- a/Tests/IndexStoreTests/IndexStore/IndexStatusControllerTests.swift +++ b/Tests/IndexStoreTests/IndexStore/IndexStatusControllerTests.swift @@ -292,26 +292,23 @@ final class IndexStatusControllerTests: XCTestCase { } func test_statusTransitions_notifyDelegate() { - // Reset the spy count (unitIsOutOfDate also fires a notification) - delegateSpy.indexStore_didProcessOutOfDateUnitCallCount = 0 - delegateSpy.indexStore_didProcessOutOfDateUnitParameterList.removeAll() - reportOutOfDateUnit(mainFilePath: "pathA", unitName: "unitA") - // 1 notification: outOfDate - instanceUnderTest.markUnitsAsOutOfDate(["unitA"]) + + // unitIsOutOfDate should NOT fire didProcessOutOfDateUnit XCTAssertFalse(delegateSpy.indexStore_didProcessOutOfDateUnitCalled) + // Transitioning to .processing should NOT fire didProcessOutOfDateUnit instanceUnderTest.markUnitsAsProcessing(["unitA"]) - // 2 notifications total: outOfDate + processing - XCTAssertEqual(delegateSpy.indexStore_didProcessOutOfDateUnitCallCount, 1) - XCTAssertEqual(delegateSpy.indexStore_didProcessOutOfDateUnitParameters?.trackedUnit.status, .processing) + XCTAssertFalse(delegateSpy.indexStore_didProcessOutOfDateUnitCalled) + // Transitioning to .processed SHOULD fire didProcessOutOfDateUnit instanceUnderTest.markUnitsAsProcessed(["unitA"]) - // 3 notifications total: outOfDate + processing + processed - XCTAssertEqual(delegateSpy.indexStore_didProcessOutOfDateUnitCallCount, 2) + XCTAssertEqual(delegateSpy.indexStore_didProcessOutOfDateUnitCallCount, 1) XCTAssertEqual(delegateSpy.indexStore_didProcessOutOfDateUnitParameters?.trackedUnit.status, .processed) + XCTAssertIdentical(delegateSpy.indexStore_didProcessOutOfDateUnitParameters?.store, indexStore) - // All delegate calls should reference the correct store + // The didDetectOutOfDateUnit delegate should have fired once (from unitIsOutOfDate) + XCTAssertEqual(delegateSpy.indexStore_didDetectOutOfDateUnitCallCount, 1) XCTAssertTrue(delegateSpy.indexStore_didDetectOutOfDateUnitParameterList.allSatisfy { $0.store === indexStore }) }