From c698d021dc017e01b99fa47eb99dfd29320c9cc3 Mon Sep 17 00:00:00 2001 From: Marcelo Fabri Date: Tue, 27 Dec 2016 02:36:33 -0200 Subject: [PATCH] Initial cache implementation --- .../Models/LinterCache.swift | 86 +++++++++++++++++++ .../Models/ViolationSeverity.swift | 4 + Source/swiftlint/Commands/LintCommand.swift | 20 ++++- SwiftLint.xcodeproj/project.pbxproj | 4 + 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 Source/SwiftLintFramework/Models/LinterCache.swift diff --git a/Source/SwiftLintFramework/Models/LinterCache.swift b/Source/SwiftLintFramework/Models/LinterCache.swift new file mode 100644 index 000000000..7b29e7cde --- /dev/null +++ b/Source/SwiftLintFramework/Models/LinterCache.swift @@ -0,0 +1,86 @@ +// +// LinterCache.swift +// SwiftLint +// +// Created by Marcelo Fabri on 12/27/16. +// Copyright © 2016 Realm. All rights reserved. +// + +import Foundation +import SourceKittenFramework + +public struct LinterCache { + private var cache = [String: [String: Any]]() + + public init() { } + + public init(contentsOf url: URL) throws { + let data = try Data(contentsOf: url) + let json = try JSONSerialization.jsonObject(with: data, options: []) + if let json = json as? [String: [String: Any]] { + cache = json + } + } + + public mutating func cacheFile(_ file: String, violations: [StyleViolation], hash: Int) { + + var entry = [String: Any]() + var fileViolations = entry["violations"] as? [[String: Any]] ?? [] + + for violation in violations { + fileViolations.append(dictionaryForViolation(violation)) + } + + entry["violations"] = fileViolations + entry["hash"] = hash + cache[file] = entry + } + + public func violations(for file: String, hash: Int) -> [StyleViolation]? { + guard let entry = cache[file], + let cacheHash = entry["hash"] as? Int, + cacheHash == hash, + let violations = entry["violations"] as? [[String: Any]] else { + return nil + } + + return violations.flatMap { StyleViolation.fromCache($0, file: file) } + } + + public func save(to url: URL) throws { + let json = toJSON(cache) + try json.write(to: url, atomically: true, encoding: .utf8) + } + + private func dictionaryForViolation(_ violation: StyleViolation) -> [String: Any] { + return [ + "line": violation.location.line ?? NSNull() as Any, + "character": violation.location.character ?? NSNull() as Any, + "severity": violation.severity.rawValue, + "type": violation.ruleDescription.name, + "rule_id": violation.ruleDescription.identifier, + "reason": violation.reason + ] + } +} + +extension StyleViolation { + fileprivate static func fromCache(_ cache: [String: Any], file: String) -> StyleViolation? { + guard let severity = (cache["severity"] as? String).flatMap(ViolationSeverity.init), + let name = cache["type"] as? String, + let ruleId = cache["rule_id"] as? String, + let reason = cache["reason"] as? String else { + return nil + } + + let line = cache["line"] as? Int + let character = cache["character"] as? Int + + let ruleDescription = RuleDescription(identifier: ruleId, name: name, description: reason) + let location = Location(file: file, line: line, character: character) + let violation = StyleViolation(ruleDescription: ruleDescription, severity: severity, + location: location, reason: reason) + + return violation + } +} diff --git a/Source/SwiftLintFramework/Models/ViolationSeverity.swift b/Source/SwiftLintFramework/Models/ViolationSeverity.swift index 010c5b935..9f6f0c8ae 100644 --- a/Source/SwiftLintFramework/Models/ViolationSeverity.swift +++ b/Source/SwiftLintFramework/Models/ViolationSeverity.swift @@ -9,6 +9,10 @@ public enum ViolationSeverity: String, Comparable { case warning case error + + public init?(identifier: String) { + self.init(rawValue: identifier) + } } // MARK: Comparable diff --git a/Source/swiftlint/Commands/LintCommand.swift b/Source/swiftlint/Commands/LintCommand.swift index 8bc4b0e50..c72aea699 100644 --- a/Source/swiftlint/Commands/LintCommand.swift +++ b/Source/swiftlint/Commands/LintCommand.swift @@ -40,12 +40,23 @@ struct LintCommand: CommandProtocol { let reporter = reporterFromString( options.reporter.isEmpty ? configuration.reporter : options.reporter ) + + let cacheUrl = URL(fileURLWithPath: "swiftlint.json") + var cache = (try? LinterCache(contentsOf: cacheUrl)) ?? LinterCache() return configuration.visitLintableFiles(options.path, action: "Linting", useSTDIN: options.useSTDIN, quiet: options.quiet, useScriptInputFiles: options.useScriptInputFiles) { linter in var currentViolations: [StyleViolation] = [] + var readFromCache = false + var fileHash: Int? autoreleasepool { - if options.benchmark { + if let file = linter.file.path, + case let hash = linter.file.contents.hash, + let cachedViolations = cache.violations(for: file, hash: hash) { + currentViolations = cachedViolations + readFromCache = true + fileHash = hash + } else if options.benchmark { let start = Date() let (_currentViolations, currentRuleTimes) = linter.styleViolationsAndRuleTimes currentViolations = _currentViolations @@ -58,6 +69,10 @@ struct LintCommand: CommandProtocol { } violations += currentViolations reporter.reportViolations(currentViolations, realtimeCondition: true) + if !readFromCache, let file = linter.file.path { + let hash = fileHash ?? linter.file.contents.hash + cache.cacheFile(file, violations: currentViolations, hash: hash) + } }.flatMap { files in if isWarningThresholdBroken(configuration, violations: violations) { violations.append(createThresholdViolation(configuration.warningThreshold!)) @@ -73,6 +88,9 @@ struct LintCommand: CommandProtocol { fileBenchmark.save() ruleBenchmark.save() } + + try? cache.save(to: cacheUrl) + if numberOfSeriousViolations > 0 { exit(2) } else if options.strict && !violations.isEmpty { diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 624bc6c1f..98bcd7b96 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -125,6 +125,7 @@ D4C4A3521DEFBBB700E0E04C /* FileHeaderConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4C4A3511DEFBBB700E0E04C /* FileHeaderConfiguration.swift */; }; D4DAE8BC1DE14E8F00B0AE7A /* NimbleOperatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4DAE8BB1DE14E8F00B0AE7A /* NimbleOperatorRule.swift */; }; D4FBADD01E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4FBADCF1E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift */; }; + D4FD58B21E12A0200019503C /* LinterCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4FD58B11E12A0200019503C /* LinterCache.swift */; }; DAD3BE4A1D6ECD9500660239 /* PrivateOutletRuleConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAD3BE491D6ECD9500660239 /* PrivateOutletRuleConfiguration.swift */; }; E315B83C1DFA4BC500621B44 /* DynamicInlineRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E315B83B1DFA4BC500621B44 /* DynamicInlineRule.swift */; }; E57B23C11B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E57B23C01B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift */; }; @@ -375,6 +376,7 @@ D4C4A3511DEFBBB700E0E04C /* FileHeaderConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHeaderConfiguration.swift; sourceTree = ""; }; D4DAE8BB1DE14E8F00B0AE7A /* NimbleOperatorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NimbleOperatorRule.swift; sourceTree = ""; }; D4FBADCF1E00DA0400669C73 /* OperatorUsageWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorUsageWhitespaceRule.swift; sourceTree = ""; }; + D4FD58B11E12A0200019503C /* LinterCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinterCache.swift; sourceTree = ""; }; DAD3BE491D6ECD9500660239 /* PrivateOutletRuleConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivateOutletRuleConfiguration.swift; sourceTree = ""; }; E315B83B1DFA4BC500621B44 /* DynamicInlineRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicInlineRule.swift; sourceTree = ""; }; E57B23C01B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReturnArrowWhitespaceRule.swift; sourceTree = ""; }; @@ -834,6 +836,7 @@ E8B67C3D1C095E6300FDED8E /* Correction.swift */, E809EDA01B8A71DF00399043 /* Configuration.swift */, E812249B1B04FADC001783D2 /* Linter.swift */, + D4FD58B11E12A0200019503C /* LinterCache.swift */, E88DEA6E1B09843F00A66CB0 /* Location.swift */, E80E018E1B92C1350078EB70 /* Region.swift */, 83D71E261B131EB5000395DE /* RuleDescription.swift */, @@ -1186,6 +1189,7 @@ E88DEA711B09847500A66CB0 /* ViolationSeverity.swift in Sources */, B2902A0C1D66815600BFCCF7 /* PrivateUnitTestRule.swift in Sources */, D47A51101DB2DD4800A4CC21 /* AttributesRule.swift in Sources */, + D4FD58B21E12A0200019503C /* LinterCache.swift in Sources */, 3BD9CD3D1C37175B009A5D25 /* YamlParser.swift in Sources */, F22314B01D4FA4D7009AD165 /* LegacyNSGeometryFunctionsRule.swift in Sources */, E88DEA8C1B0999A000A66CB0 /* ASTRule.swift in Sources */,