976 lines
23 KiB
Plaintext
976 lines
23 KiB
Plaintext
---
|
|
source: ~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/code-style.mdc
|
|
confidence: 0.9
|
|
namespace: work
|
|
last_synced: 2026-04-28
|
|
alwaysApply: true
|
|
---
|
|
|
|
# Swift Code Style Guide
|
|
|
|
*This style guide is based on the [official iOS style guide](iOS/styleguide/STYLEGUIDE.md) and incorporates DuckDuckGo-specific patterns and requirements.*
|
|
|
|
## Correctness
|
|
|
|
**Strive to make your code compile without warnings.** This rule informs many style decisions such as using `#selector` types instead of string literals.
|
|
|
|
## SwiftLint
|
|
|
|
We use [SwiftLint](https://github.com/realm/SwiftLint) for enforcing Swift style and conventions. See the [SwiftLint configuration](.swiftlint.yml) for specific rules.
|
|
|
|
**Key SwiftLint settings**:
|
|
- Line length: 150 characters (not the default 100)
|
|
- Force cast/try: warnings (not errors for pragmatic development)
|
|
- Identifier naming: flexible for single-letter variables in closures
|
|
|
|
## Naming Conventions
|
|
|
|
Follow the [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/) with these key principles:
|
|
|
|
### Core Principles
|
|
- **Clarity at the call site** over brevity
|
|
- **Use camelCase** (not snake_case)
|
|
- **UpperCamelCase** for types and protocols
|
|
- **lowerCamelCase** for everything else
|
|
- **Include all needed words** while omitting needless words
|
|
- **Use names based on roles**, not types
|
|
|
|
### Type Names
|
|
```swift
|
|
// ✅ CORRECT: Descriptive, UpperCamelCase
|
|
class UserAuthenticationManager { }
|
|
struct BookmarkItem { }
|
|
enum NavigationState { }
|
|
protocol DataSourceProtocol { }
|
|
|
|
// ❌ INCORRECT: Too generic
|
|
class Manager { }
|
|
struct Data { }
|
|
```
|
|
|
|
### Variable and Function Names
|
|
```swift
|
|
// ✅ CORRECT: Descriptive lowerCamelCase
|
|
let maximumRetryCount = 3
|
|
var isLoading = false
|
|
func fetchUserData() { }
|
|
|
|
// Boolean properties should read like assertions
|
|
var isEnabled: Bool
|
|
var hasCompleted: Bool
|
|
var canDelete: Bool
|
|
|
|
// ❌ INCORRECT: Abbreviations and unclear names
|
|
let usrMgr = UserManager()
|
|
func calcTotal() { }
|
|
```
|
|
|
|
### Protocol Naming
|
|
```swift
|
|
// ✅ CORRECT: Capability protocols end in -able, -ible, -ing
|
|
protocol Loadable { }
|
|
protocol Refreshable { }
|
|
protocol UserAuthenticating { }
|
|
|
|
// ✅ CORRECT: Type protocols are nouns
|
|
protocol DataSource { }
|
|
protocol Delegate { }
|
|
```
|
|
|
|
### Method Naming Patterns
|
|
```swift
|
|
// ✅ CORRECT: Method naming patterns
|
|
// Factory methods begin with "make"
|
|
func makeLocationManager() -> CLLocationManager
|
|
|
|
// Verb methods follow -ed, -ing rule for non-mutating
|
|
func sorted() -> [Element] // non-mutating
|
|
func sort() // mutating
|
|
|
|
// Boolean methods read like assertions
|
|
func canDelete() -> Bool
|
|
func hasCompleted() -> Bool
|
|
```
|
|
|
|
### Delegate Methods
|
|
When creating custom delegate methods, the **unnamed first parameter should be the delegate source**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Delegate pattern
|
|
func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
|
|
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
|
|
|
|
// ❌ INCORRECT: Missing source parameter
|
|
func didSelectName(namePicker: NamePickerViewController, name: String)
|
|
func namePickerShouldReload() -> Bool
|
|
```
|
|
|
|
### Use Type Inferred Context
|
|
Use compiler inferred context to write shorter, clear code:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Type inferred context
|
|
let selector = #selector(viewDidLoad)
|
|
view.backgroundColor = .red
|
|
let toView = context.view(forKey: .to)
|
|
let view = UIView(frame: .zero)
|
|
|
|
// ❌ INCORRECT: Redundant type information
|
|
let selector = #selector(ViewController.viewDidLoad)
|
|
view.backgroundColor = UIColor.red
|
|
let toView = context.view(forKey: UITransitionContextViewKey.to)
|
|
let view = UIView(frame: CGRect.zero)
|
|
```
|
|
|
|
### Generics
|
|
Generic type parameters should be **descriptive, UpperCamelCase names**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Descriptive generic names
|
|
struct Stack<Element> { ... }
|
|
func write<Target: OutputStream>(to target: inout Target)
|
|
func swap<T>(_ a: inout T, _ b: inout T) // T is acceptable when no meaningful relationship
|
|
|
|
// ❌ INCORRECT: Non-descriptive or wrong case
|
|
struct Stack<T> { ... }
|
|
func write<target: OutputStream>(to target: inout target)
|
|
```
|
|
|
|
### Language
|
|
Use **US English spelling** to match Apple's API:
|
|
|
|
```swift
|
|
// ✅ CORRECT: US English
|
|
let color = "red"
|
|
|
|
// ❌ INCORRECT: British English
|
|
let colour = "red"
|
|
```
|
|
|
|
## Code Organization
|
|
|
|
### File Structure
|
|
```swift
|
|
// 1. Import statements (minimal - only what's needed)
|
|
import UIKit
|
|
import Combine
|
|
|
|
// 2. Protocol definitions
|
|
protocol FeatureDelegate: AnyObject {
|
|
func featureDidUpdate()
|
|
}
|
|
|
|
// 3. Main type declaration
|
|
class FeatureViewController: UIViewController {
|
|
// Properties first
|
|
private let viewModel: FeatureViewModel
|
|
|
|
// Lifecycle methods
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
setupUI()
|
|
}
|
|
|
|
// Private methods
|
|
private func setupUI() { }
|
|
}
|
|
|
|
// 4. Extensions for protocol conformance
|
|
// MARK: - UITableViewDataSource
|
|
extension FeatureViewController: UITableViewDataSource {
|
|
// Protocol methods
|
|
}
|
|
```
|
|
|
|
### Protocol Conformance
|
|
**Prefer separate extensions** for protocol conformance to keep related methods grouped:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Separate extensions
|
|
class MyViewController: UIViewController {
|
|
// class implementation
|
|
}
|
|
|
|
// MARK: - UITableViewDataSource
|
|
extension MyViewController: UITableViewDataSource {
|
|
// table view data source methods
|
|
}
|
|
|
|
// MARK: - UIScrollViewDelegate
|
|
extension MyViewController: UIScrollViewDelegate {
|
|
// scroll view delegate methods
|
|
}
|
|
|
|
// ❌ INCORRECT: All in main class declaration
|
|
class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
|
|
// all methods mixed together
|
|
}
|
|
```
|
|
|
|
### Minimal Imports
|
|
**Import only the modules a source file requires**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Minimal imports
|
|
import UIKit
|
|
var view: UIView
|
|
var deviceModels: [String]
|
|
|
|
// ✅ CORRECT: Foundation when UIKit not needed
|
|
import Foundation
|
|
var deviceModels: [String]
|
|
|
|
// ❌ INCORRECT: Unnecessary imports
|
|
import UIKit
|
|
import Foundation // UIKit already includes Foundation
|
|
var view: UIView
|
|
var deviceModels: [String]
|
|
```
|
|
|
|
### Remove Unused Code
|
|
**Remove unused (dead) code**, including Xcode template code:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Keep only implemented methods
|
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
return Database.contacts.count
|
|
}
|
|
|
|
// ❌ INCORRECT: Template code and unused methods
|
|
override func didReceiveMemoryWarning() {
|
|
super.didReceiveMemoryWarning()
|
|
// Dispose of any resources that can be recreated.
|
|
}
|
|
|
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
|
// #warning Incomplete implementation, return the number of sections
|
|
return 1
|
|
}
|
|
```
|
|
|
|
## Formatting and Style
|
|
|
|
### Line Breaks and Length
|
|
- **Line margin: 150 characters** (not the default 100)
|
|
- **Long lines should be wrapped** at around 150 characters
|
|
- **Avoid trailing whitespace** at ends of lines
|
|
- **Add single newline** at end of each file
|
|
|
|
### Spacing
|
|
- **Indent using 4 spaces** rather than tabs
|
|
- **Method braces open on same line**, close on new line
|
|
- **One blank line between methods**
|
|
- **No blank lines after opening brace or before closing brace**
|
|
|
|
```swift
|
|
// ✅ CORRECT: Spacing and braces
|
|
if user.isHappy {
|
|
// Do something
|
|
} else {
|
|
// Do something else
|
|
}
|
|
|
|
// ❌ INCORRECT: Wrong brace placement
|
|
if user.isHappy
|
|
{
|
|
// Do something
|
|
}
|
|
else {
|
|
// Do something else
|
|
}
|
|
```
|
|
|
|
### Colons
|
|
**Colons have no space on left, one space on right**. Exceptions: ternary operator `? :`, empty dictionary `[:]`, `#selector` syntax:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Colon spacing
|
|
class TestDatabase: Database {
|
|
var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
|
|
}
|
|
|
|
// ❌ INCORRECT: Wrong colon spacing
|
|
class TestDatabase : Database {
|
|
var data :[String:CGFloat] = ["A" : 1.2, "B":3.2]
|
|
}
|
|
```
|
|
|
|
### Function Parameters
|
|
**Closing parentheses should not appear on line by themselves**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Closing parenthesis placement
|
|
let user = try await getUser(
|
|
for: userID,
|
|
on: connection)
|
|
|
|
// ❌ INCORRECT: Closing parenthesis on own line
|
|
let user = try await getUser(
|
|
for: userID,
|
|
on: connection
|
|
)
|
|
```
|
|
|
|
## Function Declarations
|
|
|
|
### Short Functions
|
|
**Keep short function declarations on one line**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Short function on one line
|
|
func reticulateSplines(spline: [Double]) -> Bool {
|
|
// implementation
|
|
}
|
|
```
|
|
|
|
### Long Function Signatures
|
|
**For functions with long signatures, put each parameter on new line**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Long signature formatting
|
|
func reticulateSplines(spline: [Double],
|
|
adjustmentFactor: Double,
|
|
translateConstant: Int,
|
|
comment: String) -> Bool {
|
|
// implementation
|
|
}
|
|
```
|
|
|
|
### Return Types
|
|
**Use `Void` for closure/function outputs, `()` for inputs**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Return type formatting
|
|
func updateConstraints() -> Void {
|
|
// implementation
|
|
}
|
|
|
|
typealias CompletionHandler = (result) -> Void
|
|
|
|
// ❌ INCORRECT: Wrong return type syntax
|
|
func updateConstraints() -> () {
|
|
// implementation
|
|
}
|
|
|
|
typealias CompletionHandler = (result) -> ()
|
|
```
|
|
|
|
## Function Calls
|
|
|
|
**Mirror function declaration style at call sites**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Single line when it fits
|
|
let success = reticulateSplines(splines)
|
|
|
|
// ✅ CORRECT: Multi-line when wrapped
|
|
let success = reticulateSplines(
|
|
spline: splines,
|
|
adjustmentFactor: 1.3,
|
|
translateConstant: 2,
|
|
comment: "normalize the display")
|
|
```
|
|
|
|
## Closure Expressions
|
|
|
|
### Trailing Closure Syntax
|
|
**Use trailing closure syntax only for single closure at end**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Trailing closure usage
|
|
UIView.animate(withDuration: 1.0) {
|
|
self.myView.alpha = 0
|
|
}
|
|
|
|
UIView.animate(withDuration: 1.0, animations: {
|
|
self.myView.alpha = 0
|
|
}, completion: { finished in
|
|
self.myView.removeFromSuperview()
|
|
})
|
|
|
|
// ❌ INCORRECT: Trailing closure with multiple closures
|
|
UIView.animate(withDuration: 1.0, animations: {
|
|
self.myView.alpha = 0
|
|
}) { f in
|
|
self.myView.removeFromSuperview()
|
|
}
|
|
```
|
|
|
|
### Single-Expression Closures
|
|
**Use implicit returns for single-expression closures**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Implicit return
|
|
attendeeList.sort { a, b in
|
|
a > b
|
|
}
|
|
```
|
|
|
|
### Chained Methods
|
|
**Format chained methods for clarity**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Chained methods - compact when clear
|
|
let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.index(of: 90)
|
|
|
|
// ✅ CORRECT: Chained methods - multi-line when complex
|
|
let value = numbers
|
|
.map { $0 * 2 }
|
|
.filter { $0 > 50 }
|
|
.map { $0 + 10 }
|
|
```
|
|
|
|
## Types and Constants
|
|
|
|
### Native Types
|
|
**Always use Swift's native types** when available:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Native Swift types
|
|
let width = 120.0 // Double
|
|
let widthString = "\(width)" // String
|
|
|
|
// ❌ INCORRECT: Objective-C types
|
|
let width: NSNumber = 120.0 // NSNumber
|
|
let widthString: NSString = width.stringValue // NSString
|
|
```
|
|
|
|
### Constants vs Variables
|
|
**Use `let` by default, change to `var` only when compiler complains**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Type properties for constants
|
|
enum Math {
|
|
static let e = 2.718281828459045235360287
|
|
static let root2 = 1.41421356237309504880168872
|
|
}
|
|
|
|
let hypotenuse = side * Math.root2
|
|
|
|
// ❌ INCORRECT: Global constants
|
|
let e = 2.718281828459045235360287 // pollutes global namespace
|
|
let root2 = 1.41421356237309504880168872
|
|
```
|
|
|
|
### Type Inference
|
|
**Prefer compact code and let compiler infer types**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Type inference
|
|
let message = "Click the button"
|
|
let currentBounds = computeViewBounds()
|
|
var names = ["Mic", "Sam", "Christine"]
|
|
let maximumWidth: CGFloat = 106.5 // Specify when needed
|
|
|
|
// ❌ INCORRECT: Unnecessary type annotations
|
|
let message: String = "Click the button"
|
|
let currentBounds: CGRect = computeViewBounds()
|
|
```
|
|
|
|
### Empty Collections
|
|
**Use type annotation for empty arrays and dictionaries**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Type annotation for empty collections
|
|
var names: [String] = []
|
|
var lookup: [String: Int] = [:]
|
|
|
|
// ❌ INCORRECT: Constructor syntax
|
|
var names = [String]()
|
|
var lookup = [String: Int]()
|
|
```
|
|
|
|
### Syntactic Sugar
|
|
**Prefer shortcut type declarations**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Syntactic sugar
|
|
var deviceModels: [String]
|
|
var employees: [Int: String]
|
|
var faxNumber: Int?
|
|
|
|
// ❌ INCORRECT: Full generics syntax
|
|
var deviceModels: Array<String>
|
|
var employees: Dictionary<Int, String>
|
|
var faxNumber: Optional<Int>
|
|
```
|
|
|
|
## Optionals
|
|
|
|
### Optional Declarations
|
|
**Use `?` for optional types, `!` only when you know initialization timing**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Optional usage
|
|
var subview: UIView?
|
|
var volume: Double?
|
|
|
|
// Use ! only for outlets that initialize in viewDidLoad
|
|
@IBOutlet weak var tableView: UITableView!
|
|
```
|
|
|
|
### Optional Binding
|
|
**Shadow original names in optional binding**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Shadow original name
|
|
if let subview = subview, let volume = volume {
|
|
// do something with unwrapped subview and volume
|
|
}
|
|
|
|
// ❌ INCORRECT: Different names for unwrapped values
|
|
if let unwrappedSubview = optionalSubview {
|
|
if let realVolume = volume {
|
|
// do something with unwrappedSubview and realVolume
|
|
}
|
|
}
|
|
```
|
|
|
|
### Optional Chaining vs Binding
|
|
**Use optional chaining for single access, binding for multiple operations**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Optional chaining for single access
|
|
textContainer?.textLabel?.setNeedsDisplay()
|
|
|
|
// ✅ CORRECT: Optional binding for multiple operations
|
|
if let textContainer = textContainer {
|
|
// do many things with textContainer
|
|
}
|
|
```
|
|
|
|
## Memory Management
|
|
|
|
### Reference Cycles
|
|
**Prevent reference cycles with `weak` and `unowned` references.**
|
|
|
|
**Example:** See [memory-management.swift](code-style/memory-management.swift)
|
|
|
|
### Lazy Initialization
|
|
**Use lazy initialization for fine-grained control**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Lazy initialization
|
|
lazy var locationManager = makeLocationManager()
|
|
|
|
private func makeLocationManager() -> CLLocationManager {
|
|
let manager = CLLocationManager()
|
|
manager.desiredAccuracy = kCLLocationAccuracyBest
|
|
manager.delegate = self
|
|
manager.requestAlwaysAuthorization()
|
|
return manager
|
|
}
|
|
```
|
|
|
|
## Access Control
|
|
|
|
### Access Control Order
|
|
**Access control comes first, except for `static` and attributes**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Access control ordering
|
|
private let message = "Great Scott!"
|
|
|
|
class TimeMachine {
|
|
private dynamic lazy var fluxCapacitor = FluxCapacitor()
|
|
@IBAction private func activate() { }
|
|
static private let timeConstant = 88.0
|
|
}
|
|
|
|
// ❌ INCORRECT: Wrong ordering
|
|
fileprivate let message = "Great Scott!"
|
|
|
|
class TimeMachine {
|
|
lazy dynamic private var fluxCapacitor = FluxCapacitor()
|
|
}
|
|
```
|
|
|
|
### Private vs Fileprivate
|
|
**Prefer `private` to `fileprivate`**; use `fileprivate` only when compiler requires it.
|
|
|
|
## Control Flow
|
|
|
|
### Loop Style
|
|
**Prefer `for-in` style over `while-condition-increment`**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: for-in style
|
|
for _ in 0..<3 {
|
|
print("Hello three times")
|
|
}
|
|
|
|
for (index, person) in attendeeList.enumerated() {
|
|
print("\(person) is at position #\(index)")
|
|
}
|
|
|
|
// ❌ INCORRECT: while style
|
|
var i = 0
|
|
while i < 3 {
|
|
print("Hello three times")
|
|
i += 1
|
|
}
|
|
```
|
|
|
|
### Ternary Operator
|
|
**Use ternary operator only when it increases clarity**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Simple ternary usage
|
|
let value = 5
|
|
result = value != 0 ? x : y
|
|
|
|
let isHorizontal = true
|
|
result = isHorizontal ? x : y
|
|
|
|
// ❌ INCORRECT: Complex nested ternary
|
|
result = a > b ? x = c > d ? c : d : y
|
|
```
|
|
|
|
### Golden Path
|
|
**Use the "golden path" pattern - don't nest `if` statements**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Golden path with guard
|
|
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
|
|
guard let context = context else {
|
|
throw FFTError.noContext
|
|
}
|
|
guard let inputData = inputData else {
|
|
throw FFTError.noInputData
|
|
}
|
|
|
|
// use context and input to compute the frequencies
|
|
return frequencies
|
|
}
|
|
|
|
// ❌ INCORRECT: Nested if statements
|
|
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
|
|
if let context = context {
|
|
if let inputData = inputData {
|
|
// use context and input to compute the frequencies
|
|
return frequencies
|
|
} else {
|
|
throw FFTError.noInputData
|
|
}
|
|
} else {
|
|
throw FFTError.noContext
|
|
}
|
|
}
|
|
```
|
|
|
|
### Compound Guard Statements
|
|
**Use compound guard for multiple optionals**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Compound guard
|
|
guard
|
|
let number1 = number1,
|
|
let number2 = number2,
|
|
let number3 = number3
|
|
else {
|
|
fatalError("impossible")
|
|
}
|
|
|
|
// ❌ INCORRECT: Nested optional binding
|
|
if let number1 = number1 {
|
|
if let number2 = number2 {
|
|
if let number3 = number3 {
|
|
// do something with numbers
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Class and Struct Definitions
|
|
|
|
### Example Well-Styled Class
|
|
```swift
|
|
final class Circle: Shape {
|
|
var x: Int, y: Int
|
|
var radius: Double
|
|
var diameter: Double {
|
|
get {
|
|
radius * 2
|
|
}
|
|
set {
|
|
radius = newValue / 2
|
|
}
|
|
}
|
|
|
|
init(x: Int, y: Int, radius: Double) {
|
|
self.x = x
|
|
self.y = y
|
|
self.radius = radius
|
|
}
|
|
|
|
convenience init(x: Int, y: Int, diameter: Double) {
|
|
self.init(x: x, y: y, radius: diameter / 2)
|
|
}
|
|
|
|
override func area() -> Double {
|
|
Double.pi * radius * radius
|
|
}
|
|
}
|
|
|
|
extension Circle: CustomStringConvertible {
|
|
var description: String {
|
|
"center = \(centerString) area = \(area())"
|
|
}
|
|
|
|
private var centerString: String {
|
|
"(\(x),\(y))"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Use of Self
|
|
**Avoid using `self` unless required by compiler**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Self only when required
|
|
class PhotoViewController: UIViewController {
|
|
var image: UIImage
|
|
|
|
init(image: UIImage) {
|
|
self.image = image // Required to disambiguate
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
func setupImageView() {
|
|
imageView.image = image // self not needed
|
|
}
|
|
}
|
|
```
|
|
|
|
### Computed Properties
|
|
**Omit get clause for read-only computed properties**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Implicit get for read-only
|
|
var diameter: Double {
|
|
radius * 2
|
|
}
|
|
|
|
// ❌ INCORRECT: Unnecessary get clause
|
|
var diameter: Double {
|
|
get {
|
|
return radius * 2
|
|
}
|
|
}
|
|
```
|
|
|
|
### Final
|
|
**Use `final` when inheritance is not intended**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Final for utility classes
|
|
final class Box<T> {
|
|
let value: T
|
|
init(_ value: T) {
|
|
self.value = value
|
|
}
|
|
}
|
|
```
|
|
|
|
## DuckDuckGo-Specific Patterns
|
|
|
|
### Design System Integration (MANDATORY)
|
|
|
|
**ALWAYS use DesignResourcesKit** for colors, typography, and icons.
|
|
|
|
**Example:** See [design-system-integration.swift](code-style/design-system-integration.swift)
|
|
|
|
### Dependency Injection Pattern
|
|
**Use AppDependencyProvider for dependency injection.**
|
|
|
|
**Example:** See [dependency-injection.swift](code-style/dependency-injection.swift)
|
|
|
|
### Async/Await Patterns
|
|
**Example:** See [async-await-pattern.swift](code-style/async-await-pattern.swift)
|
|
|
|
### Property Wrappers
|
|
**Example:** See [property-wrappers.swift](code-style/property-wrappers.swift)
|
|
|
|
## Comments and Documentation
|
|
|
|
### When to Comment
|
|
**Use comments to explain WHY, not WHAT**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Explains why
|
|
// We delay the animation to avoid conflicting with the previous transition
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
self.animateTransition()
|
|
}
|
|
|
|
// ❌ INCORRECT: Explains what (obvious from code)
|
|
// Set the background color to red
|
|
view.backgroundColor = .red
|
|
```
|
|
|
|
### Comment Style
|
|
**Prefer double/triple-slash over C-style comments**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Swift-style comments
|
|
// This is a comment
|
|
/// This is a documentation comment
|
|
|
|
// ❌ INCORRECT: C-style comments
|
|
/* This is a comment */
|
|
```
|
|
|
|
## String Literals
|
|
|
|
### Multi-line Strings
|
|
**Use multi-line string syntax for long strings**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Multi-line string formatting
|
|
let message = """
|
|
You cannot charge the flux \
|
|
capacitor with a 9V battery.
|
|
You must use a super-charger \
|
|
which costs 10 credits. You currently \
|
|
have \(credits) credits available.
|
|
"""
|
|
|
|
// ❌ INCORRECT: Concatenation or inline text
|
|
let message = """You cannot charge the flux \
|
|
capacitor with a 9V battery.
|
|
You must use a super-charger \
|
|
which costs 10 credits. You currently \
|
|
have \(credits) credits available.
|
|
"""
|
|
```
|
|
|
|
## Prohibited Patterns
|
|
|
|
### No Emoji
|
|
**Do not use emoji in code** - it creates unnecessary friction:
|
|
|
|
```swift
|
|
// ❌ FORBIDDEN: Emoji in code
|
|
let isHappy = true 😀
|
|
func celebrate() 🎉 { }
|
|
|
|
// ✅ CORRECT: Clear, text-based names
|
|
let isHappy = true
|
|
func celebrate() { }
|
|
```
|
|
|
|
### No Color/Image Literals
|
|
**Do not use `#colorLiteral` or `#imageLiteral`** - they're hard to read and maintain:
|
|
|
|
```swift
|
|
// ❌ FORBIDDEN: Literals
|
|
let color = #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1)
|
|
let image = #imageLiteral(resourceName: "icon")
|
|
|
|
// ✅ CORRECT: Explicit constructors (but prefer DesignResourcesKit)
|
|
let color = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
|
|
let image = UIImage(named: "icon")
|
|
|
|
// ✅ BEST: DesignResourcesKit
|
|
let color = UIColor(designSystemColor: .accent)
|
|
let image = DesignSystemImages.Color.Size24.bookmark
|
|
```
|
|
|
|
### No Parentheses Around Conditionals
|
|
**Don't use unnecessary parentheses**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: No parentheses needed
|
|
if name == "Hello" {
|
|
print("World")
|
|
}
|
|
|
|
// ❌ INCORRECT: Unnecessary parentheses
|
|
if (name == "Hello") {
|
|
print("World")
|
|
}
|
|
```
|
|
|
|
### No Semicolons
|
|
**Swift doesn't require semicolons** - don't use them:
|
|
|
|
```swift
|
|
// ✅ CORRECT: No semicolons
|
|
let swift = "not a scripting language"
|
|
|
|
// ❌ INCORRECT: Unnecessary semicolons
|
|
let swift = "not a scripting language";
|
|
```
|
|
|
|
## Error Handling and Assertions
|
|
|
|
### Fatal Errors
|
|
**Use `fatalError()` when app reaches unrecoverable state**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Fatal error for impossible states
|
|
guard let viewController = storyboard.instantiateViewController(withIdentifier: "Main") as? MainViewController else {
|
|
fatalError("Failed to instantiate MainViewController from storyboard")
|
|
}
|
|
```
|
|
|
|
### Assertions
|
|
**Use `assert()` and `assertionFailure()` for recoverable but unexpected states**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Assert for development debugging
|
|
func processItems(_ items: [Item]) {
|
|
assert(!items.isEmpty, "Items array should not be empty")
|
|
|
|
// Handle empty array gracefully in release builds
|
|
guard !items.isEmpty else { return }
|
|
|
|
// Process items...
|
|
}
|
|
```
|
|
|
|
## Logging
|
|
|
|
**Use unified logging system** for all logging.
|
|
|
|
**Example:** See [logging-pattern.swift](code-style/logging-pattern.swift)
|
|
|
|
**See [Logging Guidelines](logging-guidelines.md) for comprehensive logging patterns.**
|
|
|
|
## Unit Test Naming
|
|
|
|
**Use "when/then" convention for test names**:
|
|
|
|
```swift
|
|
// ✅ CORRECT: When/then test naming
|
|
func testWhenUrlIsNotATrackerThenMatchesIsFalse() { }
|
|
func testWhenUserTapsBookmarkButtonThenBookmarkIsAdded() { }
|
|
func testWhenNetworkFailsThenErrorIsDisplayed() { }
|
|
|
|
// ❌ INCORRECT: Unclear test names
|
|
func testBookmarks() { }
|
|
func testNetworking() { }
|
|
```
|
|
|
|
## Functions vs Methods
|
|
|
|
**Prefer methods over free functions** for discoverability:
|
|
|
|
```swift
|
|
// ✅ CORRECT: Methods are easily discoverable
|
|
let sorted = items.mergeSorted()
|
|
rocket.launch()
|
|
|
|
// ❌ INCORRECT: Free functions are hard to discover
|
|
let sorted = mergeSort(items)
|
|
launch(&rocket)
|
|
|
|
// ✅ ACCEPTABLE: Free functions that feel natural
|
|
let tuples = zip(a, b)
|
|
let value = max(x, y, z)
|
|
```
|
|
|
|
---
|
|
|
|
**Remember**: This style guide ensures consistency across the DuckDuckGo browser codebase. When in doubt, prioritize clarity and follow the patterns established in existing code.
|