diff --git a/.github/workflows/swift-tests.yml b/.github/workflows/swift-tests.yml index 81c42eb2..e64683de 100644 --- a/.github/workflows/swift-tests.yml +++ b/.github/workflows/swift-tests.yml @@ -264,11 +264,13 @@ jobs: CODE_SIGNING_ALLOWED=NO \ test - # Advisory only: SwiftLint reports style violations without ever failing the - # build. Runs in a pinned container (no Xcode plugin, no pbxproj changes) so - # it can never break the documented xcodebuild path or block a merge. + # Blocking since the backlog reached zero (all violations fixed; rules the + # codebase deliberately breaks are disabled in .swiftlint.yml). --strict + # promotes warnings to errors so new violations fail the job. Runs in a + # pinned container (no Xcode plugin, no pbxproj changes) so it can never + # break the documented xcodebuild path. lint: - name: SwiftLint (advisory) + name: SwiftLint runs-on: ubuntu-latest timeout-minutes: 15 # This job runs a third-party container image, so give it the least @@ -280,10 +282,9 @@ jobs: # Tag for readability, digest for immutability (tags can be repointed). # Bump both together, deliberately — never a floating tag. image: ghcr.io/realm/swiftlint:0.65.0@sha256:a482729f4b58741875af1566f23397f3f6db300372756fc31606d0a4527fab9e - continue-on-error: true steps: - uses: actions/checkout@v5 with: persist-credentials: false - name: Run SwiftLint - run: swiftlint lint --reporter github-actions-logging + run: swiftlint lint --strict --reporter github-actions-logging diff --git a/.swiftlint.yml b/.swiftlint.yml index 833662e9..8d916f35 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -3,6 +3,7 @@ excluded: - .build - .claude + - .device-lab - .swiftpm - .DerivedData - DerivedData @@ -21,6 +22,7 @@ disabled_rules: - control_statement - void_function_in_ternary - redundant_discardable_let # SwiftUI breaks without it + - todo # TODOs that cite a tracked issue are deliberate markers, not lint debt # To be enabled as we fix the issues - trailing_whitespace - cyclomatic_complexity diff --git a/bitchat/Noise/NoiseSessionManager.swift b/bitchat/Noise/NoiseSessionManager.swift index c2aacbb0..80b3cc42 100644 --- a/bitchat/Noise/NoiseSessionManager.swift +++ b/bitchat/Noise/NoiseSessionManager.swift @@ -1001,8 +1001,7 @@ final class NoiseSessionManager { 0, recentInitiatorCompletionGracePeriod - elapsed ) - let timeout = DispatchWorkItem(flags: .barrier) { - [weak self, weak establishedSession] in + let timeout = DispatchWorkItem(flags: .barrier) { [weak self, weak establishedSession] in guard let self, let establishedSession, let current = self.sessions[peerID], diff --git a/bitchat/Services/BLE/BLEService.swift b/bitchat/Services/BLE/BLEService.swift index 0c4ecfb1..5f9b97af 100644 --- a/bitchat/Services/BLE/BLEService.swift +++ b/bitchat/Services/BLE/BLEService.swift @@ -4027,10 +4027,8 @@ extension BLEService { ) } } - service.onRekeyHandshakeReady = { - [weak self, weak service] peerID, initiation in - self?.messageQueue.async { - [weak self, weak service] in + service.onRekeyHandshakeReady = { [weak self, weak service] peerID, initiation in + self?.messageQueue.async { [weak self, weak service] in guard let self, let service, self.noiseService === service else { @@ -4046,14 +4044,12 @@ extension BLEService { self.broadcastNoiseHandshake(message, to: peerID) } } - service.onHandshakeRecoveryRequired = { - [weak self, weak service] request in + service.onHandshakeRecoveryRequired = { [weak self, weak service] request in guard let self, let service else { return } #if DEBUG self._test_beforeHandshakeRecoveryEnqueued?(request.peerID) #endif - self.messageQueue.async { - [weak self, weak service] in + self.messageQueue.async { [weak self, weak service] in guard let self, let service, self.noiseService === service else { @@ -5294,8 +5290,7 @@ extension BLEService { ) else { return } - messageQueue.async { - [weak self, weak service] in + messageQueue.async { [weak self, weak service] in guard let self, let service, self.noiseService === service, diff --git a/bitchat/Utils/QuickJoinRegions.swift b/bitchat/Utils/QuickJoinRegions.swift index 31a6fae6..149aa8d3 100644 --- a/bitchat/Utils/QuickJoinRegions.swift +++ b/bitchat/Utils/QuickJoinRegions.swift @@ -108,6 +108,6 @@ struct QuickJoinSuggestion { "UY": "6c", "UZ": "tx", "VA": "sr", "VC": "dd", "VE": "d9", "VI": "de", "VN": "w7", "VU": "rs", "WS": "2j", "XK": "sr", "YE": "sf", "YT": "mj", - "ZA": "ke", "ZM": "kt", "ZW": "ks", + "ZA": "ke", "ZM": "kt", "ZW": "ks" ] } diff --git a/bitchat/ViewModels/ChatMediaTransferCoordinator.swift b/bitchat/ViewModels/ChatMediaTransferCoordinator.swift index 84295135..fe314f62 100644 --- a/bitchat/ViewModels/ChatMediaTransferCoordinator.swift +++ b/bitchat/ViewModels/ChatMediaTransferCoordinator.swift @@ -1859,8 +1859,7 @@ private extension ChatMediaTransferCoordinator { TimeInterval(UInt64.max) / 1_000_000_000 ) * 1_000_000_000 ) - reconnectRetryExpiryTasks[messageID] = Task { - @MainActor [weak self] in + reconnectRetryExpiryTasks[messageID] = Task { @MainActor [weak self] in if nanoseconds > 0 { try? await Task.sleep(nanoseconds: nanoseconds) } diff --git a/bitchat/Views/ContentComposerView.swift b/bitchat/Views/ContentComposerView.swift index 64021d52..356c4141 100644 --- a/bitchat/Views/ContentComposerView.swift +++ b/bitchat/Views/ContentComposerView.swift @@ -482,7 +482,7 @@ private struct AutocompleteKeyboardNavigationModifier: ViewModifier { /// list is up. Inactive monitors pass everything through. private func handleKeyDown(_ event: NSEvent) -> NSEvent? { guard isActive(), - event.modifierFlags.intersection([.command, .option, .control]).isEmpty else { + event.modifierFlags.isDisjoint(with: [.command, .option, .control]) else { return event } diff --git a/bitchatTests/ChatMediaTransferCoordinatorContextTests.swift b/bitchatTests/ChatMediaTransferCoordinatorContextTests.swift index ad200113..1969602c 100644 --- a/bitchatTests/ChatMediaTransferCoordinatorContextTests.swift +++ b/bitchatTests/ChatMediaTransferCoordinatorContextTests.swift @@ -653,8 +653,7 @@ struct ChatMediaTransferCoordinatorContextTests { @Test @MainActor func deleteStableMediaReleasesRetainedRetryBeforeTombstoneCommit() - async throws - { + async throws { let context = MockChatMediaTransferContext() let peerID = PeerID(str: "1122334455667788") context.selectedPrivateChatPeer = peerID diff --git a/bitchatTests/Services/BLENoisePacketHandlerTests.swift b/bitchatTests/Services/BLENoisePacketHandlerTests.swift index cdfa6f81..e2b80def 100644 --- a/bitchatTests/Services/BLENoisePacketHandlerTests.swift +++ b/bitchatTests/Services/BLENoisePacketHandlerTests.swift @@ -150,14 +150,12 @@ struct BLENoisePacketHandlerTests { recorder.clearedSessions.append(peerID) service.clearSession(for: peerID) }, - handleAuthenticatedPeerState: { - peerID, payload, generation in + handleAuthenticatedPeerState: { peerID, payload, generation in recorder.authenticatedPeerStates.append( (peerID, payload, generation) ) }, - deliverNoisePayload: { - peerID, type, payload, timestamp in + deliverNoisePayload: { peerID, type, payload, timestamp in recorder.deliveries.append( (peerID, type, payload, timestamp) ) diff --git a/localPackages/BitFoundation/Sources/BitFoundation/AnnounceV2Packet.swift b/localPackages/BitFoundation/Sources/BitFoundation/AnnounceV2Packet.swift index 0bee3e6f..72b2fd14 100644 --- a/localPackages/BitFoundation/Sources/BitFoundation/AnnounceV2Packet.swift +++ b/localPackages/BitFoundation/Sources/BitFoundation/AnnounceV2Packet.swift @@ -8,6 +8,9 @@ import Foundation +// periphery:ignore - intentionally unreferenced by production code; nothing +// emits or consumes this type yet, and BLEService parses it only to ignore it. +// Delete this annotation when the mesh starts using it. /// Identity-free presence announcement for rotating peer IDs. /// /// The v1 `AnnouncementPacket` broadcasts, in cleartext, every 4–30 seconds: the @@ -37,9 +40,6 @@ import Foundation /// `docs/PEER-ID-ROTATION.md`. /// /// Not emitted or consumed by the shipping mesh yet. -// periphery:ignore - intentionally unreferenced by production code; nothing -// emits or consumes this type yet, and BLEService parses it only to ignore it. -// Delete this annotation when the mesh starts using it. public struct AnnounceV2Packet: Equatable, Sendable { /// Rotation epoch this announce was built for. Carried explicitly so a /// receiver matches against a stated epoch instead of guessing. diff --git a/localPackages/BitFoundation/Sources/BitFoundation/PeerIDRotation.swift b/localPackages/BitFoundation/Sources/BitFoundation/PeerIDRotation.swift index 17a079d9..2edfa951 100644 --- a/localPackages/BitFoundation/Sources/BitFoundation/PeerIDRotation.swift +++ b/localPackages/BitFoundation/Sources/BitFoundation/PeerIDRotation.swift @@ -9,6 +9,10 @@ import Foundation private import CryptoKit +// periphery:ignore - intentionally unreferenced by production code. These are +// the reviewable primitives for a protocol change that cannot ship until both +// platforms agree on it; wiring them into the transport is the next step, not +// this one. Delete this annotation when the mesh starts using them. /// Derivations for rotating peer IDs and pairwise recognition tags. /// /// See `docs/PEER-ID-ROTATION.md` for the design, the threat model, and the @@ -32,10 +36,6 @@ private import CryptoKit /// Recognition tags come from the X25519 shared secret between two static /// keys, so exactly two parties can compute a given tag and an observer can /// compute none of them. -// periphery:ignore - intentionally unreferenced by production code. These are -// the reviewable primitives for a protocol change that cannot ship until both -// platforms agree on it; wiring them into the transport is the next step, not -// this one. Delete this annotation when the mesh starts using them. public enum PeerIDRotation { // MARK: - Parameters