refactor: clean up force-unwraps and enable lint ratchet#29
Merged
Conversation
…forms Cleans up 3 force_unwrapping violations in: - Managers/CameraManager.swift:93 - Utilities/OpenAIClient.swift:26 - Utilities/SignInWithAppleHelper.swift:236 Each site uses the smallest fix that matches its semantics — guard let with early return, if let, ?? default, or guard let + fatalError when an invariant guarantees non-nil. No swiftlint:disable directives added. Part 1 of 4 in docs/plans/maintenance-cleanup.md Task 3 cleanup.
The --use-script-input-files flag reads from Xcode build-phase env vars (SCRIPT_INPUT_FILE_*), not stdin. The heredoc <<< was a no-op and every CLI git commit failed with "SCRIPT_INPUT_FILE_COUNT variable not set". Pass each staged file as a positional argument instead. Discovered while running the Task 3 force-unwrap cleanup.
Cleans up 2 force_unwrapping violations in: - Models/IncomeModel.swift:32 - Models/ExpenseModel.swift:32 Applied the fix inline in each file rather than introducing a shared helper, since deduplicating a single line across two files would have required adding a third file and a project.pbxproj edit. Part 2 of 4 in docs/plans/maintenance-cleanup.md Task 3 cleanup.
Cleans up 5 force_unwrapping violations in: - ViewModels/ProfileTab/ProfileViewModel.swift:125, 142 - ViewModels/Dashboard/ReportsViewModel.swift:77, 92 - Views/MainNavigationView.swift:659 Each site uses the smallest fix that matches its semantics — guard let with early return, if let, ?? default. No swiftlint:disable directives. Part 3 of 4 in docs/plans/maintenance-cleanup.md Task 3 cleanup.
The 10 force_unwrapping sites that previously triggered warnings have all been refactored in this PR to use safe forms (guard let, if let, ?? default, or fatalError for invariant-guaranteed values). Bumping severity to error means any new force-unwrap from this commit forward fails CI and the local pre-commit hook. Part 4 of 4 in docs/plans/maintenance-cleanup.md Task 3 cleanup.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes Task 3 of
docs/plans/maintenance-cleanup.md. Five commits:e640ab9— services layer (CameraManager, OpenAIClient, SignInWithAppleHelper)a53346c— fix broken pre-commit hook (discovered during Task 3 — see below)7935d15— data models (IncomeModel, ExpenseModel)b13e472— viewmodels + one view (ProfileViewModel, ReportsViewModel, MainNavigationView)aa1ae55— flip.swiftlint.yml:force_unwrappingseveritywarning→errorAfter this PR, any new
value!from this commit forward fails CI and the local pre-commit hook.Why
swiftlint lintwas surfacing 10force_unwrappingwarnings on every CI run. They were warnings (not errors) so CI passed, but every new force-unwrap added to the pile silently. The "ratchet" pattern says: clean the existing violations, then bump severity toerrorso any future violation is caught at the door.After this PR:
swiftlint lint --strictreports 0 violations across all 75 linted files.swiftlint lint --reporter github-actions-loggingstep (no--strict) keeps annotating, but the bumped severity means any new force-unwrap shows as a CI error and blocks merge.What each fix looks like
Services (commit 1)
CameraManager.swift:93—previewLayer!after just-initialized → use a locallet layer = …and assign to the stored property at the end. Removes both the!and the noisypreviewLayer?.chain.OpenAIClient.swift:26—URL(string: literal)!→guard let url = … else { return nil }. The function already returnsString?andnilis the established failure path.SignInWithAppleHelper.swift:236—self.view.window!inpresentationAnchor(for:)→self.view.window ?? ASPresentationAnchor(). The protocol can be invoked before a window is attached; a fallback anchor is the documented escape hatch.Data models (commit 3) and viewmodels (commit 4)
Six sites all had the same shape:
Calendar.current.date(byAdding: .day, value: 1, to: endDate)!. All replaced withguard let nextDay = … else { return [] }(functions return arrays ofIncomeModel/ExpenseModel, so empty array is the safe degenerate result that matches existing error paths). No shared helper extracted — duplication was 4 short lines per file, not worth a new file + pbxproj edit.View (commit 4)
MainNavigationView.swift:659— Force-unwrap ofgoalName: String?inside aText(...)ternary →goalName.map { "Add $\(Int(selectedPreset)) to \($0)" } ?? "Select a goal". No new hardcoded strings introduced (both literals were already there). Thenilbranch is unreachable in practice (button is.disabled(selectedGoal == nil)), but the safe form documents the contract anyway.Out-of-scope but folded in: pre-commit hook fix (commit 2)
The
.githooks/pre-commitscript calledswiftlint lint --use-script-input-files <<< "$file", but--use-script-input-filesreads from Xcode build-phase env vars (SCRIPT_INPUT_FILE_*), not stdin. Every CLIgit commitwas failing withSCRIPT_INPUT_FILE_COUNT variable not setsince PR #22 — local lint enforcement had never actually run. Fixed by passing the file as a positional argument:Verified by staging a Swift file with a known
force_unwrappingwarning — the hook caught it. Folded into this PR (rather than a separate one) because the entire point of Task 3 is "make lint enforcement actually work end-to-end" and the hook was the broken local half of that.Test plan
swiftlint lint --strict— 0 violations across 75 filesxcodebuild build -scheme Savely -destination 'platform=iOS Simulator,name=iPhone 17 Pro'— passesxcodebuild test -scheme Savely -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -skip-testing:SavelyUITests— passesforce_unwrappingon staged file)Risks
[]on calendar-failure now degrade silently instead of crashing. If you ever want to surface this as a UI error, those would be the spots to add an error state — but a calendar arithmetic failure on+1 dayis essentially impossible in practice, so silent degradation matches the prior implicit behavior more honestly than a crash did.MainNavigationView.swift:659change usesOptional.map(...) ?? defaultinstead of anif let/elseview-builder split. Functionally identical but slightly less SwiftUI-idiomatic — flag if you'd rather see the explicit form.guard let x = value else { fatalError("invariant violated: …") }— explicit failure mode, satisfies the linter.🤖 Generated with Claude Code