From e4b1c29b04f832fd2ac7db1915d89b5bf559afcd Mon Sep 17 00:00:00 2001 From: Fumito Ito Date: Tue, 19 Mar 2024 14:49:52 +0900 Subject: [PATCH] add swift lint and fix some warnings --- .swiftlint.yml | 112 ++++++++++++++++++ Package.resolved | 86 ++++++++++++++ Package.swift | 7 +- Sources/AnthropicSwiftSDK/Anthropic.swift | 2 +- .../AnthropicSwiftSDK/AnthropicAPIError.swift | 2 +- .../AnthropicSwiftSDK/AnthropicVersion.swift | 1 + .../AnthropicSwiftSDK/Entity/Content.swift | 2 +- Sources/AnthropicSwiftSDK/Entity/Model.swift | 3 + Sources/AnthropicSwiftSDK/Messages.swift | 5 +- .../Network/AnthropicAPIClient.swift | 8 +- .../AnthropicHeaderProvider.swift | 2 +- .../AuthenticationHeaderProvider.swift | 4 +- .../StreamingDataLineParser.swift | 4 +- .../StreamingEventLineParser.swift | 2 +- .../StreamingResponseParser.swift | 2 +- .../Util/AnthropicJSONDecoder.swift | 4 +- .../Network/MessagesResponseTests.swift | 8 +- 17 files changed, 231 insertions(+), 23 deletions(-) create mode 100644 .swiftlint.yml create mode 100644 Package.resolved diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..57a1573 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,112 @@ +excluded: + - ${PWD}/DerivedData + - ${PWD}/.build + - Tests + - Example.swiftpm + +disabled_rules: + - discarded_notification_center_observer + - notification_center_detachment + - orphaned_doc_comment + - todo + - unused_capture_list + - opening_brace + - trailing_comma + +analyzer_rules: + - unused_import + +opt_in_rules: + - array_init + - attributes + - closure_end_indentation + - closure_spacing + - collection_alignment + - colon # promote to error + - convenience_type + - discouraged_object_literal + - empty_collection_literal + - empty_count + - empty_string + - enum_case_associated_values_count + - fatal_error_message + - first_where + - force_unwrapping + - implicitly_unwrapped_optional + - indentation_width + - last_where + - legacy_random + - literal_expression_end_indentation + - multiline_arguments + - multiline_function_chains + - multiline_literal_brackets + - multiline_parameters + - multiline_parameters_brackets + - operator_usage_whitespace + - overridden_super_call + - pattern_matching_keywords + - prefer_self_type_over_type_of_self + - redundant_nil_coalescing + - redundant_type_annotation + - strict_fileprivate + - toggle_bool + - trailing_closure + - unneeded_parentheses_in_closure_argument + - vertical_whitespace_closing_braces + - vertical_whitespace_opening_braces + - yoda_condition + + +custom_rules: + array_constructor: + name: "Array/Dictionary initializer" + regex: '[let,var] .+ = (\[.+\]\(\))' + capture_group: 1 + message: "Use explicit type annotation when initializing empty arrays and dictionaries" + severity: warning + + +attributes: + always_on_same_line: + - "@IBSegueAction" + - "@IBAction" + - "@NSManaged" + - "@objc" + +force_cast: warning +force_try: warning +function_body_length: + warning: 100 + +legacy_hashing: error + +identifier_name: + excluded: + - i + - id + - x + - y + - z + +indentation_width: + indentation_width: 4 + +line_length: + warning: 150 + ignores_urls: true + ignores_function_declarations: true + ignores_comments: true + +multiline_arguments: + first_argument_location: next_line + only_enforce_after_first_closure_on_first_line: true + +private_over_fileprivate: + validate_extensions: true + +trailing_whitespace: + ignores_empty_lines: false + ignores_comments: true + +vertical_whitespace: + max_empty_lines: 2 \ No newline at end of file diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..6562482 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,86 @@ +{ + "pins" : [ + { + "identity" : "collectionconcurrencykit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git", + "state" : { + "revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95", + "version" : "0.2.0" + } + }, + { + "identity" : "cryptoswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/CryptoSwift.git", + "state" : { + "revision" : "7892a123f7e8d0fe62f9f03728b17bbd4f94df5c", + "version" : "1.8.1" + } + }, + { + "identity" : "sourcekitten", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/SourceKitten.git", + "state" : { + "revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56", + "version" : "0.34.1" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", + "version" : "1.2.3" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036", + "version" : "509.0.2" + } + }, + { + "identity" : "swiftlint", + "kind" : "remoteSourceControl", + "location" : "https://github.com/realm/SwiftLint", + "state" : { + "revision" : "f17a4f9dfb6a6afb0408426354e4180daaf49cee", + "version" : "0.54.0" + } + }, + { + "identity" : "swiftytexttable", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scottrhoyt/SwiftyTextTable.git", + "state" : { + "revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", + "version" : "0.9.0" + } + }, + { + "identity" : "swxmlhash", + "kind" : "remoteSourceControl", + "location" : "https://github.com/drmohundro/SWXMLHash.git", + "state" : { + "revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f", + "version" : "7.0.2" + } + }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams.git", + "state" : { + "revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3", + "version" : "5.0.6" + } + } + ], + "version" : 2 +} diff --git a/Package.swift b/Package.swift index 09f6ea5..37285fd 100644 --- a/Package.swift +++ b/Package.swift @@ -12,11 +12,16 @@ let package = Package( name: "AnthropicSwiftSDK", targets: ["AnthropicSwiftSDK"]), ], + dependencies: [ + .package(url: "https://github.com/realm/SwiftLint", .upToNextMajor(from: "0.54.0")), + ], 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: "AnthropicSwiftSDK"), + name: "AnthropicSwiftSDK", + plugins: [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")] + ), .testTarget( name: "AnthropicSwiftSDKTests", dependencies: ["AnthropicSwiftSDK"]), diff --git a/Sources/AnthropicSwiftSDK/Anthropic.swift b/Sources/AnthropicSwiftSDK/Anthropic.swift index 78e1e9c..ff312d8 100644 --- a/Sources/AnthropicSwiftSDK/Anthropic.swift +++ b/Sources/AnthropicSwiftSDK/Anthropic.swift @@ -11,7 +11,7 @@ import Foundation public final class Anthropic { /// Messages API Interface public let messages: Messages - + /// Construction of SDK /// - Parameter apiKey: API key to access Anthropic API. public init(apiKey: String) { diff --git a/Sources/AnthropicSwiftSDK/AnthropicAPIError.swift b/Sources/AnthropicSwiftSDK/AnthropicAPIError.swift index 23b5843..454ec88 100644 --- a/Sources/AnthropicSwiftSDK/AnthropicAPIError.swift +++ b/Sources/AnthropicSwiftSDK/AnthropicAPIError.swift @@ -17,7 +17,7 @@ public enum AnthropicAPIError: String, Decodable, Error { case apiError = "api_error" case overloadedError = "overloaded_error" case unknown - + /// Description of Anthropic API errors. /// /// for more detail, see https://docs.anthropic.com/claude/reference/errors diff --git a/Sources/AnthropicSwiftSDK/AnthropicVersion.swift b/Sources/AnthropicSwiftSDK/AnthropicVersion.swift index e8d7200..5e8b217 100644 --- a/Sources/AnthropicSwiftSDK/AnthropicVersion.swift +++ b/Sources/AnthropicSwiftSDK/AnthropicVersion.swift @@ -8,6 +8,7 @@ import Foundation enum AnthropicVersion { + // swiftlint:disable:next identifier_name case v2023_06_01 case custom(String) } diff --git a/Sources/AnthropicSwiftSDK/Entity/Content.swift b/Sources/AnthropicSwiftSDK/Entity/Content.swift index ead5b11..f77dfd0 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Content.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Content.swift @@ -71,7 +71,7 @@ extension Content: Decodable { let image = try container.decode(ImageContent.self, forKey: .source) self = .image(image) default: - fatalError() + fatalError("Unknown content type detected") } } } diff --git a/Sources/AnthropicSwiftSDK/Entity/Model.swift b/Sources/AnthropicSwiftSDK/Entity/Model.swift index 53c826a..f938e74 100644 --- a/Sources/AnthropicSwiftSDK/Entity/Model.swift +++ b/Sources/AnthropicSwiftSDK/Entity/Model.swift @@ -12,10 +12,13 @@ import Foundation /// See [models](https://docs.anthropic.com/claude/docs/models-overview) for additional details and options. public enum Model { /// Most powerful model for highly complex tasks + // swiftlint:disable:next identifier_name case claude_3_Opus /// Ideal balance of intelligence and speed for enterprise workloads + // swiftlint:disable:next identifier_name case claude_3_Sonnet /// Fastest and most compact model for near-instant responsiveness + // swiftlint:disable:next identifier_name case claude_3_Haiku /// Custom Model case custom(String) diff --git a/Sources/AnthropicSwiftSDK/Messages.swift b/Sources/AnthropicSwiftSDK/Messages.swift index 836703d..93a0d3d 100644 --- a/Sources/AnthropicSwiftSDK/Messages.swift +++ b/Sources/AnthropicSwiftSDK/Messages.swift @@ -86,7 +86,7 @@ public struct Messages { throw AnthropicAPIError(fromHttpStatusCode: httpResponse.statusCode) } - return try AnthropicJSONDecoder.decode(MessagesResponse.self, from: data) + return try anthropicJSONDecoder.decode(MessagesResponse.self, from: data) } public func streamMessage( @@ -115,6 +115,7 @@ public struct Messages { ) } + // swiftlint:disable:next cyclomatic_complexity public func streamMessage( _ messages: [Message], model: Model = .claude_3_Opus, @@ -159,7 +160,7 @@ public struct Messages { return AsyncThrowingStream.init { continuation in let task = Task { - var currentEvent: StreamingEvent? = nil + var currentEvent: StreamingEvent? for try await line in data.lines { do { let lineType = try StreamingResponseParser.parse(line: line) diff --git a/Sources/AnthropicSwiftSDK/Network/AnthropicAPIClient.swift b/Sources/AnthropicSwiftSDK/Network/AnthropicAPIClient.swift index ac9b531..02242ca 100644 --- a/Sources/AnthropicSwiftSDK/Network/AnthropicAPIClient.swift +++ b/Sources/AnthropicSwiftSDK/Network/AnthropicAPIClient.swift @@ -45,7 +45,7 @@ struct AnthropicAPIClient { }() private var headers: [String: String] { - var headers = [String: String]() + var headers: [String: String] = [:] anthropicHeaderProvider.getAnthropicAPIHeaders().forEach { key, value in headers.updateValue(value, forKey: key) @@ -70,7 +70,7 @@ struct AnthropicAPIClient { private var urlRequest: URLRequest { guard let url = requestURL else { - fatalError() + fatalError("APIClient must have requestURL") } return URLRequest(url: url) @@ -87,7 +87,7 @@ struct AnthropicAPIClient { self.authenticationHeaderProvider = authenticationHeaderProvider self.session = session } - + /// Send messages API request. This method receives HTTP response from API. /// /// For more detail, see https://docs.anthropic.com/claude/reference/messages_post. @@ -103,7 +103,7 @@ struct AnthropicAPIClient { return try await session.data(for: request) } - + /// Send messages API request. This method read the messages api response sequentially. /// /// For more detail, see https://docs.anthropic.com/claude/reference/messages-streaming. diff --git a/Sources/AnthropicSwiftSDK/Network/HeaderProvider/AnthropicHeaderProvider.swift b/Sources/AnthropicSwiftSDK/Network/HeaderProvider/AnthropicHeaderProvider.swift index caba25b..cc03629 100644 --- a/Sources/AnthropicSwiftSDK/Network/HeaderProvider/AnthropicHeaderProvider.swift +++ b/Sources/AnthropicSwiftSDK/Network/HeaderProvider/AnthropicHeaderProvider.swift @@ -22,7 +22,7 @@ struct DefaultAnthropicHeaderProvider: AnthropicHeaderProvider { private let betaDescription = "messages-2023-12-15" - func getAnthropicAPIHeaders() -> [String : String] { + func getAnthropicAPIHeaders() -> [String: String] { var headers: [String: String] = [ "anthropic-version": version.stringfy, "content-type": contentType diff --git a/Sources/AnthropicSwiftSDK/Network/HeaderProvider/AuthenticationHeaderProvider.swift b/Sources/AnthropicSwiftSDK/Network/HeaderProvider/AuthenticationHeaderProvider.swift index 8a9189f..026d8d2 100644 --- a/Sources/AnthropicSwiftSDK/Network/HeaderProvider/AuthenticationHeaderProvider.swift +++ b/Sources/AnthropicSwiftSDK/Network/HeaderProvider/AuthenticationHeaderProvider.swift @@ -15,10 +15,10 @@ public protocol AuthenticationHeaderProvider { } struct APIKeyAuthenticationHeaderProvider: AuthenticationHeaderProvider { - func getAuthenticationHeaders() -> [String : String] { + func getAuthenticationHeaders() -> [String: String] { ["x-api-key": apiKey] } - + private let apiKey: String init(apiKey: String) { diff --git a/Sources/AnthropicSwiftSDK/Network/StreamingParser/StreamingDataLineParser.swift b/Sources/AnthropicSwiftSDK/Network/StreamingParser/StreamingDataLineParser.swift index a3eae16..ea7159d 100644 --- a/Sources/AnthropicSwiftSDK/Network/StreamingParser/StreamingDataLineParser.swift +++ b/Sources/AnthropicSwiftSDK/Network/StreamingParser/StreamingDataLineParser.swift @@ -7,7 +7,7 @@ import Foundation -struct StreamingDataLineParser { +enum StreamingDataLineParser { static func parse(dataLine line: String) throws -> T where T: StreamingResponse { // data line has `data: json` structure and json value might have `data: ` string let jsonString = String(line.dropFirst(6)) @@ -15,6 +15,6 @@ struct StreamingDataLineParser { throw ClientError.cannotHandleDataOfDataLine(jsonString) } - return try AnthropicJSONDecoder.decode(T.self, from: data) + return try anthropicJSONDecoder.decode(T.self, from: data) } } diff --git a/Sources/AnthropicSwiftSDK/Network/StreamingParser/StreamingEventLineParser.swift b/Sources/AnthropicSwiftSDK/Network/StreamingParser/StreamingEventLineParser.swift index 39ed35a..7a3cb12 100644 --- a/Sources/AnthropicSwiftSDK/Network/StreamingParser/StreamingEventLineParser.swift +++ b/Sources/AnthropicSwiftSDK/Network/StreamingParser/StreamingEventLineParser.swift @@ -7,7 +7,7 @@ import Foundation -struct StreamingEventLineParser { +enum StreamingEventLineParser { static func parse(eventLine line: String) throws -> StreamingEvent { // event line has `event: event_name` structure let eventName = line.replacingOccurrences(of: "event: ", with: "") diff --git a/Sources/AnthropicSwiftSDK/Network/StreamingParser/StreamingResponseParser.swift b/Sources/AnthropicSwiftSDK/Network/StreamingParser/StreamingResponseParser.swift index 2154785..78f0b56 100644 --- a/Sources/AnthropicSwiftSDK/Network/StreamingParser/StreamingResponseParser.swift +++ b/Sources/AnthropicSwiftSDK/Network/StreamingParser/StreamingResponseParser.swift @@ -13,7 +13,7 @@ enum StreamingResponseLineType { case data } -struct StreamingResponseParser { +enum StreamingResponseParser { /// line is these kinds /// - `event: event_name` /// - `data: json` diff --git a/Sources/AnthropicSwiftSDK/Util/AnthropicJSONDecoder.swift b/Sources/AnthropicSwiftSDK/Util/AnthropicJSONDecoder.swift index b63264b..e714f21 100644 --- a/Sources/AnthropicSwiftSDK/Util/AnthropicJSONDecoder.swift +++ b/Sources/AnthropicSwiftSDK/Util/AnthropicJSONDecoder.swift @@ -1,5 +1,5 @@ // -// AnthropicJSONDecoder.swift +// anthropicJSONDecoder.swift // // // Created by Fumito Ito on 2024/03/17. @@ -7,7 +7,7 @@ import Foundation -let AnthropicJSONDecoder: JSONDecoder = { +let anthropicJSONDecoder: JSONDecoder = { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase diff --git a/Tests/AnthropicSwiftSDK/Network/MessagesResponseTests.swift b/Tests/AnthropicSwiftSDK/Network/MessagesResponseTests.swift index 1037de7..1644f81 100644 --- a/Tests/AnthropicSwiftSDK/Network/MessagesResponseTests.swift +++ b/Tests/AnthropicSwiftSDK/Network/MessagesResponseTests.swift @@ -31,7 +31,7 @@ final class MessagesResponseTests: XCTestCase { } } """ - XCTAssertNoThrow(try AnthropicJSONDecoder.decode(MessagesResponse.self, from: json.data(using: .utf8)!)) + XCTAssertNoThrow(try anthropicJSONDecoder.decode(MessagesResponse.self, from: json.data(using: .utf8)!)) } func testParseMultipleConversationalTurnsResponse() { @@ -54,7 +54,7 @@ final class MessagesResponseTests: XCTestCase { } } """ - XCTAssertNoThrow(try AnthropicJSONDecoder.decode(MessagesResponse.self, from: json.data(using: .utf8)!)) + XCTAssertNoThrow(try anthropicJSONDecoder.decode(MessagesResponse.self, from: json.data(using: .utf8)!)) } func testPuttingWordsInClaudesMouthResponse() { @@ -78,7 +78,7 @@ final class MessagesResponseTests: XCTestCase { } } """ - XCTAssertNoThrow(try AnthropicJSONDecoder.decode(MessagesResponse.self, from: json.data(using: .utf8)!)) + XCTAssertNoThrow(try anthropicJSONDecoder.decode(MessagesResponse.self, from: json.data(using: .utf8)!)) } func testVisionResponse() { @@ -102,6 +102,6 @@ final class MessagesResponseTests: XCTestCase { } } """ - XCTAssertNoThrow(try AnthropicJSONDecoder.decode(MessagesResponse.self, from: json.data(using: .utf8)!)) + XCTAssertNoThrow(try anthropicJSONDecoder.decode(MessagesResponse.self, from: json.data(using: .utf8)!)) } }