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.」エラーの解決法
はじめに
本記事は SwiftWednesday Advent Calendar 2023 の5日目の記事です。
昨日は @ojun_9 さんで @Observable Macro を構造体へ適用させる難しさについて でした。
SwiftLintで私が自作しているカスタムルールを紹介します。
環境
- OS:macOS Sonoma 14.0(23A344)
- Swift:5.9
オレオレカスタムルール一覧
私が自作しているカスタムルールの一覧です。
enum_lower_case
enumのcase名の先頭が大文字より小文字を好むルールです。
enum_lower_case:
name: 'Enum Lower Case'
regex: '\bcase\s+([A-Z][a-zA-Z0-9]*)\b'
capture_group: 1
message: 'The enum case should be lower camel case'
severity: warning
// Non Triggering Examples
enum Foo {
case bar
}
// Triggering Examples
enum Foo {
case ↓Bar
}
final_class
classに final
を必ず付けるルールです。
final_class:
name: 'Final Class'
regex: '\b(?<!final)\s+(class)\b'
capture_group: 1
message: 'Always add `final` to classes that do not inherit'
severity: warning
// Non Triggering Examples
final class Foo {}
// Triggering Examples
↓class Foo {}
date_now
Date()
より .now
を好むルールです。
.now
は Date()
と同等ですが、 .now
のほうがわかりやすいので好みです。
# iOS 15.0+
date_now:
name: 'Date Now'
regex: '[=:(]\s*(Date\(\))'
capture_group: 1
message: 'Prefer using `.now` over `Date()`'
severity: warning
// Non Triggering Examples
let weekday = Calendar.current.component(.weekday, from: .now)
// Triggering Examples
let weekday = Calendar.current.component(.weekday, from: ↓Date())
enumerated
enumerated()
より swift-algorithms の indexed()
を好むルールです。
enumerated:
name: 'Enumerated'
regex: '.+\.(enumerated\([^)]*\))'
capture_group: 1
message: 'Prefer using `indexed()` over `enumerated()`'
severity: warning
// Non Triggering Examples
import Algorithms
ForEach(monsters.indexed(), id: \.element.id) { index, monster in
// Do something
}
// Triggering Examples
ForEach(Array(monsters.↓enumerated()), id: \.element.id) { index, monster in
// Do something
}
print(_:separator:terminator:)
より Logger.debug()
を好むルールです。
Logger.debug()
は自作の関数なので、必要に応じてメッセージを変更してください。
print:
name: 'Print'
regex: '\bprint\([^)]*\)'
capture_group: 0
message: 'Prefer using `Logger.debug()` over `print()`'
severity: warning
// Non Triggering Examples
Logger.debug("Foo")
// Triggering Examples
↓print("Foo")
aspect_ratio_fill
.aspectRatio(contentMode: .fill)
より .scaledToFill()
を好むルールです。
.scaledToFill()
は .aspectRatio(contentMode: .fill)
と同等ですが、短いほうが好みです。
# iOS 13.0+
aspect_ratio_fill:
name: 'Aspect Ratio Fill'
regex: '.+\.(aspectRatio\(contentMode:\s*\.fill\s*\))'
capture_group: 1
message: 'Prefer using `scaledToFill()` over `aspectRatio(contentMode: .fill)`'
severity: warning
// Non Triggering Examples
Image(systemName: "swift")
.resizable()
.scaledToFill()
// Triggering Examples
Image(systemName: "swift")
.resizable()
.↓aspectRatio(contentMode: .fill)
aspect_ratio_fit
.aspectRatio(contentMode: .fit)
より .scaledToFit()
を好むルールです。
aspect_ratio_fill
と同じ理由でルール化しています。
# iOS 13.0+
aspect_ratio_fit:
name: 'Aspect Ratio Fit'
regex: '.+\.(aspectRatio\(contentMode:\s*\.fit\s*\))'
capture_group: 1
message: 'Prefer using `scaledToFit()` over `aspectRatio(contentMode: .fit)`'
severity: warning
// Non Triggering Examples
Image(systemName: "swift")
.resizable()
.scaledToFit()
// Triggering Examples
Image(systemName: "swift")
.resizable()
.↓aspectRatio(contentMode: .fit)
corner_radius
cornerRadius(_:antialiased:)
は非推奨になったため、 clipShape(_:style:)
または fill(style:)
を好むルールです。
clipShape()
内は RoundedRectangle(roundedRect:cornerRadius:style:)
に置き換えることが多いです。
# iOS 13.0+
corner_radius:
name: 'Corner Radius'
regex: '.+\.(cornerRadius\([^)]*\))'
capture_group: 1
message: 'Prefer using `clipShape(RoundedRectangle(cornerRadius:))` or `fill()` over `cornerRadius()` because it was deprecated'
severity: warning
// Non Triggering Examples
Text("Foo")
.clipShape(RoundedRectangle(cornerRadius: 8))
// Triggering Examples
Text("Foo")
.↓cornerRadius(8)
foreground_color
foregroundColor(_:)
は非推奨になったため、 foregroundStyle(_:)
を好むルールです。
# iOS 15.0+
foreground_color:
name: 'Foreground Color'
regex: '.+\.(foregroundColor\([^)]*\))'
capture_group: 1
message: 'Prefer using `foregroundStyle()` over `foregroundColor()` because it was deprecated'
severity: warning
// Non Triggering Examples
Text("Foo")
.foregroundStyle(.blue)
// Triggering Examples
Text("Foo")
.↓foregroundColor(.blue)
navigation_bar_leading
.navigationBarLeading
は非推奨になったため、 .topBarLeading
を好むルールです。
# iOS 14.0+
navigation_bar_leading:
name: 'Navigation Bar Leading'
regex: '\bToolbarItem\(placement:\s*(.navigationBarLeading)\s*\)'
capture_group: 1
message: 'Prefer using `.topBarLeading` over `.navigationBarLeading` because it was deprecated'
severity: warning
// Non Triggering Examples
Text("Foo")
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button {
print("Bar")
} label: {
Text("Bar")
}
}
}
// Triggering Examples
Text("Foo")
.toolbar {
ToolbarItem(placement: ↓.navigationBarLeading) {
Button {
print("Bar")
} label: {
Text("Bar")
}
}
}
navigation_bar_trailing
.navigationBarTrailing
は非推奨になったため、 .topBarTrailing
を好むルールです。
# iOS 14.0+
navigation_bar_trailing:
name: 'Navigation Bar Trailing'
regex: '\bToolbarItem\(placement:\s*(.navigationBarTrailing)\s*\)'
capture_group: 1
message: 'Prefer using `.topBarTrailing` over `.navigationBarTrailing` because it was deprecated'
severity: warning
// Non Triggering Examples
Text("Foo")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
print("Bar")
} label: {
Text("Bar")
}
}
}
// Triggering Examples
Text("Foo")
.toolbar {
ToolbarItem(placement: ↓.navigationBarTrailing) {
Button {
print("Bar")
} label: {
Text("Bar")
}
}
}
import_lottie
Lottie のコンポーネントを直接使うより、自作の UICore.LottieAnimationView
を好むルールです。
私はできる限りサードパーティ製のライブラリをカプセル化したいため、このようなルールを自作しています。
import_lottie:
name: 'Import Lottie'
regex: '\bimport\s+(Lottie)\b'
capture_group: 1
message: 'Prefer using `UICore.LottieAnimationView` over Lottie components'
severity: warning
// Non Triggering Examples
import UICore
// Triggering Examples
import ↓Lottie
おわりに
SwiftLintのカスタムルールは正規表現で書くので、慣れていないと難しいです。
私も試行錯誤しながら書いたので、誤っていたり、よりいい書き方があればコメントしてくださると嬉しいです
以上 SwiftWednesday Advent Calendar 2023 の5日目の記事でした。
明日も @ojun_9 さんです。