15
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SwiftWednesdayAdvent Calendar 2023

Day 5

SwiftLintのオレオレカスタムルール一覧

Last updated at Posted at 2023-12-04

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

はじめに

本記事は SwiftWednesday Advent Calendar 2023 の5日目の記事です。
昨日は @ojun_9 さんで @Observable Macro を構造体へ適用させる難しさについて でした。

SwiftLintで私が自作しているカスタムルールを紹介します。

環境

  • OS:macOS Sonoma 14.0(23A344)
  • Swift:5.9

オレオレカスタムルール一覧

私が自作しているカスタムルールの一覧です。

enum_lower_case

enumのcase名の先頭が大文字より小文字を好むルールです。

.swiftlint.yml
  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 を必ず付けるルールです。

.swiftlint.yml
  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 を好むルールです。

.nowDate() と同等ですが、 .now のほうがわかりやすいので好みです。

.swiftlint.yml
  # 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-algorithmsindexed() を好むルールです。

.swiftlint.yml
  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

print(_:separator:terminator:) より Logger.debug() を好むルールです。

Logger.debug() は自作の関数なので、必要に応じてメッセージを変更してください。

.swiftlint.yml
  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) と同等ですが、短いほうが好みです。

.swiftlint.yml
  # 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 と同じ理由でルール化しています。

.swiftlint.yml
  # 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:) に置き換えることが多いです。

.swiftlint.yml
  # 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(_:) を好むルールです。

.swiftlint.yml
  # 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 を好むルールです。

.swiftlint.yml
  # 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 を好むルールです。

.swiftlint.yml
  # 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 を好むルールです。

私はできる限りサードパーティ製のライブラリをカプセル化したいため、このようなルールを自作しています。

.swiftlint.yml
  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のカスタムルールは正規表現で書くので、慣れていないと難しいです。
私も試行錯誤しながら書いたので、誤っていたり、よりいい書き方があればコメントしてくださると嬉しいです :relaxed:

以上 SwiftWednesday Advent Calendar 2023 の5日目の記事でした。
明日も @ojun_9 さんです。

15
7
0

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
  3. You can use dark theme
What you can do with signing up
15
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?