From 9c59e313b207a71a6c2b2455c9097192385b6b7e Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 23 Mar 2026 06:15:40 -0700 Subject: [PATCH 1/5] Add .swift-format --- .swift-format | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .swift-format diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..a0de283 --- /dev/null +++ b/.swift-format @@ -0,0 +1,15 @@ +{ + "version": 1, + "indentation": { + "spaces": 4 + }, + "lineLength": 120, + "maximumBlankLines": 1, + "respectsExistingLineBreaks": true, + "lineBreakBeforeEachArgument": true, + "multiElementCollectionTrailingCommas": true, + "spacesAroundRangeFormationOperators": true, + "rules": { + "AlwaysUseLowerCamelCase": false + } +} \ No newline at end of file From dcbc532c7df32e7a71ee341a194cba84c434d170 Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 23 Mar 2026 06:18:05 -0700 Subject: [PATCH 2/5] Overhaul CI workflow --- .github/workflows/ci.yml | 56 ++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 889a1df..5c10b03 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,27 +2,49 @@ name: CI on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] jobs: - test: - runs-on: macos-latest - + test-macos: + name: Swift ${{ matrix.swift }} on macOS ${{ matrix.macos }} with Xcode ${{ matrix.xcode }} + runs-on: macos-${{ matrix.macos }} + env: + DEVELOPER_DIR: "/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer" strategy: + fail-fast: false matrix: - swift-version: - - ^6 + include: + - swift: "6.0" + xcode: "16.0" + macos: "15" + - swift: "6.1" + xcode: "16.3" + macos: "15" + - swift: "6.2" + xcode: "26.0" + macos: "26" + timeout-minutes: 10 + steps: + - name: Checkout code + uses: actions/checkout@v6 - name: Build and Test (Swift ${{ matrix.swift-version }}) + - name: Cache Swift Package Manager dependencies + uses: actions/cache@v5 + with: + path: | + ~/.cache/org.swift.swiftpm + .build + key: ${{ runner.os }}-swift-${{ matrix.swift }}-spm-${{ hashFiles('Package.swift', 'Package.resolved') }} + restore-keys: | + ${{ runner.os }}-swift-${{ matrix.swift }}-spm- - steps: - - uses: actions/checkout@v4 - - uses: swift-actions/setup-swift@v2 - with: - swift-version: ${{ matrix.swift-version }} - - name: Build - run: swift build -v - - name: Run tests - run: swift test -v + - name: Lint + run: swift format lint --strict --recursive . + + - name: Build + run: swift build + + - name: Test + run: swift test From 06f2ccfc8bc091c7c20631dc909d6789ffb1b66f Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 23 Mar 2026 06:19:52 -0700 Subject: [PATCH 3/5] swift format -i --recursive . --- Package.swift | 12 ++++++++---- Sources/TypedStream/Archivable.swift | 14 +++++++------- Sources/TypedStream/Type.swift | 2 +- Sources/TypedStream/TypedStreamDecoder.swift | 2 +- Sources/iMessage/Database.swift | 12 ++++++++---- .../iMessage/Extensions/Data+Extensions.swift | 2 +- Tests/iMessageTests/DatabaseTests.swift | 8 ++++---- Tests/iMessageTests/Helpers/Fixtures.swift | 18 ++++++++++++------ 8 files changed, 42 insertions(+), 28 deletions(-) diff --git a/Package.swift b/Package.swift index 469f5a0..663bfe3 100644 --- a/Package.swift +++ b/Package.swift @@ -12,20 +12,24 @@ let package = Package( // Products define the executables and libraries a package produces, making them visible to other packages. .library( name: "TypedStream", - targets: ["TypedStream"]), + targets: ["TypedStream"] + ), .library( name: "iMessage", - targets: ["iMessage"]), + targets: ["iMessage"] + ), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( name: "TypedStream", - dependencies: []), + dependencies: [] + ), .target( name: "iMessage", - dependencies: ["TypedStream"]), + dependencies: ["TypedStream"] + ), .testTarget( name: "iMessageTests", dependencies: ["iMessage"] diff --git a/Sources/TypedStream/Archivable.swift b/Sources/TypedStream/Archivable.swift index eefc6a0..9d838fe 100644 --- a/Sources/TypedStream/Archivable.swift +++ b/Sources/TypedStream/Archivable.swift @@ -17,7 +17,7 @@ public enum Archivable: Hashable, Sendable { /** If this archivable represents an `NSString` or `NSMutableString` object, returns its string value. - + ### Example ```swift let nsstring = Archivable.object( @@ -25,7 +25,7 @@ public enum Archivable: Hashable, Sendable { [.string("Hello world")] ) print(nsstring.stringValue) // Optional("Hello world") - + let notNSString = Archivable.object( Class(name: "NSNumber", version: 1), [.signedInteger(100)] @@ -47,7 +47,7 @@ public enum Archivable: Hashable, Sendable { { return nil } - + return text } return nil @@ -56,7 +56,7 @@ public enum Archivable: Hashable, Sendable { /** If this archivable represents an `NSNumber` object containing an integer, returns its 64-bit integer value. - + ### Example ```swift let nsnumber = Archivable.object( @@ -64,7 +64,7 @@ public enum Archivable: Hashable, Sendable { [.signedInteger(100)] ) print(nsnumber.integerValue) // Optional(100) - + let notNSNumber = Archivable.object( Class(name: "NSString", version: 1), [.string("Hello world")] @@ -86,7 +86,7 @@ public enum Archivable: Hashable, Sendable { /** If this archivable represents an `NSNumber` object containing a floating-point value, returns its double-precision value. - + ### Example ```swift let nsnumber = Archivable.object( @@ -94,7 +94,7 @@ public enum Archivable: Hashable, Sendable { [.double(100.001)] ) print(nsnumber.doubleValue) // Optional(100.001) - + let notNSNumber = Archivable.object( Class(name: "NSString", version: 1), [.string("Hello world")] diff --git a/Sources/TypedStream/Type.swift b/Sources/TypedStream/Type.swift index 437149d..f7c4f39 100644 --- a/Sources/TypedStream/Type.swift +++ b/Sources/TypedStream/Type.swift @@ -92,7 +92,7 @@ public enum Type: Hashable, Sendable { var index = 1 while index < types.count, let digit = UInt8(exactly: types[index]), - (48...57).contains(digit) + (48 ... 57).contains(digit) { length = length * 10 + Int(digit - 48) // ASCII '0' is 48 index += 1 diff --git a/Sources/TypedStream/TypedStreamDecoder.swift b/Sources/TypedStream/TypedStreamDecoder.swift index 4000abc..0e21024 100644 --- a/Sources/TypedStream/TypedStreamDecoder.swift +++ b/Sources/TypedStream/TypedStreamDecoder.swift @@ -247,7 +247,7 @@ public final class TypedStreamDecoder { guard idx + size <= stream.count else { throw Error.outOfBounds(index: idx + size, length: stream.count) } - let data = Data(stream[idx..<(idx + size)]) + let data = Data(stream[idx ..< (idx + size)]) idx += size return data } diff --git a/Sources/iMessage/Database.swift b/Sources/iMessage/Database.swift index eae1f3a..9d6fdeb 100644 --- a/Sources/iMessage/Database.swift +++ b/Sources/iMessage/Database.swift @@ -173,7 +173,8 @@ public final class Database { GROUP BY chat_id HAVING COUNT(DISTINCT handle_id) = ? ) - """) + """ + ) // Add each participant as a value handles.forEach { handle in @@ -207,7 +208,8 @@ public final class Database { let displayName = sqlite3_column_text(statement, 1).map { String(cString: $0) } let lastMessageDate = Date( - nanosecondsSinceReferenceDate: sqlite3_column_int64(statement, 3)) + nanosecondsSinceReferenceDate: sqlite3_column_int64(statement, 3) + ) // Fetch participants for this chat let participants = try fetchParticipants(for: chatId) @@ -257,7 +259,8 @@ public final class Database { JOIN handle h ON m.handle_id = h.ROWID WHERE h.id IN (\(String(repeating: "?,", count: handles.count).dropLast())) ) - """) + """ + ) // Add each participant as a value handles.forEach { handle in @@ -325,7 +328,8 @@ public final class Database { } let date = Date( - nanosecondsSinceReferenceDate: sqlite3_column_int64(statement, 3)) + nanosecondsSinceReferenceDate: sqlite3_column_int64(statement, 3) + ) let isFromMe = sqlite3_column_int(statement, 4) != 0 let rawReadAt = sqlite3_column_int64(statement, 7) let readAt = diff --git a/Sources/iMessage/Extensions/Data+Extensions.swift b/Sources/iMessage/Extensions/Data+Extensions.swift index 735699f..65ef69e 100644 --- a/Sources/iMessage/Extensions/Data+Extensions.swift +++ b/Sources/iMessage/Extensions/Data+Extensions.swift @@ -9,7 +9,7 @@ extension Data { while index < string.endIndex { let nextIndex = string.index(index, offsetBy: 2) guard nextIndex <= string.endIndex, - let byte = UInt8(string[index.. Date: Mon, 23 Mar 2026 06:22:24 -0700 Subject: [PATCH 4/5] Relax maximum line length to avoid linter warnings --- .swift-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swift-format b/.swift-format index a0de283..8cfd52e 100644 --- a/.swift-format +++ b/.swift-format @@ -3,7 +3,7 @@ "indentation": { "spaces": 4 }, - "lineLength": 120, + "lineLength": 160, "maximumBlankLines": 1, "respectsExistingLineBreaks": true, "lineBreakBeforeEachArgument": true, From ea93d221c662075e18722be4c6176314f74c5b8c Mon Sep 17 00:00:00 2001 From: Mattt Zmuda Date: Mon, 23 Mar 2026 06:25:19 -0700 Subject: [PATCH 5/5] Convert /** */ comments to /// --- Sources/TypedStream/Archivable.swift | 108 +++++++++++++-------------- 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/Sources/TypedStream/Archivable.swift b/Sources/TypedStream/Archivable.swift index 9d838fe..26e0a43 100644 --- a/Sources/TypedStream/Archivable.swift +++ b/Sources/TypedStream/Archivable.swift @@ -14,25 +14,23 @@ public enum Archivable: Hashable, Sendable { // MARK: - Convenience Properties - /** - If this archivable represents an `NSString` or `NSMutableString` object, - returns its string value. - - ### Example - ```swift - let nsstring = Archivable.object( - Class(name: "NSString", version: 1), - [.string("Hello world")] - ) - print(nsstring.stringValue) // Optional("Hello world") - - let notNSString = Archivable.object( - Class(name: "NSNumber", version: 1), - [.signedInteger(100)] - ) - print(notNSString.stringValue) // nil - ``` - */ + /// If this archivable represents an `NSString` or `NSMutableString` object, + /// returns its string value. + /// + /// ### Example + /// ```swift + /// let nsstring = Archivable.object( + /// Class(name: "NSString", version: 1), + /// [.string("Hello world")] + /// ) + /// print(nsstring.stringValue) // Optional("Hello world") + /// + /// let notNSString = Archivable.object( + /// Class(name: "NSNumber", version: 1), + /// [.signedInteger(100)] + /// ) + /// print(notNSString.stringValue) // nil + /// ``` public var stringValue: String? { if case let .object(classInfo, value) = self, classInfo.name == "NSString" || classInfo.name == "NSMutableString", @@ -53,25 +51,23 @@ public enum Archivable: Hashable, Sendable { return nil } - /** - If this archivable represents an `NSNumber` object containing an integer, - returns its 64-bit integer value. - - ### Example - ```swift - let nsnumber = Archivable.object( - Class(name: "NSNumber", version: 1), - [.signedInteger(100)] - ) - print(nsnumber.integerValue) // Optional(100) - - let notNSNumber = Archivable.object( - Class(name: "NSString", version: 1), - [.string("Hello world")] - ) - print(notNSNumber.integerValue) // nil - ``` - */ + /// If this archivable represents an `NSNumber` object containing an integer, + /// returns its 64-bit integer value. + /// + /// ### Example + /// ```swift + /// let nsnumber = Archivable.object( + /// Class(name: "NSNumber", version: 1), + /// [.signedInteger(100)] + /// ) + /// print(nsnumber.integerValue) // Optional(100) + /// + /// let notNSNumber = Archivable.object( + /// Class(name: "NSString", version: 1), + /// [.string("Hello world")] + /// ) + /// print(notNSNumber.integerValue) // nil + /// ``` public var integerValue: Int64? { if case let .object(classInfo, value) = self, classInfo.name == "NSNumber", @@ -83,25 +79,23 @@ public enum Archivable: Hashable, Sendable { return nil } - /** - If this archivable represents an `NSNumber` object containing a floating-point value, - returns its double-precision value. - - ### Example - ```swift - let nsnumber = Archivable.object( - Class(name: "NSNumber", version: 1), - [.double(100.001)] - ) - print(nsnumber.doubleValue) // Optional(100.001) - - let notNSNumber = Archivable.object( - Class(name: "NSString", version: 1), - [.string("Hello world")] - ) - print(notNSNumber.doubleValue) // nil - ``` - */ + /// If this archivable represents an `NSNumber` object containing a floating-point value, + /// returns its double-precision value. + /// + /// ### Example + /// ```swift + /// let nsnumber = Archivable.object( + /// Class(name: "NSNumber", version: 1), + /// [.double(100.001)] + /// ) + /// print(nsnumber.doubleValue) // Optional(100.001) + /// + /// let notNSNumber = Archivable.object( + /// Class(name: "NSString", version: 1), + /// [.string("Hello world")] + /// ) + /// print(notNSNumber.doubleValue) // nil + /// ``` public var doubleValue: Double? { if case let .object(classInfo, value) = self, classInfo.name == "NSNumber",