Skill: Clean Code
Automated dead code detection and removal using Periphery.
When to use this skill
- •Remove unused code from the project
- •Clean up dead code before releases
- •Maintain code hygiene periodically
Workflow
Execute the following steps in order:
Step 1: Run Periphery Scan
If a recent build exists, reuse its index store to skip the build step:
# Find the index store from the most recent build INDEX_STORE=$(find ~/Library/Developer/Xcode/DerivedData/Challenge-*/Index.noindex/DataStore -maxdepth 0 2>/dev/null | head -1) # Skip build if index store exists, otherwise run full scan if [ -n "$INDEX_STORE" ]; then mise x -- periphery scan --skip-build --index-store-path "$INDEX_STORE" else mise x -- periphery scan fi
Analyze the output to identify unused code. The scan excludes:
- •Domain Models (
**/Domain/Models/**) - •DTOs (
**/DTOs/**) - •Test targets
- •SwiftUI Previews
- •Codable properties
- •Public declarations (library code)
Step 2: Search for Related Tests and Mocks (BEFORE removing code)
CRITICAL: Before removing any code, search for tests and mocks that reference it.
For each unused item reported by Periphery:
# Search for references in Tests directories grep -r "functionName\|ClassName" Libraries/**/Tests/
Check these locations:
- •
Tests/Mocks/- Mock implementations of protocols - •
Tests/Data/- Repository and DataSource tests - •
Tests/Domain/- UseCase tests - •
Tests/Presentation/- ViewModel tests
If references are found:
- •Note which test files and mocks need updating
- •Plan to update/remove them along with the production code
Step 3: Remove Unused Code and Update Tests
For each warning reported by Periphery:
- •
Remove from production code:
- •Delete the unused declaration
- •If removing from a protocol, also remove from all conforming types
- •
Update related tests and mocks:
- •Remove the method from mock implementations
- •Delete test cases that test the removed functionality
- •Update any test that calls the removed code
- •
For ViewState Equatable removals:
- •Create Equatable extension in
Tests/Extensions/(see below)
- •Create Equatable extension in
Step 4: Run SwiftLint Auto-fix
After removing code, run SwiftLint to auto-correct formatting issues:
mise x -- swiftlint --fix --quiet
Step 5: Run Tests (Full Workspace)
Build and execute all tests in the workspace:
mise x -- tuist test
If tests fail:
- •Check if failed tests were testing removed code → delete them
- •Check if mocks are missing removed methods → update mocks
- •If unrelated failure → investigate and fix
- •Re-run tests until all pass
Step 6: Final Verification
Run Periphery again to confirm no unused code remains. The tuist test build from the previous step provides a fresh index store:
INDEX_STORE=$(find ~/Library/Developer/Xcode/DerivedData/Challenge-*/Index.noindex/DataStore -maxdepth 0 2>/dev/null | head -1) mise x -- periphery scan --skip-build --index-store-path "$INDEX_STORE"
Expected output: * No unused code detected.
Configuration
Periphery configuration is in .periphery.yml:
project: {AppName}.xcworkspace
schemes:
- "{AppName} (Dev)"
retain_public: true
retain_objc_annotated: true
retain_codable_properties: true
retain_swift_ui_previews: true
exclude_tests: true
report_exclude:
- "**/Domain/Models/**"
- "**/DTOs/**"
Configuration Options
| Option | Description |
|---|---|
retain_public | Keep public declarations (for libraries) |
retain_codable_properties | Keep Codable properties even if unread |
retain_swift_ui_previews | Keep SwiftUI Preview providers |
exclude_tests | Don't analyze test targets |
report_exclude | Glob patterns to exclude from reports |
Common Scenarios
Unused Protocol Method
When Periphery reports an unused protocol method:
- •Search first:
grep -r "methodName" Libraries/**/Tests/ - •Remove from protocol definition
- •Remove from all conforming types (including mocks!)
- •Delete test cases that test this method
- •Update any mock that implements this protocol
Unused MemoryDataSource Methods
Cache/storage methods might be unused if caching isn't implemented yet. Options:
- •Remove: If not planned for near future
- •Also remove from
{Name}MemoryDataSourceMock - •Delete tests in
{Name}MemoryDataSourceTests.swift
- •Also remove from
- •Keep: Add to
report_excludeif intentionally reserved
Unused ViewState Equatable
Custom == implementations on ViewState enums are reported as unused because:
- •SwiftUI doesn't require
Equatablefor view state - •Periphery excludes test targets from analysis
Important: Tests likely use these implementations for assertions like #expect(sut.state == .loaded(expected)).
When removing == from ViewState:
- •Remove the
==function from production code - •Create an Equatable extension in
Tests/Extensions/:
// Tests/Extensions/MyViewState+Equatable.swift
@testable import MyModule
extension MyViewState: @retroactive Equatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
switch (lhs, rhs) {
case (.idle, .idle), (.loading, .loading):
true
case let (.loaded(lhsValue), .loaded(rhsValue)):
lhsValue == rhsValue
case let (.error(lhsError), .error(rhsError)):
lhsError.localizedDescription == rhsError.localizedDescription
default:
false
}
}
}
Note: Use @retroactive to silence the "conformance of imported type" warning.
See /testing skill for more details on Equatable extensions.
Unused Model Properties
If a Domain Model property is unused:
- •Check if it's mapped from DTO (might be needed for API contract)
- •If truly unused, remove from model
- •Update all initializers and stubs
- •Remove from DTO mapping if applicable
Checklist
- • Periphery scan completed
- • Tests and mocks searched BEFORE removing code
- • All unused code removed from production
- • Related mocks updated (removed methods)
- • Related tests deleted or updated
- • Equatable extensions created in Tests/Extensions/ if needed
- • SwiftLint auto-fix executed
- •
tuist testpasses (build + tests) - • Final Periphery scan shows no unused code