From bb18197860ffdb491f010648ec903003b6e1bb65 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Mon, 20 Jul 2026 16:22:28 -0500 Subject: v4.8.3: Clear SonarCloud new-code issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the 4 reported bugs and ~97 code smells flagged in the new-code period. No behavior changes. Bugs (swift:S3923) — DomainInspectionService's confidenceFor* helpers each returned `error == nil ? .low : .low`, an inert conditional. Simplified to `return .low` and dropped the now-unused `error` parameter. Smells: - Merged 14 identical `.empty`/`.error` switch branches in DomainViewModel - Consolidated duplicate implementations (clearPresentedResults/reset, String.nonEmpty/nilIfEmpty, ExportFormat.id/fileExtension) - Extracted nested ternaries into TLSGrade.tone, EmailSecurityGrade.tone, and ChangeImpactClassification.color; removed ContentView.impactColor and the duplicate mapping in BatchResultsView - Documented empty closures and singleton inits - Marked unused protocol-conformance parameters `_` - Renamed CloudSyncTrigger.`import` to `imported` (raw value preserved) and SSLSessionDelegate's _serverTrust/_tlsMetadata - Merged nested ifs in the DER parser; flattened closure nesting in PortScanService and IntegrationService - Replaced two-case switches with if/else Left open: S107 (init parameter counts), S115 (constants mirroring DoH and ipapi JSON keys), S1075 (false positives on https:// literals), and two S117 hits on SwiftUI $binding shorthand. These want a Won't Fix resolution in SonarCloud, not a code change. --- DomainDig.xcodeproj/project.pbxproj | 24 +++---- DomainDig/AppVersion.swift | 2 +- DomainDig/AuditModeView.swift | 2 +- DomainDig/BatchResultsView.swift | 2 +- DomainDig/CloudSharingLifecycle.swift | 8 +-- DomainDig/CloudSharingSheet.swift | 4 +- DomainDig/CloudSyncService.swift | 4 +- DomainDig/ContentView.swift | 87 +++++++++++++++-------- DomainDig/DashboardView.swift | 6 +- DomainDig/DomainAvailabilityService.swift | 1 + DomainDig/DomainDigIntents.swift | 2 +- DomainDig/DomainInsightEngine.swift | 11 ++- DomainDig/DomainMonitoringService.swift | 6 +- DomainDig/DomainViewModel.swift | 98 +++++--------------------- DomainDig/HistoryView.swift | 6 +- DomainDig/IntegrationService.swift | 10 ++- DomainDig/LocalNotificationService.swift | 13 ++-- DomainDig/Models.swift | 14 ++-- DomainDig/PortScanService.swift | 39 +++++----- DomainDig/PurchaseService.swift | 6 +- DomainDig/SSLCheckService.swift | 60 ++++++++-------- DomainDig/ScheduledReportService.swift | 6 +- DomainDig/SweepActivityController.swift | 2 +- DomainDig/WatchlistView.swift | 12 ++-- DomainDig/WorkflowsView.swift | 16 ++--- DomainDigWidget/DomainDigPortfolioWidget.swift | 9 ++- DomainInspectionService.swift | 24 +++---- DomainReportExporter.swift | 2 +- RELEASE_ROADMAP.md | 33 ++++++++- 29 files changed, 258 insertions(+), 251 deletions(-) diff --git a/DomainDig.xcodeproj/project.pbxproj b/DomainDig.xcodeproj/project.pbxproj index 7aa48ed..8889f33 100644 --- a/DomainDig.xcodeproj/project.pbxproj +++ b/DomainDig.xcodeproj/project.pbxproj @@ -497,7 +497,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = DomainDig/DomainDig.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 42; + CURRENT_PROJECT_VERSION = 43; DEVELOPMENT_TEAM = ZCNAX3VL9D; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -514,7 +514,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.8.2; + MARKETING_VERSION = 4.8.3; PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; @@ -534,7 +534,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = DomainDig/DomainDig.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 42; + CURRENT_PROJECT_VERSION = 43; DEVELOPMENT_TEAM = ZCNAX3VL9D; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -551,7 +551,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.8.2; + MARKETING_VERSION = 4.8.3; PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig; PRODUCT_NAME = "$(TARGET_NAME)"; STRING_CATALOG_GENERATE_SYMBOLS = YES; @@ -570,7 +570,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CODE_SIGN_ENTITLEMENTS = DomainDigWidget/DomainDigWidget.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 42; + CURRENT_PROJECT_VERSION = 43; DEVELOPMENT_TEAM = ZCNAX3VL9D; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = DomainDigWidget/Info.plist; @@ -581,7 +581,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 4.8.2; + MARKETING_VERSION = 4.8.3; PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig.DomainDigWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -599,7 +599,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CODE_SIGN_ENTITLEMENTS = DomainDigWidget/DomainDigWidget.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 42; + CURRENT_PROJECT_VERSION = 43; DEVELOPMENT_TEAM = ZCNAX3VL9D; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = DomainDigWidget/Info.plist; @@ -610,7 +610,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 4.8.2; + MARKETING_VERSION = 4.8.3; PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig.DomainDigWidget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -628,7 +628,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CODE_SIGN_ENTITLEMENTS = DomainDigShareExtension/DomainDigShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 42; + CURRENT_PROJECT_VERSION = 43; DEVELOPMENT_TEAM = ZCNAX3VL9D; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = DomainDigShareExtension/Info.plist; @@ -639,7 +639,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 4.8.2; + MARKETING_VERSION = 4.8.3; PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig.DomainDigShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -657,7 +657,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CODE_SIGN_ENTITLEMENTS = DomainDigShareExtension/DomainDigShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 42; + CURRENT_PROJECT_VERSION = 43; DEVELOPMENT_TEAM = ZCNAX3VL9D; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = DomainDigShareExtension/Info.plist; @@ -668,7 +668,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 4.8.2; + MARKETING_VERSION = 4.8.3; PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.DomainDig.DomainDigShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/DomainDig/AppVersion.swift b/DomainDig/AppVersion.swift index e8402b8..a0956df 100644 --- a/DomainDig/AppVersion.swift +++ b/DomainDig/AppVersion.swift @@ -2,6 +2,6 @@ import Foundation enum AppVersion { nonisolated static var current: String { - "4.8.2" + "4.8.3" } } diff --git a/DomainDig/AuditModeView.swift b/DomainDig/AuditModeView.swift index fcd4083..b3e363e 100644 --- a/DomainDig/AuditModeView.swift +++ b/DomainDig/AuditModeView.swift @@ -57,7 +57,7 @@ struct AuditModeView: View { } } .alert("Audit Export", isPresented: Binding(get: { exportMessage != nil }, set: { if !$0 { exportMessage = nil } })) { - Button("OK", role: .cancel) {} + Button("OK", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } } message: { Text(exportMessage ?? "") } diff --git a/DomainDig/BatchResultsView.swift b/DomainDig/BatchResultsView.swift index e97899e..027d074 100644 --- a/DomainDig/BatchResultsView.swift +++ b/DomainDig/BatchResultsView.swift @@ -104,7 +104,7 @@ struct BatchResultRowView: View { if let changeClassification = result.changeClassification { Text("Impact: \(changeClassification.title)") .font(appDensity.font(.caption2)) - .foregroundStyle(changeClassification == .critical ? .red : (changeClassification == .warning ? .yellow : .secondary)) + .foregroundStyle(changeClassification.color) } if let errorMessage = result.errorMessage { diff --git a/DomainDig/CloudSharingLifecycle.swift b/DomainDig/CloudSharingLifecycle.swift index fc5de80..26425e5 100644 --- a/DomainDig/CloudSharingLifecycle.swift +++ b/DomainDig/CloudSharingLifecycle.swift @@ -3,9 +3,9 @@ import UIKit final class DomainDigAppDelegate: NSObject, UIApplicationDelegate { func application( - _ application: UIApplication, + _: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, - options: UIScene.ConnectionOptions + options _: UIScene.ConnectionOptions ) -> UISceneConfiguration { let configuration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role) configuration.delegateClass = DomainDigSceneDelegate.self @@ -14,12 +14,12 @@ final class DomainDigAppDelegate: NSObject, UIApplicationDelegate { } final class DomainDigSceneDelegate: NSObject, UIWindowSceneDelegate { - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + func scene(_: UIScene, willConnectTo _: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let metadata = connectionOptions.cloudKitShareMetadata else { return } accept(metadata: metadata) } - func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) { + func windowScene(_: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) { accept(metadata: cloudKitShareMetadata) } diff --git a/DomainDig/CloudSharingSheet.swift b/DomainDig/CloudSharingSheet.swift index 3d524ef..8318e2a 100644 --- a/DomainDig/CloudSharingSheet.swift +++ b/DomainDig/CloudSharingSheet.swift @@ -13,7 +13,7 @@ struct CloudSharingSheet: UIViewControllerRepresentable { Coordinator(dismiss: dismiss, title: title) } - func makeUIViewController(context: Context) -> UIViewController { + func makeUIViewController(context _: Context) -> UIViewController { UIViewController() } @@ -77,7 +77,7 @@ struct CloudSharingSheet: UIViewControllerRepresentable { } } - func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + func presentationControllerDidDismiss(_: UIPresentationController) { dismissAction() } } diff --git a/DomainDig/CloudSyncService.swift b/DomainDig/CloudSyncService.swift index 2cb4cfe..985d2a4 100644 --- a/DomainDig/CloudSyncService.swift +++ b/DomainDig/CloudSyncService.swift @@ -40,7 +40,7 @@ enum CloudSyncTrigger: String { case launch case automatic case manual - case `import` + case imported = "import" } enum ShareableEntity: Identifiable, Hashable { @@ -1186,7 +1186,7 @@ final class CloudSyncService { } private func collaborationMetadata( - for record: CKRecord, + for _: CKRecord, share: CKShare?, scope: SyncDatabaseScope ) -> CollaborationMetadata { diff --git a/DomainDig/ContentView.swift b/DomainDig/ContentView.swift index 3e1015d..40c97d1 100644 --- a/DomainDig/ContentView.swift +++ b/DomainDig/ContentView.swift @@ -569,13 +569,13 @@ struct ContentView: View { shareSingleResults(format: .pdf) } } else { - Button("CSV Export • Available in Pro") {} + Button("CSV Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("JSON Export • Available in Pro") {} + Button("JSON Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("Markdown Export • Available in Pro") {} + Button("Markdown Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("PDF Export • Available in Pro") {} + Button("PDF Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) } } label: { @@ -621,13 +621,13 @@ struct ContentView: View { shareBatchResults(format: .pdf) } } else { - Button("Batch CSV • Available in Pro") {} + Button("Batch CSV • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("Batch JSON • Available in Pro") {} + Button("Batch JSON • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("Batch Markdown • Available in Pro") {} + Button("Batch Markdown • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("Batch PDF • Available in Pro") {} + Button("Batch PDF • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) } } label: { @@ -1040,10 +1040,10 @@ struct DomainChangeSummaryView: View { .clipShape(Capsule()) Text(summary.impactClassification.title.uppercased()) .font(appDensity.font(.caption2)) - .foregroundStyle(impactColor(summary.impactClassification)) + .foregroundStyle(summary.impactClassification.color) .padding(.horizontal, 8) .padding(.vertical, 4) - .background(impactColor(summary.impactClassification).opacity(0.16)) + .background(summary.impactClassification.color.opacity(0.16)) .clipShape(Capsule()) Text(summary.generatedAt, style: .time) .font(appDensity.font(.caption2)) @@ -1107,16 +1107,6 @@ struct DomainChangeSummaryView: View { } } - private func impactColor(_ impact: ChangeImpactClassification) -> Color { - switch impact { - case .informational: - return .secondary - case .warning: - return .yellow - case .critical: - return .red - } - } } struct DomainDiffView: View { @@ -2008,7 +1998,7 @@ struct WebSectionView: View { } SectionTrustMetadataView(provenance: tlsProvenance, confidence: nil) if !sslLoading, let tlsSummary { - LabeledValueRow(row: InfoRowViewData(label: "TLS Grade", value: tlsSummary.tlsGrade.rawValue, tone: tlsSummary.tlsGrade == .a ? .success : (tlsSummary.tlsGrade == .f ? .failure : .warning))) + LabeledValueRow(row: InfoRowViewData(label: "TLS Grade", value: tlsSummary.tlsGrade.rawValue, tone: tlsSummary.tlsGrade.tone)) ForEach(Array(tlsSummary.tlsHighlights.enumerated()), id: \.offset) { _, highlight in MessageRowView(text: highlight, isError: isTLSHighlightError(highlight)) } @@ -2153,7 +2143,7 @@ struct EmailSectionView: View { .opacity(loading ? 0 : 1) } if let assessment, let grade = assessment.grade { - LabeledValueRow(row: InfoRowViewData(label: "Grade", value: grade.rawValue, tone: grade == .a ? .success : (grade == .f ? .failure : .warning))) + LabeledValueRow(row: InfoRowViewData(label: "Grade", value: grade.rawValue, tone: grade.tone)) if !assessment.reasons.isEmpty { Text(assessment.reasons.joined(separator: " | ")) .font(appDensity.font(.caption2)) @@ -2897,6 +2887,11 @@ private struct LocalAPISettingsView: View { @State private var localAPIService = LocalAPIService.shared @State private var portText = "" + private var statusText: String { + if localAPIService.isRunning { return "Running" } + return localAPIService.config.isEnabled ? "Stopped" : "Disabled" + } + var body: some View { Form { Section("Local API") { @@ -2923,7 +2918,7 @@ private struct LocalAPISettingsView: View { .keyboardType(.numberPad) LabeledContent("Address", value: localAPIService.address) - LabeledContent("Status", value: localAPIService.isRunning ? "Running" : (localAPIService.config.isEnabled ? "Stopped" : "Disabled")) + LabeledContent("Status", value: statusText) LabeledContent("Token", value: localAPIService.maskedToken) if let statusMessage = localAPIService.statusMessage { @@ -3339,7 +3334,7 @@ private struct DataPortabilitySettingsView: View { Button("Replace", role: .destructive) { applyPendingImport() } - Button("Cancel", role: .cancel) {} + Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } } message: { Text("Replace mode overwrites local data covered by the imported file and may remove items that are only on this device.") } @@ -3347,7 +3342,7 @@ private struct DataPortabilitySettingsView: View { get: { pendingImportError != nil }, set: { if !$0 { pendingImportError = nil } } )) { - Button("OK", role: .cancel) {} + Button("OK", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } } message: { Text(pendingImportError ?? "The import could not be completed.") } @@ -3585,7 +3580,7 @@ private struct DataManagementSettingsView: View { Button("Clear", role: .destructive) { viewModel.clearHistory() } - Button("Cancel", role: .cancel) {} + Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } } message: { Text("This removes saved lookup snapshots and clears monitoring run history on this device.") } @@ -3593,7 +3588,7 @@ private struct DataManagementSettingsView: View { Button("Clear", role: .destructive) { viewModel.clearLookupCache() } - Button("Cancel", role: .cancel) {} + Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } } message: { Text("This clears the in-memory lookup cache and cancels any cached in-flight work.") } @@ -3601,7 +3596,7 @@ private struct DataManagementSettingsView: View { Button("Clear", role: .destructive) { viewModel.clearWorkflows() } - Button("Cancel", role: .cancel) {} + Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } } message: { Text("This removes saved workflows only. History, tracked domains, and saved reports stay intact.") } @@ -3609,12 +3604,12 @@ private struct DataManagementSettingsView: View { Button("Clear", role: .destructive) { viewModel.clearTrackedDomains() } - Button("Cancel", role: .cancel) {} + Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } } message: { Text("This removes the watchlist and clears monitoring run history. History and workflows stay intact.") } .alert("Delete All Data?", isPresented: $showDeleteAllConfirmation) { - Button("Cancel", role: .cancel) {} + Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } Button("Delete All Data", role: .destructive) { deleteAllData() } @@ -3625,7 +3620,7 @@ private struct DataManagementSettingsView: View { get: { deleteAllErrorMessage != nil }, set: { if !$0 { deleteAllErrorMessage = nil } } )) { - Button("OK", role: .cancel) {} + Button("OK", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } } message: { Text(deleteAllErrorMessage ?? "The local data reset could not be completed.") } @@ -3745,6 +3740,36 @@ private struct DataImportPreviewSheet: View { } } +extension ChangeImpactClassification { + var color: Color { + switch self { + case .informational: return .secondary + case .warning: return .yellow + case .critical: return .red + } + } +} + +extension TLSGrade { + var tone: ResultTone { + switch self { + case .a: return .success + case .f: return .failure + default: return .warning + } + } +} + +extension EmailSecurityGrade { + var tone: ResultTone { + switch self { + case .a: return .success + case .f: return .failure + default: return .warning + } + } +} + #Preview { ContentView(viewModel: DomainViewModel()) } diff --git a/DomainDig/DashboardView.swift b/DomainDig/DashboardView.swift index 087f080..398ff3e 100644 --- a/DomainDig/DashboardView.swift +++ b/DomainDig/DashboardView.swift @@ -239,22 +239,26 @@ struct DashboardView: View { let warningCount = states.filter { $0.health == .warning }.count let title: String let color: Color + let systemImage: String if criticalCount > 0 { title = "\(criticalCount) critical" color = .red + systemImage = "exclamationmark.octagon.fill" } else if warningCount > 0 { title = "\(warningCount) warning" color = .yellow + systemImage = "exclamationmark.triangle.fill" } else { title = "Healthy" color = .green + systemImage = "checkmark.circle.fill" } return AppStatusBadgeView( model: .init( title: title, - systemImage: criticalCount > 0 ? "exclamationmark.octagon.fill" : (warningCount > 0 ? "exclamationmark.triangle.fill" : "checkmark.circle.fill"), + systemImage: systemImage, foregroundColor: color, backgroundColor: color.opacity(0.16) ) diff --git a/DomainDig/DomainAvailabilityService.swift b/DomainDig/DomainAvailabilityService.swift index b671fbc..1d112e1 100644 --- a/DomainDig/DomainAvailabilityService.swift +++ b/DomainDig/DomainAvailabilityService.swift @@ -47,6 +47,7 @@ struct DomainAvailabilityService { return .registered } } catch { + // No A record (or the lookup failed); fall through to the NS probe. } do { diff --git a/DomainDig/DomainDigIntents.swift b/DomainDig/DomainDigIntents.swift index c0c73cc..8b46aa7 100644 --- a/DomainDig/DomainDigIntents.swift +++ b/DomainDig/DomainDigIntents.swift @@ -149,7 +149,7 @@ struct RunSweepIntent: AppIntent { final class DomainDigIntentRouter { static let shared = DomainDigIntentRouter() var pendingAction: DomainDigDeepLink.Action? - private init() {} + private init() { /* Singleton; use the shared instance. */ } } /// Exposes DomainDig intents to Spotlight and Siri with invocation phrases. diff --git a/DomainDig/DomainInsightEngine.swift b/DomainDig/DomainInsightEngine.swift index f1de9c0..6f8edeb 100644 --- a/DomainDig/DomainInsightEngine.swift +++ b/DomainDig/DomainInsightEngine.swift @@ -412,7 +412,7 @@ enum DomainInsightEngine { if hasMXRecords(snapshot), snapshot.emailSecurity == nil { patterns.append("MX records exist without corresponding email security records") } - if snapshot.sslInfo != nil && !(snapshot.dnsSections.first(where: { $0.recordType == .CAA })?.records.isEmpty == false) { + if snapshot.sslInfo != nil, snapshot.dnsSections.first(where: { $0.recordType == .CAA })?.records.isEmpty != false { patterns.append("TLS is active but no CAA record was found") } @@ -427,9 +427,16 @@ enum DomainInsightEngine { let dmarcFound = result.dmarc.found let dmarcStrict = isStrictDMARC(result.dmarc.value) + let dmarcReason: String + if !dmarcFound { + dmarcReason = "DMARC missing" + } else { + dmarcReason = dmarcStrict ? "DMARC policy is strict" : "DMARC policy is not strict" + } + let reasons = [ spfFound ? "SPF present" : "SPF missing", - dmarcFound ? (dmarcStrict ? "DMARC policy is strict" : "DMARC policy is not strict") : "DMARC missing", + dmarcReason, dkimFound ? "DKIM present" : "DKIM not detected" ] diff --git a/DomainDig/DomainMonitoringService.swift b/DomainDig/DomainMonitoringService.swift index 917ede0..eb6a581 100644 --- a/DomainDig/DomainMonitoringService.swift +++ b/DomainDig/DomainMonitoringService.swift @@ -85,7 +85,7 @@ final class DomainMonitoringScheduler { private var isRegistered = false - private init() {} + private init() { /* Singleton; use the shared instance. */ } func registerBackgroundTask() { #if canImport(BackgroundTasks) @@ -613,8 +613,8 @@ final class DomainMonitoringService { private func processAdaptiveMonitoringState( trackedDomain: TrackedDomain, - snapshot: LookupSnapshot, - previousSnapshot: LookupSnapshot?, + snapshot _: LookupSnapshot, + previousSnapshot _: LookupSnapshot?, alertDescriptor: MonitoringAlertDescriptor?, settings: MonitoringSettings, notificationsAuthorized: Bool, diff --git a/DomainDig/DomainViewModel.swift b/DomainDig/DomainViewModel.swift index 674efdf..256c1e1 100644 --- a/DomainDig/DomainViewModel.swift +++ b/DomainDig/DomainViewModel.swift @@ -1280,19 +1280,7 @@ final class DomainViewModel { } func clearPresentedResults() { - lookupTask?.cancel() - customPortScanTask?.cancel() - batchTask?.cancel() - hasRun = false - searchedDomain = "" - lastLookupDurationMs = nil - currentDiffSections = [] - currentChangeSummary = nil - ownershipDiff = [] - currentReport = nil - refreshingTrackedDomainID = nil - clearBatchState() - clearLookupState() + reset() } func run() { @@ -1857,7 +1845,7 @@ final class DomainViewModel { portabilityStatusMessage = result.summary CloudSyncService.shared.markAppSettingsChanged() CloudSyncService.shared.markMonitoringSettingsChanged(localActivationConfirmed: monitoringSettings.isEnabled) - CloudSyncService.shared.scheduleSyncIfNeeded(trigger: .import) + CloudSyncService.shared.scheduleSyncIfNeeded(trigger: .imported) return result } @@ -2174,10 +2162,7 @@ final class DomainViewModel { case let .success(sections): dnsSections = sections dnsError = nil - case let .empty(message): - dnsSections = [] - dnsError = message - case let .error(message): + case let .empty(message), let .error(message): dnsSections = [] dnsError = message } @@ -2199,10 +2184,7 @@ final class DomainViewModel { case let .success(info): sslInfo = info sslError = nil - case let .empty(message): - sslInfo = nil - sslError = message - case let .error(message): + case let .empty(message), let .error(message): sslInfo = nil sslError = message } @@ -2228,15 +2210,7 @@ final class DomainViewModel { httpProtocol = headersResult.httpProtocol http3Advertised = headersResult.http3Advertised httpHeadersError = nil - case let .empty(message): - httpHeaders = [] - httpSecurityGrade = nil - httpStatusCode = nil - httpResponseTimeMs = nil - httpProtocol = nil - http3Advertised = false - httpHeadersError = message - case let .error(message): + case let .empty(message), let .error(message): httpHeaders = [] httpSecurityGrade = nil httpStatusCode = nil @@ -2255,10 +2229,7 @@ final class DomainViewModel { case let .success(results): reachabilityResults = results reachabilityError = nil - case let .empty(message): - reachabilityResults = [] - reachabilityError = message - case let .error(message): + case let .empty(message), let .error(message): reachabilityResults = [] reachabilityError = message } @@ -2272,10 +2243,7 @@ final class DomainViewModel { case let .success(emailResult): emailSecurity = emailResult emailSecurityError = nil - case let .empty(message): - emailSecurity = nil - emailSecurityError = message - case let .error(message): + case let .empty(message), let .error(message): emailSecurity = nil emailSecurityError = message } @@ -2289,10 +2257,7 @@ final class DomainViewModel { case let .success(ownership): ownershipResult = ownership ownershipError = nil - case let .empty(message): - ownershipResult = nil - ownershipError = message - case let .error(message): + case let .empty(message), let .error(message): ownershipResult = nil ownershipError = message } @@ -2306,10 +2271,7 @@ final class DomainViewModel { case let .success(record): ptrRecord = record ptrError = nil - case let .empty(message): - ptrRecord = nil - ptrError = message - case let .error(message): + case let .empty(message), let .error(message): ptrRecord = nil ptrError = message } @@ -2323,10 +2285,7 @@ final class DomainViewModel { case let .success(hops): redirectChain = hops redirectChainError = nil - case let .empty(message): - redirectChain = [] - redirectChainError = message - case let .error(message): + case let .empty(message), let .error(message): redirectChain = [] redirectChainError = message } @@ -2340,10 +2299,7 @@ final class DomainViewModel { case let .success(results): subdomains = results subdomainsError = nil - case let .empty(message): - subdomains = [] - subdomainsError = message - case let .error(message): + case let .empty(message), let .error(message): subdomains = [] subdomainsError = message } @@ -2358,11 +2314,7 @@ final class DomainViewModel { guard !Task.isCancelled, isCurrentLookup(lookupID) else { return } portScanResults = enrichedResults portScanError = nil - case let .empty(message): - guard !Task.isCancelled, isCurrentLookup(lookupID) else { return } - portScanResults = [] - portScanError = message - case let .error(message): + case let .empty(message), let .error(message): guard !Task.isCancelled, isCurrentLookup(lookupID) else { return } portScanResults = [] portScanError = message @@ -2377,10 +2329,7 @@ final class DomainViewModel { case let .success(geolocation): ipGeolocation = geolocation ipGeolocationError = nil - case let .empty(message): - ipGeolocation = nil - ipGeolocationError = message - case let .error(message): + case let .empty(message), let .error(message): ipGeolocation = nil ipGeolocationError = message } @@ -2408,10 +2357,7 @@ final class DomainViewModel { customPortResults = results customPortScanError = nil _ = saveHistoryEntry(replaceLatest: true) - case let .empty(message): - customPortResults = [] - customPortScanError = message - case let .error(message): + case let .empty(message), let .error(message): customPortResults = [] customPortScanError = message } @@ -4024,7 +3970,7 @@ final class DomainViewModel { static func summaryFields(from snapshot: LookupSnapshot) -> [SummaryFieldViewData] { [ - SummaryFieldViewData(label: "Domain", value: snapshot.domain.nonEmpty ?? "Unavailable", tone: .primary), + SummaryFieldViewData(label: "Domain", value: snapshot.domain.nilIfEmpty ?? "Unavailable", tone: .primary), SummaryFieldViewData(label: "Observed IP", value: primaryIPAddress(from: snapshot) ?? "Unavailable", tone: .primary), SummaryFieldViewData(label: "Observed Redirect", value: finalRedirectTarget(from: snapshot) ?? "Unavailable", tone: .secondary), SummaryFieldViewData(label: "Inference", value: availabilityInference(from: snapshot), tone: availabilityTone(snapshot.availabilityResult?.status)), @@ -4780,10 +4726,7 @@ final class DomainViewModel { case let .success(pricing): domainPricing = pricing domainPricingError = nil - case let .empty(message): - domainPricing = nil - domainPricingError = conciseExternalMessage(message, fallback: "External pricing unavailable") - case let .error(message): + case let .empty(message), let .error(message): domainPricing = nil domainPricingError = conciseExternalMessage(message, fallback: "External pricing unavailable") } @@ -4803,10 +4746,7 @@ final class DomainViewModel { case let .success(result): reputation = result reputationError = nil - case let .empty(message): - reputation = nil - reputationError = conciseExternalMessage(message, fallback: "Reputation check unavailable") - case let .error(message): + case let .empty(message), let .error(message): reputation = nil reputationError = conciseExternalMessage(message, fallback: "Reputation check unavailable") } @@ -4865,10 +4805,6 @@ final class DomainViewModel { } private extension String { - var nonEmpty: String? { - isEmpty ? nil : self - } - var nilIfEmpty: String? { isEmpty ? nil : self } diff --git a/DomainDig/HistoryView.swift b/DomainDig/HistoryView.swift index 316fcce..5350168 100644 --- a/DomainDig/HistoryView.swift +++ b/DomainDig/HistoryView.swift @@ -115,7 +115,7 @@ struct HistoryView: View { Button("Clear All", role: .destructive) { viewModel.clearHistory() } - Button("Cancel", role: .cancel) {} + Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } } message: { Text("This will delete all saved history entries. This cannot be undone.") } @@ -314,7 +314,7 @@ struct HistoryDetailView: View { isCloudflareProxied: snapshot.httpHeaders.contains(where: { $0.name.lowercased() == "cf-ray" }), customPortsExpanded: .constant(false), customPortInput: .constant(""), - onScanCustomPorts: {} + onScanCustomPorts: { /* Historical snapshot; re-scanning is not offered here. */ } ) .padding(.top, appDensity.metrics.sectionSpacing) } @@ -344,7 +344,7 @@ struct HistoryDetailView: View { Button("Run with Snapshot Resolver") { viewModel.rerunLookup(from: entry, useSnapshotResolver: true) } - Button("Cancel", role: .cancel) {} + Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } } message: { Text(viewModel.resolverMismatchNote(for: entry) ?? "Choose how to reproduce this snapshot.") } diff --git a/DomainDig/IntegrationService.swift b/DomainDig/IntegrationService.swift index 23085d3..de24c23 100644 --- a/DomainDig/IntegrationService.swift +++ b/DomainDig/IntegrationService.swift @@ -768,9 +768,7 @@ private final class SMTPChannel { connection.stateUpdateHandler = { [weak self] state in switch state { case .ready: - DispatchQueue.global(qos: .utility).async { - self?.startReceiveLoop() - } + self?.scheduleReceiveLoop() continuation.resume() case .failed(let error): continuation.resume(throwing: error) @@ -782,6 +780,12 @@ private final class SMTPChannel { } } + private func scheduleReceiveLoop() { + DispatchQueue.global(qos: .utility).async { [weak self] in + self?.startReceiveLoop() + } + } + func cancel() { connection.cancel() } diff --git a/DomainDig/LocalNotificationService.swift b/DomainDig/LocalNotificationService.swift index d3474e3..3648cb8 100644 --- a/DomainDig/LocalNotificationService.swift +++ b/DomainDig/LocalNotificationService.swift @@ -5,7 +5,7 @@ import UserNotifications final class LocalNotificationService { static let shared = LocalNotificationService() - private init() {} + private init() { /* Singleton; use the shared instance. */ } static let domainUserInfoKey = "domain" static let domainCategoryIdentifier = "domain-event" @@ -188,14 +188,14 @@ private final class NotificationCenterDelegate: NSObject, UNUserNotificationCent static let shared = NotificationCenterDelegate() func userNotificationCenter( - _ center: UNUserNotificationCenter, - willPresent notification: UNNotification + _: UNUserNotificationCenter, + willPresent _: UNNotification ) async -> UNNotificationPresentationOptions { [.banner, .list, .sound] } func userNotificationCenter( - _ center: UNUserNotificationCenter, + _: UNUserNotificationCenter, didReceive response: UNNotificationResponse ) async { let userInfo = response.notification.request.content.userInfo @@ -204,10 +204,9 @@ private final class NotificationCenterDelegate: NSObject, UNUserNotificationCent else { return } let action: DomainDigDeepLink.Action - switch response.actionIdentifier { - case LocalNotificationService.reinspectActionIdentifier: + if response.actionIdentifier == LocalNotificationService.reinspectActionIdentifier { action = .inspect(domain) - default: + } else { // Default tap: open the tracked domain's detail. action = .detail(domain) } diff --git a/DomainDig/Models.swift b/DomainDig/Models.swift index 6989cba..d83dc79 100644 --- a/DomainDig/Models.swift +++ b/DomainDig/Models.swift @@ -244,8 +244,14 @@ struct DomainChangeSummary: Codable, Equatable { changedSections = try container.decodeIfPresent([String].self, forKey: .changedSections) ?? [] generatedAt = try container.decode(Date.self, forKey: .generatedAt) severity = try container.decodeIfPresent(ChangeSeverity.self, forKey: .severity) ?? (hasChanges ? .medium : .low) + let defaultImpact: ChangeImpactClassification + if severity == .high { + defaultImpact = .critical + } else { + defaultImpact = hasChanges ? .warning : .informational + } impactClassification = try container.decodeIfPresent(ChangeImpactClassification.self, forKey: .impactClassification) - ?? (severity == .high ? .critical : (hasChanges ? .warning : .informational)) + ?? defaultImpact message = try container.decodeIfPresent(String.self, forKey: .message) ?? (changedSections.isEmpty ? "No meaningful changes" : changedSections.joined(separator: " • ")) observedFacts = try container.decodeIfPresent([String].self, forKey: .observedFacts) ?? [] @@ -562,12 +568,10 @@ enum DomainClassificationKind: String, Codable, CaseIterable, Sendable { case unknown var title: String { - switch self { - case .staticSite: + if self == .staticSite { return "Static" - default: - return rawValue.capitalized } + return rawValue.capitalized } } diff --git a/DomainDig/PortScanService.swift b/DomainDig/PortScanService.swift index 1f3e649..808b38a 100644 --- a/DomainDig/PortScanService.swift +++ b/DomainDig/PortScanService.swift @@ -89,24 +89,7 @@ struct PortScanService { switch state { case .ready: connection.receive(minimumIncompleteLength: 1, maximumLength: 256) { data, _, _, error in - guard error == nil, - let data, - !data.isEmpty, - let rawBanner = String(data: data, encoding: .utf8) else { - context.finish(with: nil) - return - } - - let printableBanner = rawBanner.filter { character in - guard let scalar = character.unicodeScalars.first, - character.unicodeScalars.count == 1 else { - return false - } - return (32...126).contains(scalar.value) - } - - let banner = String(printableBanner.prefix(80)) - context.finish(with: banner.isEmpty ? nil : banner) + context.finish(with: printableBanner(from: data, error: error)) } case .failed, .cancelled: context.finish(with: nil) @@ -123,6 +106,26 @@ struct PortScanService { } } + private static func printableBanner(from data: Data?, error: Error?) -> String? { + guard error == nil, + let data, + !data.isEmpty, + let rawBanner = String(data: data, encoding: .utf8) else { + return nil + } + + let printable = rawBanner.filter { character in + guard let scalar = character.unicodeScalars.first, + character.unicodeScalars.count == 1 else { + return false + } + return (32...126).contains(scalar.value) + } + + let banner = String(printable.prefix(80)) + return banner.isEmpty ? nil : banner + } + private static func probe(domain: String, port: UInt16) async -> PortProbeResult { await probe(domain: domain, port: port, timeout: 1.5) } diff --git a/DomainDig/PurchaseService.swift b/DomainDig/PurchaseService.swift index d31afc7..4ab8ef4 100644 --- a/DomainDig/PurchaseService.swift +++ b/DomainDig/PurchaseService.swift @@ -288,12 +288,10 @@ final class PurchaseService { private func storeMessage(for error: Error, fallback: String) -> String { if let storeKitError = error as? StoreKitError { - switch storeKitError { - case .networkError: + if case .networkError = storeKitError { return "The App Store is offline right now." - default: - return fallback } + return fallback } let message = error.localizedDescription.trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/DomainDig/SSLCheckService.swift b/DomainDig/SSLCheckService.swift index 73c00bd..b1affa8 100644 --- a/DomainDig/SSLCheckService.swift +++ b/DomainDig/SSLCheckService.swift @@ -156,10 +156,9 @@ private enum DERCertificateParser { var offset = tbsContent.contentStart // Skip version (explicit tag [0]) if present - if offset < bytes.count && (bytes[offset] & 0xE0) == 0xA0 { - if let tagLen = readTagAndLength(bytes, offset: offset) { - offset = tagLen.contentStart + tagLen.length - } + if offset < bytes.count, (bytes[offset] & 0xE0) == 0xA0, + let tagLen = readTagAndLength(bytes, offset: offset) { + offset = tagLen.contentStart + tagLen.length } // Skip serialNumber @@ -199,11 +198,10 @@ private enum DERCertificateParser { // Extensions are in an explicit tag [3] while offset < tbsContent.contentStart + tbsContent.length { if bytes[offset] == 0xA3 { - if let extWrapper = readTagAndLength(bytes, offset: offset) { - // Inside is a SEQUENCE of SEQUENCE extensions - if let extsSeq = readTagAndLength(bytes, offset: extWrapper.contentStart) { - result.subjectAltNames = extractSANs(bytes, sequenceStart: extsSeq.contentStart, length: extsSeq.length) - } + // Inside the wrapper is a SEQUENCE of SEQUENCE extensions + if let extWrapper = readTagAndLength(bytes, offset: offset), + let extsSeq = readTagAndLength(bytes, offset: extWrapper.contentStart) { + result.subjectAltNames = extractSANs(bytes, sequenceStart: extsSeq.contentStart, length: extsSeq.length) } break } @@ -267,27 +265,25 @@ private enum DERCertificateParser { if oidBytes == sanOID { var valuePos = oidTL.contentStart + oidTL.length // Skip optional critical BOOLEAN - if valuePos < extEnd && bytes[valuePos] == 0x01 { - if let boolTL = readTagAndLength(bytes, offset: valuePos) { - valuePos = boolTL.contentStart + boolTL.length - } + if valuePos < extEnd, bytes[valuePos] == 0x01, + let boolTL = readTagAndLength(bytes, offset: valuePos) { + valuePos = boolTL.contentStart + boolTL.length } // The value is an OCTET STRING wrapping a SEQUENCE of GeneralNames - if let octetTL = readTagAndLength(bytes, offset: valuePos) { - if let sanSeq = readTagAndLength(bytes, offset: octetTL.contentStart) { - let sanEnd = sanSeq.contentStart + sanSeq.length - var sanPos = sanSeq.contentStart - while sanPos < sanEnd { - guard let nameTL = readTagAndLength(bytes, offset: sanPos) else { break } - // Context tag [2] = dNSName (IA5String) - if (bytes[sanPos] & 0x1F) == 2 { - let nameBytes = bytes[nameTL.contentStart.. ScheduledReportOutcome { + func generateReport(trigger _: MonitoringRunTrigger, requireEnabledSetting: Bool) async -> ScheduledReportOutcome { var settings = ScheduledReportStorage.loadSettings() guard FeatureAccessService.hasAccess(to: .automatedMonitoring) else { @@ -191,7 +191,7 @@ final class ScheduledReportScheduler { private var isRegistered = false - private init() {} + private init() { /* Singleton; use the shared instance. */ } func registerBackgroundTask() { #if canImport(BackgroundTasks) diff --git a/DomainDig/SweepActivityController.swift b/DomainDig/SweepActivityController.swift index 26fcb2d..a00bb2f 100644 --- a/DomainDig/SweepActivityController.swift +++ b/DomainDig/SweepActivityController.swift @@ -8,7 +8,7 @@ final class SweepActivityController { private var activity: Activity? - private init() {} + private init() { /* Singleton; use the shared instance. */ } func begin(title: String, total: Int) { guard ActivityAuthorizationInfo().areActivitiesEnabled else { return } diff --git a/DomainDig/WatchlistView.swift b/DomainDig/WatchlistView.swift index 5670e5e..389ae91 100644 --- a/DomainDig/WatchlistView.swift +++ b/DomainDig/WatchlistView.swift @@ -169,13 +169,13 @@ struct WatchlistView: View { shareTrackedDomains(format: .pdf) } } else { - Button("CSV Export • Available in Pro") {} + Button("CSV Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("JSON Export • Available in Pro") {} + Button("JSON Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("Markdown Export • Available in Pro") {} + Button("Markdown Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("PDF Export • Available in Pro") {} + Button("PDF Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) } } label: { @@ -250,7 +250,7 @@ struct WatchlistView: View { Button("Save") { viewModel.saveCurrentWatchlistView(name: newSavedViewName) } - Button("Cancel", role: .cancel) {} + Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } } message: { Text("Saves the current tag, filter, and sort as a reusable preset.") } @@ -736,7 +736,7 @@ struct TrackedDomainDetailView: View { viewModel.rerunInspection(for: liveTrackedDomain, useSnapshotResolver: true) } } - Button("Cancel", role: .cancel) {} + Button("Cancel", role: .cancel) { /* Dismiss only; SwiftUI closes the alert. */ } } message: { Text(viewModel.resolverMismatchNote(for: liveTrackedDomain) ?? "Choose how to reproduce the most recent snapshot.") } diff --git a/DomainDig/WorkflowsView.swift b/DomainDig/WorkflowsView.swift index 3cfca42..ce889fb 100644 --- a/DomainDig/WorkflowsView.swift +++ b/DomainDig/WorkflowsView.swift @@ -253,13 +253,13 @@ struct WorkflowDetailView: View { shareWorkflowResults(format: .pdf) } } else { - Button("CSV Export • Available in Pro") {} + Button("CSV Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("JSON Export • Available in Pro") {} + Button("JSON Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("Markdown Export • Available in Pro") {} + Button("Markdown Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("PDF Export • Available in Pro") {} + Button("PDF Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) } } label: { @@ -554,13 +554,13 @@ struct WorkflowRunSummaryView: View { share(format: .pdf) } } else { - Button("CSV Export • Available in Pro") {} + Button("CSV Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("JSON Export • Available in Pro") {} + Button("JSON Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("Markdown Export • Available in Pro") {} + Button("Markdown Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) - Button("PDF Export • Available in Pro") {} + Button("PDF Export • Available in Pro") { /* Inert: disabled Pro upsell affordance. */ } .disabled(true) } } label: { diff --git a/DomainDigWidget/DomainDigPortfolioWidget.swift b/DomainDigWidget/DomainDigPortfolioWidget.swift index f26964c..bc88ac0 100644 --- a/DomainDigWidget/DomainDigPortfolioWidget.swift +++ b/DomainDigWidget/DomainDigPortfolioWidget.swift @@ -7,7 +7,7 @@ struct DomainDigEntry: TimelineEntry { } struct DomainDigProvider: TimelineProvider { - func placeholder(in context: Context) -> DomainDigEntry { + func placeholder(in _: Context) -> DomainDigEntry { DomainDigEntry(date: Date(), data: .placeholder) } @@ -16,7 +16,7 @@ struct DomainDigProvider: TimelineProvider { completion(DomainDigEntry(date: Date(), data: data)) } - func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { + func getTimeline(in _: Context, completion: @escaping (Timeline) -> Void) { let data = DomainDigWidgetStore.read() ?? .empty let entry = DomainDigEntry(date: Date(), data: data) // The app reloads timelines on foreground and on watchlist changes; this @@ -49,10 +49,9 @@ struct DomainDigWidgetView: View { if data.totalDomains == 0 { emptyState } else { - switch family { - case .systemSmall: + if family == .systemSmall { smallView - default: + } else { mediumOrLargeView } } diff --git a/DomainInspectionService.swift b/DomainInspectionService.swift index 3cdb21c..3ebfe09 100644 --- a/DomainInspectionService.swift +++ b/DomainInspectionService.swift @@ -295,10 +295,10 @@ struct DomainInspectionService { let ptrRecord = mapOptionalServiceResult(ptrOutcome.map { normalizeErrors(in: $0.value) }, missingMessage: "No A record available") let geolocation = mapOptionalServiceResult(geoOutcome.map { normalizeErrors(in: $0.value) }, missingMessage: "No A record available") let availabilityConfidence = confidenceForAvailability(result: availability.value, provenance: provenanceBySection[.availability]) - let ownershipConfidence = confidenceForOwnership(result: ownership.value, error: ownership.message) - let subdomainConfidence = confidenceForSubdomains(results: subdomains.value, error: subdomains.message) - let emailConfidence = confidenceForEmail(result: emailSecurity.value, error: emailSecurity.message) - let geolocationConfidence = confidenceForGeolocation(result: geolocation.value, error: geolocation.message) + let ownershipConfidence = confidenceForOwnership(result: ownership.value) + let subdomainConfidence = confidenceForSubdomains(results: subdomains.value) + let emailConfidence = confidenceForEmail(result: emailSecurity.value) + let geolocationConfidence = confidenceForGeolocation(result: geolocation.value) let validationIssues = validationIssues(for: normalizedDomain, snapshotTimestamp: startedAt, availability: availability.value, dnsSections: dnsSections.value, provenanceBySection: provenanceBySection) let snapshot = LookupSnapshot( @@ -580,21 +580,21 @@ struct DomainInspectionService { return .low } - private func confidenceForOwnership(result: DomainOwnership?, error: String?) -> ConfidenceLevel { - guard let result else { return error == nil ? .low : .low } + private func confidenceForOwnership(result: DomainOwnership?) -> ConfidenceLevel { + guard let result else { return .low } let hasDirectRegistrationData = result.registrar != nil || result.createdDate != nil || result.expirationDate != nil return hasDirectRegistrationData ? .high : .medium } - private func confidenceForSubdomains(results: [DiscoveredSubdomain], error: String?) -> ConfidenceLevel { + private func confidenceForSubdomains(results: [DiscoveredSubdomain]) -> ConfidenceLevel { if !results.isEmpty { return .medium } - return error == nil ? .low : .low + return .low } - private func confidenceForEmail(result: EmailSecurityResult?, error: String?) -> ConfidenceLevel { - guard let result else { return error == nil ? .low : .low } + private func confidenceForEmail(result: EmailSecurityResult?) -> ConfidenceLevel { + guard let result else { return .low } let foundCount = [result.spf.found, result.dmarc.found, result.dkim.found, result.bimi.found, result.mtaSts?.txtFound == true] .filter { $0 } .count @@ -607,8 +607,8 @@ struct DomainInspectionService { return .low } - private func confidenceForGeolocation(result: IPGeolocation?, error: String?) -> ConfidenceLevel { - guard let result else { return error == nil ? .low : .low } + private func confidenceForGeolocation(result: IPGeolocation?) -> ConfidenceLevel { + guard let result else { return .low } if result.city != nil && result.country_name != nil && result.latitude != nil && result.longitude != nil { return .high } diff --git a/DomainReportExporter.swift b/DomainReportExporter.swift index a8e2ee9..232cd88 100644 --- a/DomainReportExporter.swift +++ b/DomainReportExporter.swift @@ -11,8 +11,8 @@ enum DomainExportFormat: String, CaseIterable, Identifiable, Codable { case markdown = "md" case pdf = "pdf" - var id: String { rawValue } var fileExtension: String { rawValue } + var id: String { fileExtension } var title: String { switch self { diff --git a/RELEASE_ROADMAP.md b/RELEASE_ROADMAP.md index c09639f..30158e7 100644 --- a/RELEASE_ROADMAP.md +++ b/RELEASE_ROADMAP.md @@ -6,7 +6,7 @@ ports, geolocation, subdomains, availability). The next several releases invest in *reach and surfacing* — getting that data onto more iOS surfaces and into more workflows — rather than adding raw protocol checks. -Current version: `v4.8.2`. +Current version: `v4.8.3`. ## v4.4.1 Patch: Release Readiness — ✅ shipped @@ -154,6 +154,37 @@ Goal: close the UAT follow-ups and make failures legible instead of silent. synced against App Store Connect. Registered in the project without target membership so it is not bundled into shipping builds. +## v4.8.3 Patch: Static Analysis Cleanup — ✅ shipped + +Goal: clear the SonarCloud new-code backlog without changing behavior. + +- **Dead confidence conditionals fixed** (4 bugs) — + `DomainInspectionService`'s `confidenceFor*` helpers each returned + `error == nil ? .low : .low`. The conditional was inert, so the unused `error` + parameter was dropped alongside it. +- **Identical switch branches merged** — 14 sites in `DomainViewModel` handled + `.empty(message)` and `.error(message)` with byte-identical bodies; they now + share one `case let .empty(message), let .error(message):`. +- **Duplicate implementations consolidated** — `clearPresentedResults()` now + delegates to `reset()`, `String.nonEmpty` was folded into `nilIfEmpty`, and + `ExportFormat.id` derives from `fileExtension`. +- **Nested ternaries extracted** — grade-to-tone and impact-to-color mappings + became `TLSGrade.tone`, `EmailSecurityGrade.tone`, and + `ChangeImpactClassification.color`, replacing `ContentView`'s private + `impactColor` and the duplicate mapping in `BatchResultsView`. +- **Remaining smells** — empty closures and singleton inits documented, unused + protocol-conformance parameters marked `_`, `CloudSyncTrigger.import` renamed + to `imported` (raw value preserved), `_serverTrust`/`_tlsMetadata` renamed, + nested `if`s merged in the DER parser, and deep closure nesting flattened in + `PortScanService` and `IntegrationService`. + +Left open deliberately: `swift:S107` (initializer parameter counts on model +memberwise inits), `swift:S115` (constants mirroring DoH/ipapi JSON keys), +`swift:S1075` (false positives on `https://` literals), and two `swift:S117` +hits on SwiftUI `$binding` shorthand in `AuditModeView`, which cannot be +renamed. These want a *Won't Fix* / *Safe* resolution in SonarCloud rather than +a code change. + ## v5.0.0 Major: Contract Stabilization & Engineering Health Goal: earn long-term compatibility promises — and pay down the debt that the -- cgit v1.2.3