lint: add SwiftLint rule against Int for epoch milliseconds (#8181)

This commit is contained in:
Will Taylor authored and GitHub committed 2026-11-02 09:43:05 -06:00
1 parent cd9357a08a
commit 50444c22d9
2 files changed
+51

No files matched your search

+17
View File
@@ -109,6 +109,23 @@ custom_rules:
message: "Use Bundle.revenueCatUI instead of Bundle.module for Compose Resources compatibility"
severity: error
no_int_epoch_milliseconds:
name: "No Int for epoch milliseconds"
message: "Epoch milliseconds (~1.7e12) overflow `Int` on 32-bit platforms (e.g. arm64_32 Apple Watches), crashing at runtime. Use `Int64` instead."
severity: error
included:
- "Sources/.*\\.swift"
- "RevenueCatUI/.*\\.swift"
- "AdapterSDKs/RevenueCatAdMob/Sources/.*\\.swift"
excluded_match_kinds:
- comment
- doccomment
- doccomment.field
- string
# Matches, within a single line: Int(<...since...> * 1000), Int(1000 * <...since...>),
# Int(<...since...>) * 1000 and 1000 * Int(<...since...>). One level of nested parentheses is supported.
regex: '\bInt\((?:[^()\n]|\([^()\n]*\)|\()*timeIntervalSince(?:1970|ReferenceDate|Now)\b(?:[^()\n]|\([^()\n]*\))*\*\s*1_?000\b|\bInt\((?:[^()\n]|\([^()\n]*\)|\()*\b1_?000\s*\*\s*(?:[^()\n]|\([^()\n]*\))*timeIntervalSince(?:1970|ReferenceDate|Now)\b|\bInt\((?:[^()\n]|\([^()\n]*\))*timeIntervalSince(?:1970|ReferenceDate|Now)\b(?:[^()\n]|\([^()\n]*\))*\)\s*\*\s*1_?000\b|\b1_?000\s*\*\s*Int\((?:[^()\n]|\([^()\n]*\))*timeIntervalSince(?:1970|ReferenceDate|Now)\b'
no_new_public_enums:
name: "No new public enums in consumer-facing APIs"
message: "Adding a new `public enum` to the SDK's consumer-facing surface (including `@_spi(Experimental)`) is not allowed. Adding a case to an existing `public enum` is a source-breaking change for any consumer with an exhaustive `switch`. Use a struct with static constants instead."
@@ -0,0 +1,34 @@
import Foundation
let date = Date()
// Int(... * 1000)
let purchaseDate = date.map { Int($0.timeIntervalSince1970 * 1000) } // violation
let expirationDate = Int(date.timeIntervalSince1970 * 1000) // violation
let underscored = Int(date.timeIntervalSince1970 * 1_000) // violation
let nested = Int((date.timeIntervalSince1970 * 1000).rounded()) // violation
let fromNow = Int(Date().timeIntervalSince1970 * 1000) // violation
let wrapped = Int(max(0, date.timeIntervalSince1970 * 1000)) // violation
let safePurchaseDate = date.map { Int64($0.timeIntervalSince1970 * 1000) }
let safeRounded = Int64((date.timeIntervalSince1970 * 1000).rounded())
// Int(...) * 1000
let convertedFirst = Int(date.timeIntervalSince1970) * 1000 // violation
let leadingMultiplier = 1000 * Int(date.timeIntervalSince1970) // violation
let safeConvertedFirst = Int64(date.timeIntervalSince1970) * 1000
// Reversed operands
let reversed = Int(1000 * date.timeIntervalSince1970) // violation
let reversedNested = Int((1_000 * date.timeIntervalSinceReferenceDate).rounded()) // violation
let safeReversed = Int64(1000 * date.timeIntervalSince1970)
// Not epoch milliseconds
let seconds = Int(date.timeIntervalSince1970)
let smallDuration = Int(duration * 1000)
let unrelatedReversed = Int(1000 * duration)
let otherCall = Int(1) + foo(date.timeIntervalSince1970 * 1000)
let separateInt = Int(5); let millis = Double(date.timeIntervalSince1970 * 1000)
// Comments are ignored
// let commented = Int(date.timeIntervalSince1970 * 1000)
let trailingComment = Int(1) // timeIntervalSince1970 * 1000