diff --git a/.swiftlint.yml b/.swiftlint.yml index e0682bab8..e447ccb0c 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -30,7 +30,7 @@ disabled_rules: excluded: - Modules/Secrets/Sources/Secrets/SecretKey.swift - - Modules/Account/Tests/AccountTests/Feedly/ + - Modules/Account/Tests/ - Modules/Account/Sources/Account/Feedly/ - Widget/Resources/Localized.swift - Shared/Extensions/NSAttributedString+NetNewsWire.swift diff --git a/Modules/Account/Sources/Account/AccountDelegate.swift b/Modules/Account/Sources/Account/AccountDelegate.swift index 22e262c4b..22694dd17 100644 --- a/Modules/Account/Sources/Account/AccountDelegate.swift +++ b/Modules/Account/Sources/Account/AccountDelegate.swift @@ -11,7 +11,7 @@ import Articles import RSWeb import Secrets -protocol AccountDelegate { +protocol AccountDelegate: AnyObject { var behaviors: AccountBehaviors { get } diff --git a/Modules/Account/Sources/Account/FeedFinder/FeedFinder.swift b/Modules/Account/Sources/Account/FeedFinder/FeedFinder.swift index 8ba85c860..6e6bfdf04 100644 --- a/Modules/Account/Sources/Account/FeedFinder/FeedFinder.swift +++ b/Modules/Account/Sources/Account/FeedFinder/FeedFinder.swift @@ -20,7 +20,7 @@ class FeedFinder { if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), urlComponents.host == "micro.blog" { urlComponents.path = "\(urlComponents.path).json" if let newURLString = urlComponents.url?.absoluteString { - let microblogFeedSpecifier = FeedSpecifier(title: nil, urlString: newURLString, source: .HTMLLink, orderFound: 1) + let microblogFeedSpecifier = FeedSpecifier(title: nil, urlString: newURLString, source: .htmlLink, orderFound: 1) completion(.success(Set([microblogFeedSpecifier]))) } } else { @@ -45,7 +45,7 @@ class FeedFinder { } if FeedFinder.isFeed(data) { - let feedSpecifier = FeedSpecifier(title: nil, urlString: url.absoluteString, source: .UserEntered, orderFound: 1) + let feedSpecifier = FeedSpecifier(title: nil, urlString: url.absoluteString, source: .userEntered, orderFound: 1) completion(.success(Set([feedSpecifier]))) return } @@ -86,7 +86,7 @@ private extension FeedFinder { var didFindFeedInHTMLHead = false for oneFeedSpecifier in possibleFeedSpecifiers { - if oneFeedSpecifier.source == .HTMLHead { + if oneFeedSpecifier.source == .htmlHead { addFeedSpecifier(oneFeedSpecifier, feedSpecifiers: &feedSpecifiers) didFindFeedInHTMLHead = true } else { @@ -116,11 +116,11 @@ private extension FeedFinder { // It’s also fairly common for /index.xml to work. if let url = URL(string: urlString) { let feedURL = url.appendingPathComponent("feed", isDirectory: true) - let wordpressFeedSpecifier = FeedSpecifier(title: nil, urlString: feedURL.absoluteString, source: .HTMLLink, orderFound: 1) + let wordpressFeedSpecifier = FeedSpecifier(title: nil, urlString: feedURL.absoluteString, source: .htmlLink, orderFound: 1) feedSpecifiers.insert(wordpressFeedSpecifier) let indexXMLURL = url.appendingPathComponent("index.xml", isDirectory: false) - let indexXMLFeedSpecifier = FeedSpecifier(title: nil, urlString: indexXMLURL.absoluteString, source: .HTMLLink, orderFound: 1) + let indexXMLFeedSpecifier = FeedSpecifier(title: nil, urlString: indexXMLURL.absoluteString, source: .htmlLink, orderFound: 1) feedSpecifiers.insert(indexXMLFeedSpecifier) } } diff --git a/Modules/Account/Sources/Account/FeedFinder/FeedSpecifier.swift b/Modules/Account/Sources/Account/FeedFinder/FeedSpecifier.swift index 38339e41c..56078be44 100644 --- a/Modules/Account/Sources/Account/FeedFinder/FeedSpecifier.swift +++ b/Modules/Account/Sources/Account/FeedFinder/FeedSpecifier.swift @@ -11,7 +11,7 @@ import Foundation struct FeedSpecifier: Hashable { enum Source: Int { - case UserEntered = 0, HTMLHead, HTMLLink + case userEntered = 0, htmlHead, htmlLink func equalToOrBetterThan(_ otherSource: Source) -> Bool { return self.rawValue <= otherSource.rawValue @@ -64,39 +64,39 @@ private extension FeedSpecifier { func calculatedScore() -> Int { var score = 0 - if source == .UserEntered { + if source == .userEntered { return 1000 - } else if source == .HTMLHead { - score = score + 50 + } else if source == .htmlHead { + score += 50 } - score = score - ((orderFound - 1) * 5) + score -= (orderFound - 1) * 5 if urlString.caseInsensitiveContains("comments") { - score = score - 10 + score -= 10 } if urlString.caseInsensitiveContains("podcast") { - score = score - 10 + score -= 10 } if urlString.caseInsensitiveContains("rss") { - score = score + 5 + score += 5 } if urlString.hasSuffix("/feed/") { - score = score + 5 + score += 5 } if urlString.hasSuffix("/feed") { - score = score + 4 + score += 4 } if urlString.caseInsensitiveContains("json") { - score = score + 6 + score += 6 } if let title = title { if title.caseInsensitiveContains("comments") { - score = score - 10 + score -= 10 } if title.caseInsensitiveContains("json") { - score = score + 1 + score += 1 } } diff --git a/Modules/Account/Sources/Account/FeedFinder/HTMLFeedFinder.swift b/Modules/Account/Sources/Account/FeedFinder/HTMLFeedFinder.swift index 8a32e39ca..12fb2dc9d 100644 --- a/Modules/Account/Sources/Account/FeedFinder/HTMLFeedFinder.swift +++ b/Modules/Account/Sources/Account/FeedFinder/HTMLFeedFinder.swift @@ -26,8 +26,8 @@ class HTMLFeedFinder { if let feedLinks = metadata.feedLinks { for oneFeedLink in feedLinks { if let oneURLString = oneFeedLink.urlString?.normalizedURL { - orderFound = orderFound + 1 - let oneFeedSpecifier = FeedSpecifier(title: oneFeedLink.title, urlString: oneURLString, source: .HTMLHead, orderFound: orderFound) + orderFound += 1 + let oneFeedSpecifier = FeedSpecifier(title: oneFeedLink.title, urlString: oneURLString, source: .htmlHead, orderFound: orderFound) addFeedSpecifier(oneFeedSpecifier) } } @@ -36,8 +36,8 @@ class HTMLFeedFinder { let bodyLinks = HTMLLinkParser.htmlLinks(with: parserData) for oneBodyLink in bodyLinks { if linkMightBeFeed(oneBodyLink), let normalizedURL = oneBodyLink.urlString?.normalizedURL { - orderFound = orderFound + 1 - let oneFeedSpecifier = FeedSpecifier(title: oneBodyLink.text, urlString: normalizedURL, source: .HTMLLink, orderFound: orderFound) + orderFound += 1 + let oneFeedSpecifier = FeedSpecifier(title: oneBodyLink.text, urlString: normalizedURL, source: .htmlLink, orderFound: orderFound) addFeedSpecifier(oneFeedSpecifier) } } diff --git a/Modules/Account/Sources/Account/Feedbin/FeedbinAPICaller.swift b/Modules/Account/Sources/Account/Feedbin/FeedbinAPICaller.swift index 47da25335..6348763ea 100644 --- a/Modules/Account/Sources/Account/Feedbin/FeedbinAPICaller.swift +++ b/Modules/Account/Sources/Account/Feedbin/FeedbinAPICaller.swift @@ -486,6 +486,7 @@ final class FeedbinAPICaller: NSObject { } + // swiftlint:disable:next large_tuple func retrieveEntries(completion: @escaping (Result<([FeedbinEntry]?, String?, Date?, Int?), Error>) -> Void) { // If this is an initial sync, go and grab the previous 3 months of entries. If not, use the last diff --git a/Modules/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift b/Modules/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift index c729122a1..2eef6c60d 100644 --- a/Modules/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Modules/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift @@ -938,9 +938,7 @@ private extension FeedbinAccountDelegate { return d } - func sendArticleStatuses(_ statuses: [SyncStatus], - apiCall: ([Int], @escaping (Result) -> Void) -> Void, - completion: @escaping ((Result) -> Void)) { + func sendArticleStatuses(_ statuses: [SyncStatus], apiCall: ([Int], @escaping (Result) -> Void) -> Void, completion: @escaping ((Result) -> Void)) { guard !statuses.isEmpty else { completion(.success(())) @@ -1011,8 +1009,8 @@ private extension FeedbinAccountDelegate { var orderFound = 0 let feedSpecifiers: [FeedSpecifier] = choices.map { choice in - let source = url == choice.url ? FeedSpecifier.Source.UserEntered : FeedSpecifier.Source.HTMLLink - orderFound = orderFound + 1 + let source = url == choice.url ? FeedSpecifier.Source.userEntered : FeedSpecifier.Source.htmlLink + orderFound += 1 let specifier = FeedSpecifier(title: choice.name, urlString: choice.url, source: source, orderFound: orderFound) return specifier } diff --git a/Modules/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift b/Modules/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift index c3dbf732e..54b7df2ab 100644 --- a/Modules/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift +++ b/Modules/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift @@ -14,13 +14,13 @@ import Articles import ArticlesDatabase import os -protocol LocalAccountRefresherDelegate { +protocol LocalAccountRefresherDelegate: AnyObject { func localAccountRefresher(_ refresher: LocalAccountRefresher, articleChanges: ArticleChanges) } final class LocalAccountRefresher { - var delegate: LocalAccountRefresherDelegate? + weak var delegate: LocalAccountRefresherDelegate? var downloadProgress: DownloadProgress { downloadSession.downloadProgress } diff --git a/Modules/Account/Sources/Account/URLRequest+Account.swift b/Modules/Account/Sources/Account/URLRequest+Account.swift index b82726567..9af79cc7b 100755 --- a/Modules/Account/Sources/Account/URLRequest+Account.swift +++ b/Modules/Account/Sources/Account/URLRequest+Account.swift @@ -22,9 +22,9 @@ public extension URLRequest { switch credentials.type { case .basic: - let data = "\(credentials.username):\(credentials.secret)".data(using: .utf8) - let base64 = data?.base64EncodedString() - let auth = "Basic \(base64 ?? "")" + let data = Data("\(credentials.username):\(credentials.secret)".utf8) + let base64 = data.base64EncodedString() + let auth = "Basic \(base64)" setValue(auth, forHTTPHeaderField: HTTPRequestHeader.authorization) case .newsBlurBasic: setValue("application/x-www-form-urlencoded", forHTTPHeaderField: HTTPRequestHeader.contentType) @@ -39,8 +39,8 @@ public extension URLRequest { setValue("\(NewsBlurAPICaller.SessionIdCookie)=\(credentials.secret)", forHTTPHeaderField: "Cookie") httpShouldHandleCookies = true case .readerBasic: - setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") - httpMethod = "POST" + setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + httpMethod = "POST" var postData = URLComponents() postData.queryItems = [ URLQueryItem(name: "Email", value: credentials.username), @@ -48,20 +48,20 @@ public extension URLRequest { ] httpBody = postData.enhancedPercentEncodedQuery?.data(using: .utf8) case .readerAPIKey: - let auth = "GoogleLogin auth=\(credentials.secret)" - setValue(auth, forHTTPHeaderField: HTTPRequestHeader.authorization) + let auth = "GoogleLogin auth=\(credentials.secret)" + setValue(auth, forHTTPHeaderField: HTTPRequestHeader.authorization) case .oauthAccessToken: - let auth = "OAuth \(credentials.secret)" - setValue(auth, forHTTPHeaderField: "Authorization") - case .oauthAccessTokenSecret: - assertionFailure("Token secrets are used by OAuth1. Did you mean to use `OAuthSwift` instead of a URLRequest?") - case .oauthRefreshToken: - // While both access and refresh tokens are credentials, it seems the `Credentials` cases - // enumerates how the identity of the user can be proved rather than - // credentials-in-general, such as in this refresh token case, - // the authority to prove an identity. - assertionFailure("Refresh tokens are used to replace expired access tokens. Did you mean to use `accessToken` instead?") - } + let auth = "OAuth \(credentials.secret)" + setValue(auth, forHTTPHeaderField: "Authorization") + case .oauthAccessTokenSecret: + assertionFailure("Token secrets are used by OAuth1. Did you mean to use `OAuthSwift` instead of a URLRequest?") + case .oauthRefreshToken: + // While both access and refresh tokens are credentials, it seems the `Credentials` cases + // enumerates how the identity of the user can be proved rather than + // credentials-in-general, such as in this refresh token case, + // the authority to prove an identity. + assertionFailure("Refresh tokens are used to replace expired access tokens. Did you mean to use `accessToken` instead?") + } guard let conditionalGet = conditionalGet else { return @@ -75,7 +75,5 @@ public extension URLRequest { if let etag = conditionalGet.etag { setValue(etag, forHTTPHeaderField: HTTPRequestHeader.ifNoneMatch) } - } - } diff --git a/Modules/Parser/Sources/Parser/FeedParser/Feeds/JSON/JSONFeedParser.swift b/Modules/Parser/Sources/Parser/FeedParser/Feeds/JSON/JSONFeedParser.swift index 2a55c6992..774f465e4 100644 --- a/Modules/Parser/Sources/Parser/FeedParser/Feeds/JSON/JSONFeedParser.swift +++ b/Modules/Parser/Sources/Parser/FeedParser/Feeds/JSON/JSONFeedParser.swift @@ -55,7 +55,7 @@ public struct JSONFeedParser { throw FeedParserError(.invalidJSON) } - guard let version = d[Key.version] as? String, let _ = version.range(of: JSONFeedParser.jsonFeedVersionMarker) else { + guard let version = d[Key.version] as? String, version.range(of: JSONFeedParser.jsonFeedVersionMarker) != nil else { throw FeedParserError(.jsonFeedVersionNotFound) } guard let itemsArray = d[Key.items] as? JSONArray else { diff --git a/Modules/RSDatabase/Sources/RSDatabase/DatabaseTable.swift b/Modules/RSDatabase/Sources/RSDatabase/DatabaseTable.swift index e8d3a2cc0..0d11f40c7 100644 --- a/Modules/RSDatabase/Sources/RSDatabase/DatabaseTable.swift +++ b/Modules/RSDatabase/Sources/RSDatabase/DatabaseTable.swift @@ -108,7 +108,7 @@ public extension DatabaseTable { func containsColumn(_ columnName: String, in database: FMDatabase) -> Bool { if let resultSet = database.executeQuery("select * from \(name) limit 1;", withArgumentsIn: nil) { if let columnMap = resultSet.columnNameToIndexMap { - if let _ = columnMap[columnName.lowercased()] { + if columnMap[columnName.lowercased()] != nil { return true } } diff --git a/Modules/RSDatabase/Sources/RSDatabase/Related Objects/DatabaseRelatedObjectsTable.swift b/Modules/RSDatabase/Sources/RSDatabase/Related Objects/DatabaseRelatedObjectsTable.swift index ec97ac95c..c976e403c 100644 --- a/Modules/RSDatabase/Sources/RSDatabase/Related Objects/DatabaseRelatedObjectsTable.swift +++ b/Modules/RSDatabase/Sources/RSDatabase/Related Objects/DatabaseRelatedObjectsTable.swift @@ -67,7 +67,7 @@ public extension DatabaseRelatedObjectsTable { // Objects in cache must already exist in database. Filter them out. let objectsToSave = objects.filter { (object) -> Bool in - if let _ = cache[object.databaseID] { + if cache[object.databaseID] != nil { return false } return true diff --git a/Modules/RSWeb/Sources/RSWeb/DownloadProgress.swift b/Modules/RSWeb/Sources/RSWeb/DownloadProgress.swift index 3dae384ad..d0ff324e2 100755 --- a/Modules/RSWeb/Sources/RSWeb/DownloadProgress.swift +++ b/Modules/RSWeb/Sources/RSWeb/DownloadProgress.swift @@ -59,26 +59,26 @@ public final class DownloadProgress { public func addToNumberOfTasks(_ n: Int) { assert(Thread.isMainThread) - numberOfTasks = numberOfTasks + n + numberOfTasks += n } public func addToNumberOfTasksAndRemaining(_ n: Int) { assert(Thread.isMainThread) - numberOfTasks = numberOfTasks + n - numberRemaining = numberRemaining + n + numberOfTasks += n + numberRemaining += n } public func completeTask() { assert(Thread.isMainThread) if numberRemaining > 0 { - numberRemaining = numberRemaining - 1 + numberRemaining -= 1 } } public func completeTasks(_ tasks: Int) { assert(Thread.isMainThread) if numberRemaining >= tasks { - numberRemaining = numberRemaining - tasks + numberRemaining -= tasks } } diff --git a/Modules/RSWeb/Sources/RSWeb/DownloadSession.swift b/Modules/RSWeb/Sources/RSWeb/DownloadSession.swift index 91e9ed5c2..8e8cf007b 100755 --- a/Modules/RSWeb/Sources/RSWeb/DownloadSession.swift +++ b/Modules/RSWeb/Sources/RSWeb/DownloadSession.swift @@ -12,7 +12,7 @@ import os // Create a DownloadSessionDelegate, then create a DownloadSession. // To download things: call download with a set of URLs. DownloadSession will call the various delegate methods. -public protocol DownloadSessionDelegate { +public protocol DownloadSessionDelegate: AnyObject { func downloadSession(_ downloadSession: DownloadSession, conditionalGetInfoFor: URL) -> HTTPConditionalGetInfo? func downloadSession(_ downloadSession: DownloadSession, downloadDidComplete: URL, response: URLResponse?, data: Data, error: NSError?) @@ -246,7 +246,7 @@ private extension DownloadSession { updateDownloadProgress() } - func urlStringIsBlackListedRedirect(_ urlString: String) -> Bool { + func urlStringIsDisallowedRedirect(_ urlString: String) -> Bool { // Hotels and similar often do permanent redirects. We can catch some of those. @@ -263,7 +263,7 @@ private extension DownloadSession { } func cacheRedirect(_ oldURL: URL, _ newURL: URL) { - if urlStringIsBlackListedRedirect(newURL.absoluteString) { + if urlStringIsDisallowedRedirect(newURL.absoluteString) { return } redirectCache[oldURL] = newURL diff --git a/Modules/RSWeb/Sources/RSWeb/Reachability.swift b/Modules/RSWeb/Sources/RSWeb/Reachability.swift index 3bc06b1a1..b3c59dc19 100644 --- a/Modules/RSWeb/Sources/RSWeb/Reachability.swift +++ b/Modules/RSWeb/Sources/RSWeb/Reachability.swift @@ -86,17 +86,13 @@ public class Reachability { fileprivate let reachabilitySerialQueue: DispatchQueue fileprivate(set) var flags: SCNetworkReachabilityFlags? - required public init(reachabilityRef: SCNetworkReachability, - queueQoS: DispatchQoS = .default, - targetQueue: DispatchQueue? = nil) { + required public init(reachabilityRef: SCNetworkReachability, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) { self.allowsCellularConnection = true self.reachabilityRef = reachabilityRef self.reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", qos: queueQoS, target: targetQueue) } - public convenience init(hostname: String, - queueQoS: DispatchQoS = .default, - targetQueue: DispatchQueue? = nil) throws { + public convenience init(hostname: String, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) throws { guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { throw ReachabilityError.failedToCreateWithHostname(hostname, SCError()) } @@ -192,16 +188,16 @@ extension SCNetworkReachabilityFlags { } var description: String { - let W = isOnWWANFlagSet ? "W" : "-" - let R = isReachableFlagSet ? "R" : "-" - let c = isConnectionRequiredFlagSet ? "c" : "-" - let t = isTransientConnectionFlagSet ? "t" : "-" - let i = isInterventionRequiredFlagSet ? "i" : "-" - let C = isConnectionOnTrafficFlagSet ? "C" : "-" - let D = isConnectionOnDemandFlagSet ? "D" : "-" - let l = isLocalAddressFlagSet ? "l" : "-" - let d = isDirectFlagSet ? "d" : "-" + let onWWANFlagSet = isOnWWANFlagSet ? "W" : "-" + let reachableFlagSet = isReachableFlagSet ? "R" : "-" + let connectionRequiredFlagSet = isConnectionRequiredFlagSet ? "c" : "-" + let transientConnectionFlagSet = isTransientConnectionFlagSet ? "t" : "-" + let interventionRequiredFlagSet = isInterventionRequiredFlagSet ? "i" : "-" + let connectionOnTrafficFlagSet = isConnectionOnTrafficFlagSet ? "C" : "-" + let connectionOnDemandFlagSet = isConnectionOnDemandFlagSet ? "D" : "-" + let localAddressFlagSet = isLocalAddressFlagSet ? "l" : "-" + let directFlagSet = isDirectFlagSet ? "d" : "-" - return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" + return "\(onWWANFlagSet)\(reachableFlagSet) \(connectionRequiredFlagSet)\(transientConnectionFlagSet)\(interventionRequiredFlagSet)\(connectionOnTrafficFlagSet)\(connectionOnDemandFlagSet)\(localAddressFlagSet)\(directFlagSet)" } }