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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jack 2026-08-08 12:12:30 +02:00 committed by GitHub
parent 9c84d4ce4f
commit 2a9fb4d53f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 28 additions and 35 deletions

View File

@ -264,11 +264,13 @@ jobs:
CODE_SIGNING_ALLOWED=NO \ CODE_SIGNING_ALLOWED=NO \
test test
# Advisory only: SwiftLint reports style violations without ever failing the # Blocking since the backlog reached zero (all violations fixed; rules the
# build. Runs in a pinned container (no Xcode plugin, no pbxproj changes) so # codebase deliberately breaks are disabled in .swiftlint.yml). --strict
# it can never break the documented xcodebuild path or block a merge. # 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: lint:
name: SwiftLint (advisory) name: SwiftLint
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 15
# This job runs a third-party container image, so give it the least # 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). # Tag for readability, digest for immutability (tags can be repointed).
# Bump both together, deliberately — never a floating tag. # Bump both together, deliberately — never a floating tag.
image: ghcr.io/realm/swiftlint:0.65.0@sha256:a482729f4b58741875af1566f23397f3f6db300372756fc31606d0a4527fab9e image: ghcr.io/realm/swiftlint:0.65.0@sha256:a482729f4b58741875af1566f23397f3f6db300372756fc31606d0a4527fab9e
continue-on-error: true
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
with: with:
persist-credentials: false persist-credentials: false
- name: Run SwiftLint - name: Run SwiftLint
run: swiftlint lint --reporter github-actions-logging run: swiftlint lint --strict --reporter github-actions-logging

View File

@ -3,6 +3,7 @@
excluded: excluded:
- .build - .build
- .claude - .claude
- .device-lab
- .swiftpm - .swiftpm
- .DerivedData - .DerivedData
- DerivedData - DerivedData
@ -21,6 +22,7 @@ disabled_rules:
- control_statement - control_statement
- void_function_in_ternary - void_function_in_ternary
- redundant_discardable_let # SwiftUI breaks without it - 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 # To be enabled as we fix the issues
- trailing_whitespace - trailing_whitespace
- cyclomatic_complexity - cyclomatic_complexity

View File

@ -1001,8 +1001,7 @@ final class NoiseSessionManager {
0, 0,
recentInitiatorCompletionGracePeriod - elapsed recentInitiatorCompletionGracePeriod - elapsed
) )
let timeout = DispatchWorkItem(flags: .barrier) { let timeout = DispatchWorkItem(flags: .barrier) { [weak self, weak establishedSession] in
[weak self, weak establishedSession] in
guard let self, guard let self,
let establishedSession, let establishedSession,
let current = self.sessions[peerID], let current = self.sessions[peerID],

View File

@ -4027,10 +4027,8 @@ extension BLEService {
) )
} }
} }
service.onRekeyHandshakeReady = { service.onRekeyHandshakeReady = { [weak self, weak service] peerID, initiation in
[weak self, weak service] peerID, initiation in self?.messageQueue.async { [weak self, weak service] in
self?.messageQueue.async {
[weak self, weak service] in
guard let self, guard let self,
let service, let service,
self.noiseService === service else { self.noiseService === service else {
@ -4046,14 +4044,12 @@ extension BLEService {
self.broadcastNoiseHandshake(message, to: peerID) self.broadcastNoiseHandshake(message, to: peerID)
} }
} }
service.onHandshakeRecoveryRequired = { service.onHandshakeRecoveryRequired = { [weak self, weak service] request in
[weak self, weak service] request in
guard let self, let service else { return } guard let self, let service else { return }
#if DEBUG #if DEBUG
self._test_beforeHandshakeRecoveryEnqueued?(request.peerID) self._test_beforeHandshakeRecoveryEnqueued?(request.peerID)
#endif #endif
self.messageQueue.async { self.messageQueue.async { [weak self, weak service] in
[weak self, weak service] in
guard let self, guard let self,
let service, let service,
self.noiseService === service else { self.noiseService === service else {
@ -5294,8 +5290,7 @@ extension BLEService {
) else { ) else {
return return
} }
messageQueue.async { messageQueue.async { [weak self, weak service] in
[weak self, weak service] in
guard let self, guard let self,
let service, let service,
self.noiseService === service, self.noiseService === service,

View File

@ -108,6 +108,6 @@ struct QuickJoinSuggestion {
"UY": "6c", "UZ": "tx", "VA": "sr", "VC": "dd", "UY": "6c", "UZ": "tx", "VA": "sr", "VC": "dd",
"VE": "d9", "VI": "de", "VN": "w7", "VU": "rs", "VE": "d9", "VI": "de", "VN": "w7", "VU": "rs",
"WS": "2j", "XK": "sr", "YE": "sf", "YT": "mj", "WS": "2j", "XK": "sr", "YE": "sf", "YT": "mj",
"ZA": "ke", "ZM": "kt", "ZW": "ks", "ZA": "ke", "ZM": "kt", "ZW": "ks"
] ]
} }

View File

@ -1859,8 +1859,7 @@ private extension ChatMediaTransferCoordinator {
TimeInterval(UInt64.max) / 1_000_000_000 TimeInterval(UInt64.max) / 1_000_000_000
) * 1_000_000_000 ) * 1_000_000_000
) )
reconnectRetryExpiryTasks[messageID] = Task { reconnectRetryExpiryTasks[messageID] = Task { @MainActor [weak self] in
@MainActor [weak self] in
if nanoseconds > 0 { if nanoseconds > 0 {
try? await Task.sleep(nanoseconds: nanoseconds) try? await Task.sleep(nanoseconds: nanoseconds)
} }

View File

@ -482,7 +482,7 @@ private struct AutocompleteKeyboardNavigationModifier: ViewModifier {
/// list is up. Inactive monitors pass everything through. /// list is up. Inactive monitors pass everything through.
private func handleKeyDown(_ event: NSEvent) -> NSEvent? { private func handleKeyDown(_ event: NSEvent) -> NSEvent? {
guard isActive(), guard isActive(),
event.modifierFlags.intersection([.command, .option, .control]).isEmpty else { event.modifierFlags.isDisjoint(with: [.command, .option, .control]) else {
return event return event
} }

View File

@ -653,8 +653,7 @@ struct ChatMediaTransferCoordinatorContextTests {
@Test @MainActor @Test @MainActor
func deleteStableMediaReleasesRetainedRetryBeforeTombstoneCommit() func deleteStableMediaReleasesRetainedRetryBeforeTombstoneCommit()
async throws async throws {
{
let context = MockChatMediaTransferContext() let context = MockChatMediaTransferContext()
let peerID = PeerID(str: "1122334455667788") let peerID = PeerID(str: "1122334455667788")
context.selectedPrivateChatPeer = peerID context.selectedPrivateChatPeer = peerID

View File

@ -150,14 +150,12 @@ struct BLENoisePacketHandlerTests {
recorder.clearedSessions.append(peerID) recorder.clearedSessions.append(peerID)
service.clearSession(for: peerID) service.clearSession(for: peerID)
}, },
handleAuthenticatedPeerState: { handleAuthenticatedPeerState: { peerID, payload, generation in
peerID, payload, generation in
recorder.authenticatedPeerStates.append( recorder.authenticatedPeerStates.append(
(peerID, payload, generation) (peerID, payload, generation)
) )
}, },
deliverNoisePayload: { deliverNoisePayload: { peerID, type, payload, timestamp in
peerID, type, payload, timestamp in
recorder.deliveries.append( recorder.deliveries.append(
(peerID, type, payload, timestamp) (peerID, type, payload, timestamp)
) )

View File

@ -8,6 +8,9 @@
import Foundation 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. /// Identity-free presence announcement for rotating peer IDs.
/// ///
/// The v1 `AnnouncementPacket` broadcasts, in cleartext, every 430 seconds: the /// The v1 `AnnouncementPacket` broadcasts, in cleartext, every 430 seconds: the
@ -37,9 +40,6 @@ import Foundation
/// `docs/PEER-ID-ROTATION.md`. /// `docs/PEER-ID-ROTATION.md`.
/// ///
/// Not emitted or consumed by the shipping mesh yet. /// 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 { public struct AnnounceV2Packet: Equatable, Sendable {
/// Rotation epoch this announce was built for. Carried explicitly so a /// Rotation epoch this announce was built for. Carried explicitly so a
/// receiver matches against a stated epoch instead of guessing. /// receiver matches against a stated epoch instead of guessing.

View File

@ -9,6 +9,10 @@
import Foundation import Foundation
private import CryptoKit 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. /// Derivations for rotating peer IDs and pairwise recognition tags.
/// ///
/// See `docs/PEER-ID-ROTATION.md` for the design, the threat model, and the /// 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 /// 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 /// keys, so exactly two parties can compute a given tag and an observer can
/// compute none of them. /// 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 { public enum PeerIDRotation {
// MARK: - Parameters // MARK: - Parameters