SwiftLintの記事はシリーズになっています。
記事を順番に読み進めると、SwiftLintを使いこなせるようになります。
- Swiftの静的解析ツール「SwiftLint」のセットアップ方法
- SwiftLintの全ルール一覧(Swift 4.2版)
- SwiftLintで追加・変更・廃止されたルールまとめ(Swift 4.2→5.1.2版) ←イマココ
- SwiftLintで追加・廃止されたルールまとめ(Swift 5.1.2→5.3.1版)
- SwiftLintのオレオレカスタムルール一覧
- SwiftLintのAnalyzeを使って高度な解析をする方法
- SwiftLintの設定ファイルをサーバーに配置して共有する方法
- SwiftLintの「Currently running SwiftLint x.y.z but configuration specified version a.b.c.」エラーの解決法
はじめに
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:).count
を 0
と比較するより 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:) != nil
や range(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:) != 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
// 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を適切に使うことができます!