feat: add Swift language-specific rules

Add 5 rule files for Swift following the established pattern used by
TypeScript, Python, and Go rule sets. Covers Swift 6 strict concurrency,
Swift Testing framework, protocol-oriented patterns, Keychain-based
secret management, and SwiftFormat/SwiftLint hooks.
This commit is contained in:
Maksim Dimitrov
2026-02-17 15:43:14 +02:00
parent 0b11849f1e
commit 6792e91735
6 changed files with 215 additions and 1 deletions

View File

@@ -17,7 +17,8 @@ rules/
│ └── security.md
├── typescript/ # TypeScript/JavaScript specific
├── python/ # Python specific
── golang/ # Go specific
── golang/ # Go specific
└── swift/ # Swift specific
```
- **common/** contains universal principles — no language-specific code examples.
@@ -32,6 +33,7 @@ rules/
./install.sh typescript
./install.sh python
./install.sh golang
./install.sh swift
# Install multiple languages at once
./install.sh typescript python
@@ -53,6 +55,7 @@ cp -r rules/common ~/.claude/rules/common
cp -r rules/typescript ~/.claude/rules/typescript
cp -r rules/python ~/.claude/rules/python
cp -r rules/golang ~/.claude/rules/golang
cp -r rules/swift ~/.claude/rules/swift
# Attention ! ! ! Configure according to your actual project requirements; the configuration here is for reference only.
```

View File

@@ -0,0 +1,47 @@
---
paths:
- "**/*.swift"
- "**/Package.swift"
---
# Swift Coding Style
> This file extends [common/coding-style.md](../common/coding-style.md) with Swift specific content.
## Formatting
- **SwiftFormat** for auto-formatting, **SwiftLint** for style enforcement
- `swift-format` is bundled with Xcode 16+ as an alternative
## Immutability
- Prefer `let` over `var` — define everything as `let` and only change to `var` if the compiler requires it
- Use `struct` with value semantics by default; use `class` only when identity or reference semantics are needed
## Naming
Follow [Apple API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/):
- Clarity at the point of use — omit needless words
- Name methods and properties for their roles, not their types
- Use `static let` for constants over global constants
## Error Handling
Use typed throws (Swift 6+) and pattern matching:
```swift
func load(id: String) throws(LoadError) -> Item {
guard let data = try? read(from: path) else {
throw .fileNotFound(id)
}
return try decode(data)
}
```
## Concurrency
Enable Swift 6 strict concurrency checking. Prefer:
- `Sendable` value types for data crossing isolation boundaries
- Actors for shared mutable state
- Structured concurrency (`async let`, `TaskGroup`) over unstructured `Task {}`

20
rules/swift/hooks.md Normal file
View File

@@ -0,0 +1,20 @@
---
paths:
- "**/*.swift"
- "**/Package.swift"
---
# Swift Hooks
> This file extends [common/hooks.md](../common/hooks.md) with Swift specific content.
## PostToolUse Hooks
Configure in `~/.claude/settings.json`:
- **SwiftFormat**: Auto-format `.swift` files after edit
- **SwiftLint**: Run lint checks after editing `.swift` files
- **swift build**: Type-check modified packages after edit
## Warning
Flag `print()` statements — use `os.Logger` or structured logging instead for production code.

66
rules/swift/patterns.md Normal file
View File

@@ -0,0 +1,66 @@
---
paths:
- "**/*.swift"
- "**/Package.swift"
---
# Swift Patterns
> This file extends [common/patterns.md](../common/patterns.md) with Swift specific content.
## Protocol-Oriented Design
Define small, focused protocols. Use protocol extensions for shared defaults:
```swift
protocol Repository: Sendable {
associatedtype Item: Identifiable & Sendable
func find(by id: Item.ID) async throws -> Item?
func save(_ item: Item) async throws
}
```
## Value Types
- Use structs for data transfer objects and models
- Use enums with associated values to model distinct states:
```swift
enum LoadState<T: Sendable>: Sendable {
case idle
case loading
case loaded(T)
case failed(Error)
}
```
## Actor Pattern
Use actors for shared mutable state instead of locks or dispatch queues:
```swift
actor Cache<Key: Hashable & Sendable, Value: Sendable> {
private var storage: [Key: Value] = [:]
func get(_ key: Key) -> Value? { storage[key] }
func set(_ key: Key, value: Value) { storage[key] = value }
}
```
## Dependency Injection
Inject protocols with default parameters — production uses defaults, tests inject mocks:
```swift
struct UserService {
private let repository: any UserRepository
init(repository: any UserRepository = DefaultUserRepository()) {
self.repository = repository
}
}
```
## References
See skill: `swift-actor-persistence` for actor-based persistence patterns.
See skill: `swift-protocol-di-testing` for protocol-based DI and testing.

33
rules/swift/security.md Normal file
View File

@@ -0,0 +1,33 @@
---
paths:
- "**/*.swift"
- "**/Package.swift"
---
# Swift Security
> This file extends [common/security.md](../common/security.md) with Swift specific content.
## Secret Management
- Use **Keychain Services** for sensitive data (tokens, passwords, keys) — never `UserDefaults`
- Use environment variables or `.xcconfig` files for build-time secrets
- Never hardcode secrets in source — decompilation tools extract them trivially
```swift
let apiKey = ProcessInfo.processInfo.environment["API_KEY"]
guard let apiKey, !apiKey.isEmpty else {
fatalError("API_KEY not configured")
}
```
## Transport Security
- App Transport Security (ATS) is enforced by default — do not disable it
- Use certificate pinning for critical endpoints
- Validate all server certificates
## Input Validation
- Sanitize all user input before display to prevent injection
- Use `URL(string:)` with validation rather than force-unwrapping
- Validate data from external sources (APIs, deep links, pasteboard) before processing

45
rules/swift/testing.md Normal file
View File

@@ -0,0 +1,45 @@
---
paths:
- "**/*.swift"
- "**/Package.swift"
---
# Swift Testing
> This file extends [common/testing.md](../common/testing.md) with Swift specific content.
## Framework
Use **Swift Testing** (`import Testing`) for new tests. Use `@Test` and `#expect`:
```swift
@Test("User creation validates email")
func userCreationValidatesEmail() throws {
#expect(throws: ValidationError.invalidEmail) {
try User(email: "not-an-email")
}
}
```
## Test Isolation
Each test gets a fresh instance — set up in `init`, tear down in `deinit`. No shared mutable state between tests.
## Parameterized Tests
```swift
@Test("Validates formats", arguments: ["json", "xml", "csv"])
func validatesFormat(format: String) throws {
let parser = try Parser(format: format)
#expect(parser.isValid)
}
```
## Coverage
```bash
swift test --enable-code-coverage
```
## Reference
See skill: `swift-protocol-di-testing` for protocol-based dependency injection and mock patterns with Swift Testing.