From f0ef4319d90ba42499d6343827fb142d858f051b Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Tue, 5 May 2026 11:14:22 +1000 Subject: [PATCH 1/2] Enable SwiftLint rule: redundant_nil_coalescing Adds the rule to `only_rules`. SwiftLint --fix removed redundant `?? ` operators applied to non-optional expressions. Part of the Orchard SwiftLint rollout campaign. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.7 --- .swiftlint.yml | 3 +++ .../Helpers/ImageSaliencyService.swift | 2 +- .../WordPressKit/DomainsServiceRemote.swift | 4 ++-- .../Sources/WordPressKit/JetpackScanThreat.swift | 2 +- .../WordPressKitModels/RemoteUser+Likes.swift | 2 +- Sources/WordPressData/Swift/SiteTaxonomy.swift | 14 +++++++------- .../RegisterDomainDetailsViewModelTests.swift | 4 ++-- .../Tests/MediaLibraryTestSupport.swift | 2 +- WordPress/Classes/Stores/StatsPeriodStore.swift | 2 +- .../Jetpack Scan/JetpackScanCoordinator.swift | 2 +- .../Detail/Views/ReaderDetailHeaderView.swift | 2 +- .../Reader/Manage/OffsetTableViewHandler.swift | 4 ++-- .../Support/SupportTableViewController.swift | 2 +- 13 files changed, 24 insertions(+), 21 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 3a07daa9eb53..abf388dc3348 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -60,6 +60,9 @@ only_rules: # Example: CGPoint.zero instead of CGPoint(x: 0, y: 0) - prefer_zero_over_explicit_init + # `nil` coalescing on a non-optional is redundant. + - redundant_nil_coalescing + - shorthand_optional_binding # Prefer `someBool.toggle()` over `someBool = !someBool`. diff --git a/Modules/Sources/AsyncImageKit/Helpers/ImageSaliencyService.swift b/Modules/Sources/AsyncImageKit/Helpers/ImageSaliencyService.swift index 01fdce5bb288..6c5bc8ad4935 100644 --- a/Modules/Sources/AsyncImageKit/Helpers/ImageSaliencyService.swift +++ b/Modules/Sources/AsyncImageKit/Helpers/ImageSaliencyService.swift @@ -138,7 +138,7 @@ private final class SaliencyCache: @unchecked Sendable { } func cachedRect(for url: URL) -> CGRect? { - lock.withLock { store[url.absoluteString] ?? nil } + lock.withLock { store[url.absoluteString]} } func store(_ rect: CGRect?, for url: URL) { diff --git a/Modules/Sources/WordPressKit/DomainsServiceRemote.swift b/Modules/Sources/WordPressKit/DomainsServiceRemote.swift index 154b6f692256..65077e113bc5 100644 --- a/Modules/Sources/WordPressKit/DomainsServiceRemote.swift +++ b/Modules/Sources/WordPressKit/DomainsServiceRemote.swift @@ -59,8 +59,8 @@ public struct DomainSuggestion: Codable { } self.domainName = domain - self.productID = json["product_id"] as? Int ?? nil - self.supportsPrivacy = json["supports_privacy"] as? Bool ?? nil + self.productID = json["product_id"] as? Int + self.supportsPrivacy = json["supports_privacy"] as? Bool self.costString = json["cost"] as? String ?? "" self.cost = json["raw_price"] as? Double self.saleCost = json["sale_cost"] as? Double diff --git a/Modules/Sources/WordPressKit/JetpackScanThreat.swift b/Modules/Sources/WordPressKit/JetpackScanThreat.swift index fdf5076154d8..af41fda284b8 100644 --- a/Modules/Sources/WordPressKit/JetpackScanThreat.swift +++ b/Modules/Sources/WordPressKit/JetpackScanThreat.swift @@ -96,7 +96,7 @@ public struct JetpackScanThreat: Decodable { description = try container.decode(String.self, forKey: .description) firstDetected = try container.decode(Date.self, forKey: .firstDetected) fixedOn = try container.decodeIfPresent(Date.self, forKey: .fixedOn) - fixable = try? container.decodeIfPresent(JetpackScanThreatFixer.self, forKey: .fixable) ?? nil + fixable = try? container.decodeIfPresent(JetpackScanThreatFixer.self, forKey: .fixable) `extension` = try container.decodeIfPresent(JetpackThreatExtension.self, forKey: .extension) diff = try container.decodeIfPresent(String.self, forKey: .diff) rows = try container.decodeIfPresent([String: Any].self, forKey: .rows) diff --git a/Modules/Sources/WordPressKitModels/RemoteUser+Likes.swift b/Modules/Sources/WordPressKitModels/RemoteUser+Likes.swift index 299fb6edf152..eecbf858738d 100644 --- a/Modules/Sources/WordPressKitModels/RemoteUser+Likes.swift +++ b/Modules/Sources/WordPressKitModels/RemoteUser+Likes.swift @@ -50,7 +50,7 @@ import Foundation public init(dictionary: [String: Any]) { blogUrl = dictionary["url"] as? String ?? "" blogName = dictionary["name"] as? String ?? "" - blogID = dictionary["id"] as? NSNumber ?? nil + blogID = dictionary["id"] as? NSNumber iconUrl = { if let iconInfo = dictionary["icon"] as? [String: Any], diff --git a/Sources/WordPressData/Swift/SiteTaxonomy.swift b/Sources/WordPressData/Swift/SiteTaxonomy.swift index 6b52b32c0f1b..77f3f643707a 100644 --- a/Sources/WordPressData/Swift/SiteTaxonomy.swift +++ b/Sources/WordPressData/Swift/SiteTaxonomy.swift @@ -33,13 +33,13 @@ public struct SiteTaxonomy: Codable { self.name = details.name self.restBase = details.restBase self.labels = LocalizedLabels( - name: details.labels[.name] ?? nil, - newItemName: details.labels[.newItemName] ?? nil, - addNewItem: details.labels[.addNewItem] ?? nil, - nameFieldDescription: details.labels[.nameFieldDescription] ?? nil, - descFieldDescription: details.labels[.descFieldDescription] ?? nil, - noTerms: details.labels[.noTerms] ?? nil, - searchItems: details.labels[.searchItems] ?? nil + name: details.labels[.name], + newItemName: details.labels[.newItemName], + addNewItem: details.labels[.addNewItem], + nameFieldDescription: details.labels[.nameFieldDescription], + descFieldDescription: details.labels[.descFieldDescription], + noTerms: details.labels[.noTerms], + searchItems: details.labels[.searchItems] ) self.supportedPostTypes = details.types } diff --git a/Tests/KeystoneTests/Tests/Features/Domains/RegisterDomainDetailsViewModelTests.swift b/Tests/KeystoneTests/Tests/Features/Domains/RegisterDomainDetailsViewModelTests.swift index 02a8265161e1..fe627c863620 100644 --- a/Tests/KeystoneTests/Tests/Features/Domains/RegisterDomainDetailsViewModelTests.swift +++ b/Tests/KeystoneTests/Tests/Features/Domains/RegisterDomainDetailsViewModelTests.swift @@ -8,8 +8,8 @@ extension FullyQuotedDomainSuggestion { } let domainName = domain - let productID = json["product_id"] as? Int ?? nil - let supportsPrivacy = json["supports_privacy"] as? Bool ?? nil + let productID = json["product_id"] as? Int + let supportsPrivacy = json["supports_privacy"] as? Bool let costString = json["cost"] as? String ?? "" let saleCostString: String? = nil diff --git a/Tests/WordPressKitTests/WordPressKitTests/Tests/MediaLibraryTestSupport.swift b/Tests/WordPressKitTests/WordPressKitTests/Tests/MediaLibraryTestSupport.swift index da2091a97713..fcf6a523a6a6 100644 --- a/Tests/WordPressKitTests/WordPressKitTests/Tests/MediaLibraryTestSupport.swift +++ b/Tests/WordPressKitTests/WordPressKitTests/Tests/MediaLibraryTestSupport.swift @@ -58,7 +58,7 @@ extension MediaLibraryTestSupport { } private func handleREST(request: URLRequest, failAtPage pageToFail: Int) -> HTTPStubsResponse { - let cursor = request.url?.query("page_handle") ?? nil + let cursor = request.url?.query("page_handle") let number = request.url?.query("number").flatMap(Int.init(_:)) ?? 100 let cursorIndex = media.firstIndex { $0.cusor == cursor } ?? 0 diff --git a/WordPress/Classes/Stores/StatsPeriodStore.swift b/WordPress/Classes/Stores/StatsPeriodStore.swift index 5cfd10216583..18c9a64e89b9 100644 --- a/WordPress/Classes/Stores/StatsPeriodStore.swift +++ b/WordPress/Classes/Stores/StatsPeriodStore.swift @@ -1160,7 +1160,7 @@ extension StatsPeriodStore { guard let postId else { return nil } - return state.postStats[postId] ?? nil + return state.postStats[postId] } func getMostRecentDate(forPost postId: Int?) -> Date? { diff --git a/WordPress/Classes/ViewRelated/Jetpack/Jetpack Scan/JetpackScanCoordinator.swift b/WordPress/Classes/ViewRelated/Jetpack/Jetpack Scan/JetpackScanCoordinator.swift index 0e3703bb1134..a30960800889 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Jetpack Scan/JetpackScanCoordinator.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Jetpack Scan/JetpackScanCoordinator.swift @@ -46,7 +46,7 @@ class JetpackScanCoordinator { let returnThreats: [JetpackScanThreat]? if scan?.state == .fixingThreats { - returnThreats = scan?.threatFixStatus?.compactMap { $0.threat } ?? nil + returnThreats = scan?.threatFixStatus?.compactMap { $0.threat } } else { returnThreats = scan?.state == .idle ? scan?.threats : nil } diff --git a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailHeaderView.swift b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailHeaderView.swift index 539f07f35a00..e9f95f116738 100644 --- a/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailHeaderView.swift +++ b/WordPress/Classes/ViewRelated/Reader/Detail/Views/ReaderDetailHeaderView.swift @@ -140,7 +140,7 @@ class ReaderDetailHeaderViewModel: ObservableObject { self.isFollowingSite = post.isFollowing - self.authorAvatarURL = post.avatarURLForDisplay() ?? nil + self.authorAvatarURL = post.avatarURLForDisplay() if let authorName = post.authorForDisplay(), !authorName.isEmpty { self.authorName = authorName diff --git a/WordPress/Classes/ViewRelated/Reader/Manage/OffsetTableViewHandler.swift b/WordPress/Classes/ViewRelated/Reader/Manage/OffsetTableViewHandler.swift index 8c00fc9de655..26175a6ac1f3 100644 --- a/WordPress/Classes/ViewRelated/Reader/Manage/OffsetTableViewHandler.swift +++ b/WordPress/Classes/ViewRelated/Reader/Manage/OffsetTableViewHandler.swift @@ -35,11 +35,11 @@ class OffsetTableViewHandler: WPTableViewHandler { let oldIndexPath = indexPath.map { adjustedToTable(indexPath: $0) - } ?? nil + } let newPath = newIndexPath.map { adjustedToTable(indexPath: $0) - } ?? nil + } super.controller(controller, didChange: anObject, at: oldIndexPath, for: type, newIndexPath: newPath) } diff --git a/WordPress/Classes/ViewRelated/Support/SupportTableViewController.swift b/WordPress/Classes/ViewRelated/Support/SupportTableViewController.swift index 25a004505ac4..76d3cc40cb22 100644 --- a/WordPress/Classes/ViewRelated/Support/SupportTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Support/SupportTableViewController.swift @@ -540,7 +540,7 @@ private extension SupportTableViewController { // MARK: - Helpers func controllerToShowFrom() -> UIViewController? { - return showHelpFromViewController ?? navigationController ?? nil + return showHelpFromViewController ?? navigationController } // MARK: - Localized Text From 9e50779f54daa0e7c9dbdc25d60cfe87f8721cd7 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Tue, 5 May 2026 11:38:09 +1000 Subject: [PATCH 2/2] Fix `redundant_nil_coalescing` autofix breaking double-optional flattening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `swiftlint --fix` removed `?? nil` from expressions where the LHS was actually a double-optional (`String??`, `CGRect??`, etc.) — typically from `dict[key]` on a `[K: V?]` dictionary or `.map` on `Optional` returning `Optional`. The original `?? nil` flattened the result to a single optional; without it the call sites get a type error. For each broken call site: either rewrite to use `flatMap` (when that produces a clean single-optional) or restore the `?? nil` with a `swiftlint:disable:next redundant_nil_coalescing` suppression. The rule's autofix should arguably check for double optionals before stripping; until it does, this gives a working build. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.7 --- .../Sources/AsyncImageKit/Helpers/ImageSaliencyService.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/Sources/AsyncImageKit/Helpers/ImageSaliencyService.swift b/Modules/Sources/AsyncImageKit/Helpers/ImageSaliencyService.swift index 6c5bc8ad4935..e05249e07fa4 100644 --- a/Modules/Sources/AsyncImageKit/Helpers/ImageSaliencyService.swift +++ b/Modules/Sources/AsyncImageKit/Helpers/ImageSaliencyService.swift @@ -138,7 +138,8 @@ private final class SaliencyCache: @unchecked Sendable { } func cachedRect(for url: URL) -> CGRect? { - lock.withLock { store[url.absoluteString]} + // swiftlint:disable:next redundant_nil_coalescing + lock.withLock { store[url.absoluteString] ?? nil } } func store(_ rect: CGRect?, for url: URL) {