From 2a9fb4d53f67beed91064eb97fa7f2cdb1f17dfb Mon Sep 17 00:00:00 2001 From: jack <212554440+jackjackbits@users.noreply.github.com> Date: Sat, 8 Aug 2026 12:12:30 +0200 Subject: [PATCH] Make SwiftLint blocking in CI now that the violation backlog is zero (#1646) * Make SwiftLint blocking in CI now that the violation backlog is zero The lint job has been advisory since #1361, with the backlog tracked in #1088. That backlog is now down to 28 violations, so this clears it and flips the job to enforcing instead of adding baseline machinery: - Fix all remaining violations: closure parameters/capture lists joined onto their opening-brace line (BLEService, NoiseSessionManager, ChatMediaTransferCoordinator, BLENoisePacketHandlerTests), one trailing comma, one opening-brace spacing (both via swiftlint --fix). - Move the two periphery:ignore comments above the doc blocks they were orphaning; verified with a periphery --strict scan that the commands still register (no new findings vs baseline). - Disable the todo rule: the single TODO cites tracked issue #1434 and is a deliberate marker, not lint debt. - Exclude .device-lab from local runs (untracked; CI never sees it). - CI: drop continue-on-error and the "(advisory)" label; run with --strict so any new warning fails the job. Full SwiftPM suite green (2,004 tests / 216 suites) after the formatting changes; swiftlint lint --strict exits 0 with the same 0.65.0 the CI container pins. Co-Authored-By: Claude Fable 5 * Fix is_disjoint violation introduced by #1542 The strict lint gate caught this on its first run: the mention-suggestion key handler used modifierFlags.intersection([...]).isEmpty, which is exactly the pattern isDisjoint(with:) expresses directly. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: jack Co-authored-by: Claude Fable 5 --- .github/workflows/swift-tests.yml | 13 +++++++------ .swiftlint.yml | 2 ++ bitchat/Noise/NoiseSessionManager.swift | 3 +-- bitchat/Services/BLE/BLEService.swift | 15 +++++---------- bitchat/Utils/QuickJoinRegions.swift | 2 +- .../ViewModels/ChatMediaTransferCoordinator.swift | 3 +-- bitchat/Views/ContentComposerView.swift | 2 +- ...ChatMediaTransferCoordinatorContextTests.swift | 3 +-- .../Services/BLENoisePacketHandlerTests.swift | 6 ++---- .../Sources/BitFoundation/AnnounceV2Packet.swift | 6 +++--- .../Sources/BitFoundation/PeerIDRotation.swift | 8 ++++---- 11 files changed, 28 insertions(+), 35 deletions(-) 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