LoginSignup
7

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-12-08

はじめに

SwiftLintの全ルール一覧(Swift 4.2版) - Qiita から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:).count0 と比較するより contains を使うべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#contains-over-filter-count

// 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

// 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:) != nilrange(of:) == nil より contains を使うべきです。
https://github.com/realm/SwiftLint/blob/0.38.0/Rules.md#contains-over-rangeof-comparison-to-nil

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// bad
foo ()

// good
foo()

NSLocalizedString Require Bundle

  • デフォルト:無効

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

// 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

// 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

// 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

// 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

// 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

// bad
class Apple { }

// good
class Apple {
    deinit { }
}

Type Contents Order

  • デフォルト:無効

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

// 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

// 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

// 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

// bad
let a = 0

// good
let a = 0
_ = a

変更されたルール

Contains over first not nil

first(where:) != nilfirstIndex(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

// 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

Redundant @objc Attribute

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

Syntactic Sugar

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

Toggle Bool

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

廃止されたルール

Unused Private Declaration

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

おわりに

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
7