From 9a5e30e794fcb1ebc49d4b2c693095508c2b253d Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Tue, 23 May 2017 17:38:02 +0200 Subject: [PATCH] Split cache into read cache and write cache --- CHANGELOG.md | 2 + .../Models/LinterCache.swift | 73 ++++++++++++++----- .../LinterCacheTests.swift | 11 +-- 3 files changed, 60 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fd566a77f..e8478450a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,9 @@ * Cache linter results for files unmodified since the previous linter run. [Victor Pimentel](https://github.com/victorpimentel) [JP Simard](https://github.com/jpsim) + [Marcelo Fabri](https://github.com/marcelofabri) [#1184](https://github.com/realm/SwiftLint/issues/1184) + [#1550](https://github.com/realm/SwiftLint/issues/1550) * Add opt-in configurations to `generic_type_name`, `identifier_name` and `type_name` rules to allow excluding non-alphanumeric characters and names diff --git a/Source/SwiftLintFramework/Models/LinterCache.swift b/Source/SwiftLintFramework/Models/LinterCache.swift index dc1edd23a0..6c47dc659d 100644 --- a/Source/SwiftLintFramework/Models/LinterCache.swift +++ b/Source/SwiftLintFramework/Models/LinterCache.swift @@ -15,30 +15,47 @@ internal enum LinterCacheError: Error { } public final class LinterCache { - private var cache = [String: Any]() + private typealias Cache = [String: [String: [String: Any]]] + + private let readCache: Cache + private var writeCache = Cache() private let lock = NSLock() - internal lazy var fileManager: LintableFileManager = FileManager.default + internal let fileManager: LintableFileManager private let location: URL? - internal init() { + internal init(fileManager: LintableFileManager = FileManager.default) { location = nil + self.fileManager = fileManager + self.readCache = [:] } - internal init(cache: Any) throws { - guard let dictionary = cache as? [String: Any] else { + internal init(cache: Any, fileManager: LintableFileManager = FileManager.default) throws { + guard let dictionary = cache as? Cache else { throw LinterCacheError.invalidFormat } - self.cache = dictionary + self.readCache = dictionary location = nil + self.fileManager = fileManager } - public init(configuration: Configuration) { + public init(configuration: Configuration, + fileManager: LintableFileManager = FileManager.default) { location = configuration.cacheURL if let data = try? Data(contentsOf: location!), - let json = try? JSONSerialization.jsonObject(with: data) { - cache = (json as? [String: Any]) ?? [:] + let json = try? JSONSerialization.jsonObject(with: data), + let cache = json as? Cache { + readCache = cache + } else { + readCache = [:] } + self.fileManager = fileManager + } + + private init(cache: Cache, location: URL?, fileManager: LintableFileManager) { + self.readCache = cache + self.location = location + self.fileManager = fileManager } internal func cache(violations: [StyleViolation], forFile file: String, configuration: Configuration) { @@ -49,12 +66,12 @@ public final class LinterCache { let configurationDescription = configuration.cacheDescription lock.lock() - var filesCache = (cache[configurationDescription] as? [String: Any]) ?? [:] + var filesCache = writeCache[configurationDescription] ?? [:] filesCache[file] = [ Key.violations.rawValue: violations.map(dictionary(for:)), Key.lastModification.rawValue: lastModification.timeIntervalSinceReferenceDate ] - cache[configurationDescription] = filesCache + writeCache[configurationDescription] = filesCache lock.unlock() } @@ -65,18 +82,14 @@ public final class LinterCache { let configurationDescription = configuration.cacheDescription - lock.lock() - - guard let filesCache = cache[configurationDescription] as? [String: Any], - let entry = filesCache[file] as? [String: Any], + guard let filesCache = readCache[configurationDescription], + let entry = filesCache[file], let cacheLastModification = entry[Key.lastModification.rawValue] as? TimeInterval, cacheLastModification == lastModification.timeIntervalSinceReferenceDate, let violations = entry[Key.violations.rawValue] as? [[String: Any]] else { - lock.unlock() return nil } - lock.unlock() return violations.flatMap { StyleViolation.from(cache: $0, file: file) } } @@ -84,12 +97,34 @@ public final class LinterCache { guard let url = location else { throw LinterCacheError.noLocation } - lock.lock() + guard !writeCache.isEmpty else { + return + } + + let cache = mergeCaches() let json = toJSON(cache) - lock.unlock() try json.write(to: url, atomically: true, encoding: .utf8) } + internal func flushed() -> LinterCache { + return LinterCache(cache: mergeCaches(), location: location, fileManager: fileManager) + } + + private func mergeCaches() -> Cache { + var cache = readCache + lock.lock() + for (key, value) in writeCache { + var filesCache = cache[key] ?? [:] + for (file, fileCache) in value { + filesCache[file] = fileCache + } + cache[key] = filesCache + } + lock.unlock() + + return cache + } + private func dictionary(for violation: StyleViolation) -> [String: Any] { return [ Key.line.rawValue: violation.location.line ?? NSNull() as Any, diff --git a/Tests/SwiftLintFrameworkTests/LinterCacheTests.swift b/Tests/SwiftLintFrameworkTests/LinterCacheTests.swift index be2813e0b2..c86404ce97 100644 --- a/Tests/SwiftLintFrameworkTests/LinterCacheTests.swift +++ b/Tests/SwiftLintFrameworkTests/LinterCacheTests.swift @@ -76,11 +76,7 @@ class LinterCacheTests: XCTestCase { // MARK: Test Helpers - private var cache: LinterCache = { - let cache = LinterCache() - cache.fileManager = TestFileManager() - return cache - }() + private var cache = LinterCache(fileManager: TestFileManager()) private func makeCacheTestHelper(dict: [String: Any]) -> CacheTestHelper { return CacheTestHelper(dict: dict, cache: cache) @@ -89,8 +85,9 @@ class LinterCacheTests: XCTestCase { private func cacheAndValidate(violations: [StyleViolation], forFile: String, configuration: Configuration, file: StaticString = #file, line: UInt = #line) { cache.cache(violations: violations, forFile: forFile, configuration: configuration) - XCTAssertEqual(cache.violations(forFile: forFile, configuration: configuration)!, violations, - file: file, line: line) + cache = cache.flushed() + XCTAssertEqual(cache.violations(forFile: forFile, configuration: configuration)!, + violations, file: file, line: line) } private func cacheAndValidateNoViolationsTwoFiles(configuration: Configuration,