SwiftLintで追加・変更・廃止されたルールまとめ(Swift 4.2→5.1.2版)

SwiftLintの記事はシリーズになっています。
記事を順番に読み進めると、SwiftLintを使いこなせるようになります。

- [Swiftの静的解析ツール「SwiftLint」のセットアップ方法](https://qiita.com/uhooi/items/bf888b53b4b8210108aa)
- [SwiftLintの全ルール一覧(Swift 4.2版)](https://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed)
- SwiftLintで追加・変更・廃止されたルールまとめ(Swift 4.2→5.1.2版)　__←イマココ__
- [SwiftLintで追加・廃止されたルールまとめ(Swift 5.1.2→5.3.1版)](https://qiita.com/uhooi/items/10b9e49d3bd6f1344cad)
- [SwiftLintのオレオレカスタムルール一覧](https://qiita.com/uhooi/items/9deb56416aaa81709f69)
- [SwiftLintのAnalyzeを使って高度な解析をする方法](https://qiita.com/uhooi/items/655655f56be8bd94b06d)
- [SwiftLintの設定ファイルをサーバーに配置して共有する方法](https://qiita.com/uhooi/items/b5b26caeeefd8dbe1afd)
- [SwiftLintの「Currently running SwiftLint x.y.z but configuration specified version a.b.c.」エラーの解決法](https://qiita.com/uhooi/items/b1f2f0df2822f04c2161)

## はじめに

[SwiftLintの全ルール一覧(Swift 4.2版) - Qiita](https://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed) から2019/12/08現在の最新版までの間に、追加・変更・廃止されたルールを解説します。

前回の記事を読んでいなくても読み進められますが、併せて読むことをオススメします。

## 比較方法

以下の2ファイルを比較し、差分を翻訳しました。
https://github.com/realm/SwiftLint/blob/0.30.1/Rules.md
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md

|ルール|数|
|:--|:--|
|追加|21|
|変更|5|
|廃止|1|

※ソースでなくドキュメントを比較して調査したため、実際の数と異なる可能性があります。

## 環境

- Swift：5.1.2
- Xcode：11.2.1 (11B500)
- SwiftLint：0.38.0

## 追加されたルール

### Contains Over Filter Count

- デフォルト：無効

`filter(where:).count` を `0` と比較するより `contains` を使うべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#contains-over-filter-count

```swift
// bad
let result = myList.filter { $0 % 2 == 0 }.count > 0
let result = myList.filter { $0 % 2 == 0 }.count == 0

// good
let result = myList.contains { $0 % 2 == 0 }
let result = myList.filter { $0 % 2 == 0 }.count > 1 // 0以外と比較するのはOK
```

### Contains Over Filter Is Empty

- デフォルト：無効

`filter(where:).isEmpty` より `contains` を使うべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#contains-over-filter-is-empty

```swift
// bad
let result = myList.filter { $0 % 2 == 0 } .isEmpty

// good
let result = myList.contains { $0 % 2 == 0 }
```

### Contains over range(of:) comparison to nil

- デフォルト：無効

`range(of:) != nil` や `range(of:) == nil` より `contains` を使うべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#contains-over-rangeof-comparison-to-nil

```swift
// bad
myString.range(of: "Test") != nil
myString.range(of: "Test") == nil

// good
myString.contains("Test")
let range = myString.range(of: "Test")
resourceString.range(of: rule.regex, options: .regularExpression) != nil
```

### Deployment Target

- デフォルト：有効

可用性のチェックや属性は、デプロイメントターゲットで満たす古いバージョンを使うべきではありません。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#deployment-target

```swift
// bad
@available(iOS 6.0, *)
class A {}

if #available(iOS 6.0, *) {}

// good
@available(iOS 12.0, *)
class A {}

if #available(iOS 10.0, *) {}
```

### Duplicate Enum Cases

- デフォルト：有効

列挙型には同名のケースを複数含められません。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#duplicate-enum-cases

```swift
// bad
enum PictureImport {
    case add(image: UIImage)
    case add(data: Data)
}

// good
enum PictureImport {
    case addImage(image: UIImage)
    case addData(data: Data)
}
```

### Empty Collection Literal

- デフォルト：無効

コレクションを空の配列や辞書リテラルと比較するより、 `isEmpty` をチェックすべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#empty-collection-literal

```swift
// bad
myArray == []
myArray != []
myDict == [:]
myDict != [:]

// good
myArray.isEmpty
!myArray.isEmpty
myDict.isEmpty
!myDict.isEmpty
```

### ExpiringTodo

- デフォルト：無効

TODOとFIXMEは有効期限前に解決すべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#expiringtodo

```swift
// bad
// TODO: [12/7/2019]
// FIXME: [12/7/2019]

// good
// TODO: [12/31/9999]
// FIXME: [12/31/9999]
```

### File Types Order

- デフォルト：無効

ファイル内の型の表示順を指定します。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#file-types-order

```swift
// bad
// `protocol` (サポートする型)は `class` (主となる型)の前に書くべき
class TestViewController: UIViewController {}
protocol TestViewControllerDelegate {
    func didPressTrackedButton()
}

// `extension` は `class` の後に書くべき
extension TestViewController: UITableViewDataSource { // (省略) }
class TestViewController: UIViewController {}

// good
// Supporting Types
protocol TestViewControllerDelegate: AnyObject {
    func didPressTrackedButton()
}

// Main Type
class TestViewController: UIViewController {
    // Type Aliases
    typealias CompletionHandler = ((TestEnum) -> Void)

    // Subtypes
    class TestClass {
        // 10 lines
    }

    struct TestStruct {
        // 3 lines
    }

    enum TestEnum {
        // 5 lines
    }

    // Stored Type Properties
    static let cellIdentifier: String = "AmazingCell"

    // Stored Instance Properties
    var shouldLayoutView1: Bool!
    weak var delegate: TestViewControllerDelegate?
    private var hasLayoutedView1: Bool = false
    private var hasLayoutedView2: Bool = false

    // Computed Instance Properties
    private var hasAnyLayoutedView: Bool {
         return hasLayoutedView1 || hasLayoutedView2
    }

    // IBOutlets
    @IBOutlet private var view1: UIView!
    @IBOutlet private var view2: UIView!

    // Initializers
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // Type Methods
    static func makeViewController() -> TestViewController {
        return TestViewController()
    }

    // Life-Cycle Methods
    override func viewDidLoad() {
        super.viewDidLoad()

        view1.setNeedsLayout()
        view1.layoutIfNeeded()
        hasLayoutedView1 = true
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        view2.setNeedsLayout()
        view2.layoutIfNeeded()
        hasLayoutedView2 = true
    }

    // IBActions
    @IBAction func goNextButtonPressed() {
        goToNextVc()
        delegate?.didPressTrackedButton()
    }

    @objc
    func goToRandomVcButtonPressed() {
        goToRandomVc()
    }

    // MARK: Other Methods
    func goToNextVc() { /* TODO */ }

    func goToInfoVc() { /* TODO */ }

    func goToRandomVc() {
        let viewCtrl = getRandomVc()
        present(viewCtrl, animated: true)
    }

    private func getRandomVc() -> UIViewController { return UIViewController() }

    // Subscripts
    subscript(_ someIndexThatIsNotEvenUsed: Int) -> String {
        get {
            return "This is just a test"
        }

        set {
            print("Just a test \(newValue)")
        }
    }
}

// Extensions
extension TestViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return UITableViewCell()
    }
}
```

### FlatMap over map and reduce

- デフォルト：無効

`reduce([], +)` が後に続く `map` よりも `flatMap` を使うべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#flatmap-over-map-and-reduce

```swift
// bad
let foo = bar.map { $0.array }.reduce([], +)

// good
let foo = bar.flatMap { $0.array }
```

### Legacy Multiple

- デフォルト：無効

`%` より `isMultiple(of:)` を使うべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#legacy-multiple

```swift
// bad
cell.contentView.backgroundColor = indexPath.row % 2 == 0 ? .gray : .white

// good
cell.contentView.backgroundColor = indexPath.row.isMultiple(of: 2) ? .gray : .white
```

### No Space in Method Call

- デフォルト：有効

メソッド名と括弧の間にスペースを入れてはいけません。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#no-space-in-method-call

```swift
// bad
foo ()

// good
foo()
```

### NSLocalizedString Require Bundle

- デフォルト：無効

`NSLocalizedString` の呼び出しは、文字列ファイルを含むバンドルを指定すべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#nslocalizedstring-require-bundle

```swift
// bad
NSLocalizedString("someKey", comment: "test")

// good
NSLocalizedString("someKey", bundle: .main, comment: "test")
NSLocalizedString("someKey", bundle: Bundle(for: A.self), comment: "test")
arbitraryFunctionCall("something")
```

### NSObject Prefer isEqual

- デフォルト：有効

`NSObject` の子クラスは `==` の代わりに `isEqual` を実装すべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#nsobject-prefer-isequal

```swift
// bad
class AClass: NSObject {
    static func ==(lhs: AClass, rhs: AClass) -> Bool {
        return true
    }
}

// good
class AClass: NSObject {}
class AClass: NSObject {
    override func isEqual(_ object: Any?) -> Bool {
        return true
    }
}
```

### Raw Value For Camel Cased Codable Enum

- デフォルト：無効

`Codable` プロトコルに準拠している `String` の列挙型において、キャメルケースのケースはRaw valueを持つべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#raw-value-for-camel-cased-codable-enum

```swift
// bad
enum Status: String, Codable {
    case ok
    case notAcceptable
    case maybeAcceptable = "maybe_acceptable"
}
enum Status: String, Decodable {
   case ok
   case notAcceptable
   case maybeAcceptable = "maybe_acceptable"
}
enum Status: String, Encodable {
   case ok
   case notAcceptable
   case maybeAcceptable = "maybe_acceptable"
}

// good
enum Status: String, Codable {
    case ok
    case notAcceptable = "not_acceptable"
    case maybeAcceptable = "maybe_acceptable"
}
```

### Reduce Boolean

- デフォルト：有効

`reduce(true)` や `reduce(false)` より、 `.allSatisfy()` や `.contains()` を使うべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#reduce-boolean

```swift
// bad
let allNines = nums.reduce(true) { $0.0 && $0.1 == 9 }
let anyNines = nums.reduce(false) { $0.0 || $0.1 == 9 }

// good
let allNines = nums.allSatisfy { $0 == 9 }
let anyNines = nums.contains { $0 == 9 }
```

### Reduce Into

- デフォルト：無効

コピーオンライト型の場合、 `reduce(_:_:)` より `reduce(into:_:)` を使うべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#reduce-into

```swift
// bad
let bar = values.reduce("abc") { $0 + "\($1)" }

// good
let foo = values.reduce(into: "abc") { $0 += "\($1)" }
```

### Required Deinit

- デフォルト：無効

クラスは明示的に `deinit` メソッドを持つべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#required-deinit

```swift
// bad
class Apple { }

// good
class Apple {
    deinit { }
}
```

### Type Contents Order

- デフォルト：無効

型内のサブタイプ、プロパティ、メソッドなどの表示順を指定します。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#type-contents-order

```swift
// bad
// `typealias` は `class` の前に書くべき
class TestViewController: UIViewController {
    class TestClass { }
    typealias CompletionHandler = ((TestEnum) -> Void)
}

// good
protocol TestViewControllerDelegate: AnyObject {
    func didPressTrackedButton()
}

class TestViewController: UIViewController {
    // Type Aliases
    typealias CompletionHandler = ((TestEnum) -> Void)

    // Subtypes
    class TestClass {
        // 10 lines
    }

    struct TestStruct {
        // 3 lines
    }

    enum TestEnum {
        // 5 lines
    }

    // Type Properties
    static let cellIdentifier: String = "AmazingCell"

    // Instance Properties
    var shouldLayoutView1: Bool!
    weak var delegate: TestViewControllerDelegate?
    private var hasLayoutedView1: Bool = false
    private var hasLayoutedView2: Bool = false

    private var hasAnyLayoutedView: Bool {
         return hasLayoutedView1 || hasLayoutedView2
    }

    // IBOutlets
    @IBOutlet private var view1: UIView!
    @IBOutlet private var view2: UIView!

    // Initializers
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // Type Methods
    static func makeViewController() -> TestViewController {
        return TestViewController()
    }

    // View Life-Cycle Methods
    override func viewDidLoad() {
        super.viewDidLoad()

        view1.setNeedsLayout()
        view1.layoutIfNeeded()
        hasLayoutedView1 = true
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        view2.setNeedsLayout()
        view2.layoutIfNeeded()
        hasLayoutedView2 = true
    }

    // IBActions
    @IBAction func goNextButtonPressed() {
        goToNextVc()
        delegate?.didPressTrackedButton()
    }

    // Other Methods
    func goToNextVc() { /* TODO */ }

    func goToInfoVc() { /* TODO */ }

    func goToRandomVc() {
        let viewCtrl = getRandomVc()
        present(viewCtrl, animated: true)
    }

    private func getRandomVc() -> UIViewController { return UIViewController() }

    // Subscripts
    subscript(_ someIndexThatIsNotEvenUsed: Int) -> String {
        get {
            return "This is just a test"
        }

        set {
            print("Just a test \(newValue)")
        }
    }
}
```

### Unowned Variable Capture

- デフォルト：無効

潜在的なクラッシュを避けるため、弱参照としてキャプチャすべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#unowned-variable-capture

```swift
// bad
foo { [unowned self] in _ }
foo { [unowned bar] in _ }

// good
foo { [weak self] in _ }
foo { [weak bar] in _ }
```

### Unused Capture List

- デフォルト：有効

キャプチャリスト内の未使用の参照は削除すべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#unused-capture-list

```swift
// bad
[1, 2].map { [weak self] num in
    print(num)
}

// good
[1, 2].map { num in
    print(num)
}

[1, 2].map { [weak self] num in
    self?.handle(num)
}
```

### Unused Declaration

- デフォルト：無効

Unused Private Declarationが廃止され、代わりに追加されました。

`private` 以外でも、リントされた全ファイル内で1回は参照されるべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#unused-declaration

```swift
// bad
let a = 0

// good
let a = 0
_ = a
```

## 変更されたルール

### Contains over first not nil

`first(where:) != nil` や `firstIndex(where:) != nil` より `contains` を使うべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#contains-over-first-not-nil

`firstIndex(where:) != nil` が追加されました。
https://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed#contains-over-first-not-nil

```swift
// bad
myList.firstIndex { $0 % 2 == 0 } != nil
myList.firstIndex(where: { $0 % 2 == 0 }) != nil

// good
let firstIndex = myList.firstIndex { $0 % 2 == 0 }
let firstIndex = myList.firstIndex(where: { $0 % 2 == 0 })
```

### Control Statement

オートコレクトされるようになりました。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#control-statement

https://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed#control-statement

### Redundant @objc Attribute

オートコレクトされるようになりました。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#redundant-objc-attribute

https://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed#redundant-objc-attribute

### Syntactic Sugar

オートコレクトされるようになりました。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#syntactic-sugar

https://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed#syntactic-sugar

### Toggle Bool

オートコレクトされるようになりました。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#toggle-bool

https://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed#toggle-bool

## 廃止されたルール

### Unused Private Declaration

[Unused Declaration](#Unused-Declaration) に統合されました。
https://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed#unused-private-declaration

## おわりに

これでSwift 5.1.2でもSwiftLintを適切に使うことができます！