Compare commits

...

9 Commits

Author SHA1 Message Date
JP Simard da7ef33c38
simplify RegexConfiguration.cacheDescription 2017-05-16 13:06:06 -07:00
JP Simard 41509fe7f0
re-enable cache when running lint command
by respecting the value of ignoreCache rather than always passing 'true'
2017-05-16 11:56:45 -07:00
JP Simard 07861f2caa
add CacheDescriptionProvider protocol
to provide more complete descriptions for cache invalidation
purposes.
2017-05-16 11:56:45 -07:00
JP Simard b4854f08f3
derive Configuration.cacheDescription in all cases
rather than just when using one of many code paths in one of many
initializers
2017-05-16 11:56:45 -07:00
JP Simard 286c8403f8
add LinterCache.Key enum
rather than use hardcoded, duplicate string literals
2017-05-16 11:56:45 -07:00
JP Simard b50e0ce4b9
simplify Configuration.swift's usage of its default file manager 2017-05-16 11:56:45 -07:00
JP Simard 63c6b602e4
simplify FileManager.modificationDate(forFileAtPath:) impl 2017-05-16 11:56:45 -07:00
JP Simard 48834fb887
move changelog entries to appropriate section
applying minor edits.

Remove `configurationDescription` from breaking changes
in changelog since additions aren't breaking changes.
2017-05-16 11:56:45 -07:00
Víctor Pimentel ab3b57bb0d
Change LinterCache to use file modification dates instead of hashing files with a weak algorithm. 2017-05-16 11:56:45 -07:00
15 changed files with 242 additions and 71 deletions

View File

@ -11,12 +11,31 @@
[Marcelo Fabri](https://github.com/marcelofabri)
[#1387](https://github.com/realm/SwiftLint/issues/1453)
* Add `modificationDate(forFileAtPath:)` function requirement to
`LintableFileManager` protocol.
[Victor Pimentel](https://github.com/victorpimentel)
* Replace `configurationHash` parameters in all `LinterCache` initializers
with `configurationDescription` parameters.
[Victor Pimentel](https://github.com/victorpimentel)
* Remove `fileHash` parameter from `LinterCache.cache(violations:forFile:)`
and `LinterCache.violations(forFile:)` functions.
[Victor Pimentel](https://github.com/victorpimentel)
* Remove `hash` attribute from `Configuration` struct.
[Victor Pimentel](https://github.com/victorpimentel)
* Rename `ConditionalReturnsOnNewline` struct to
`ConditionalReturnsOnNewlineRule` to match rule naming conventions.
[JP Simard](https://github.com/jpsim)
##### Enhancements
* Cache linter results for files unmodified since the previous linter run.
[Victor Pimentel](https://github.com/victorpimentel)
[#1184](https://github.com/realm/SwiftLint/issues/1184)
* Add opt-in configurations to `generic_type_name`, `identifier_name` and
`type_name` rules to allow excluding non-alphanumeric characters and names
that start with uppercase.

View File

@ -10,6 +10,7 @@ import Foundation
public protocol LintableFileManager {
func filesToLint(inPath: String, rootDirectory: String?) -> [String]
func modificationDate(forFileAtPath: String) -> Date?
}
extension FileManager: LintableFileManager {
@ -31,4 +32,8 @@ extension FileManager: LintableFileManager {
return nil
} ?? []
}
public func modificationDate(forFileAtPath path: String) -> Date? {
return (try? attributesOfItem(atPath: path))?[.modificationDate] as? Date
}
}

View File

@ -9,8 +9,6 @@
import Foundation
import SourceKittenFramework
private let fileManager = FileManager.default
private enum ConfigurationKey: String {
case cachePath = "cache_path"
case disabledRules = "disabled_rules"
@ -34,9 +32,25 @@ public struct Configuration: Equatable {
public let rules: [Rule]
public var rootPath: String? // the root path to search for nested configurations
public var configurationPath: String? // if successfully loaded from a path
public var hash: Int?
public let cachePath: String?
public var cacheDescription: String {
let cacheRulesDescriptions: [String: Any] = rules.reduce([:]) { accu, element in
var accu = accu
accu[type(of: element).description.identifier] = element.cacheDescription
return accu
}
let dict: [String: Any] = [
"root": rootPath ?? FileManager.default.currentDirectoryPath,
"rules": cacheRulesDescriptions
]
if let jsonData = try? JSONSerialization.data(withJSONObject: dict),
let jsonString = String(data: jsonData, encoding: .utf8) {
return jsonString
}
fatalError("Could not serialize configuration for cache")
}
public init?(disabledRules: [String] = [],
optInRules: [String] = [],
enableAllRules: Bool = false,
@ -176,7 +190,6 @@ public struct Configuration: Equatable {
}
self.init(dict: dict, enableAllRules: enableAllRules)!
configurationPath = fullPath
hash = dict.description.hashValue
self.rootPath = rootPath
return
} catch YamlParserError.yamlParsing(let message) {
@ -192,7 +205,7 @@ public struct Configuration: Equatable {
optional: !CommandLine.arguments.contains("--config"), quiet: quiet, enableAllRules: enableAllRules)
}
public func lintablePaths(inPath path: String, fileManager: LintableFileManager = fileManager) -> [String] {
public func lintablePaths(inPath path: String, fileManager: LintableFileManager = FileManager.default) -> [String] {
// If path is a Swift file, skip filtering with excluded/included paths
if path.bridge().isSwiftFile() && path.isFile {
return [path]

View File

@ -85,8 +85,7 @@ public struct Linter {
}
if let cache = cache, let path = file.path {
let hash = file.contents.hash
cache.cache(violations: violations, forFile: path, fileHash: hash)
cache.cache(violations: violations, forFile: path)
}
for (deprecatedIdentifier, identifier) in deprecatedToValidIdentifier {
@ -99,11 +98,8 @@ public struct Linter {
private func cachedStyleViolations(benchmark: Bool = false) -> ([StyleViolation], [(id: String, time: Double)])? {
let start: Date! = benchmark ? Date() : nil
guard let cache = cache,
let file = file.path,
case let hash = self.file.contents.hash,
let cachedViolations = cache.violations(forFile: file, hash: hash) else {
return nil
guard let cache = cache, let file = file.path, let cachedViolations = cache.violations(forFile: file) else {
return nil
}
var ruleTimes = [(id: String, time: Double)]()

View File

@ -18,25 +18,26 @@ public enum LinterCacheError: Error {
public final class LinterCache {
private var cache: [String: Any]
private let lock = NSLock()
internal lazy var fileManager: LintableFileManager = FileManager.default
public init(currentVersion: Version = .current, configurationHash: Int? = nil) {
public init(currentVersion: Version = .current, configurationDescription: String? = nil) {
cache = [
"version": currentVersion.value,
"files": [:]
Key.version.rawValue: currentVersion.value,
Key.files.rawValue: [:]
]
cache["configuration_hash"] = configurationHash
cache[Key.configuration.rawValue] = configurationDescription
}
public init(cache: Any, currentVersion: Version = .current, configurationHash: Int? = nil) throws {
public init(cache: Any, currentVersion: Version = .current, configurationDescription: String? = nil) throws {
guard let dictionary = cache as? [String: Any] else {
throw LinterCacheError.invalidFormat
}
guard let version = dictionary["version"] as? String, version == currentVersion.value else {
guard dictionary[Key.version.rawValue] as? String == currentVersion.value else {
throw LinterCacheError.differentVersion
}
if dictionary["configuration_hash"] as? Int != configurationHash {
guard dictionary[Key.configuration.rawValue] as? String == configurationDescription else {
throw LinterCacheError.differentConfiguration
}
@ -44,32 +45,40 @@ public final class LinterCache {
}
public convenience init(contentsOf url: URL, currentVersion: Version = .current,
configurationHash: Int? = nil) throws {
configurationDescription: String? = nil) throws {
let data = try Data(contentsOf: url)
let json = try JSONSerialization.jsonObject(with: data, options: [])
try self.init(cache: json, currentVersion: currentVersion,
configurationHash: configurationHash)
configurationDescription: configurationDescription)
}
public func cache(violations: [StyleViolation], forFile file: String, fileHash: Int) {
public func cache(violations: [StyleViolation], forFile file: String) {
guard let lastModification = fileManager.modificationDate(forFileAtPath: file) else {
return
}
lock.lock()
var filesCache = (cache["files"] as? [String: Any]) ?? [:]
var filesCache = (cache[Key.files.rawValue] as? [String: Any]) ?? [:]
filesCache[file] = [
"violations": violations.map(dictionary(for:)),
"hash": fileHash
Key.violations.rawValue: violations.map(dictionary(for:)),
Key.lastModification.rawValue: lastModification.timeIntervalSinceReferenceDate
]
cache["files"] = filesCache
cache[Key.files.rawValue] = filesCache
lock.unlock()
}
public func violations(forFile file: String, hash: Int) -> [StyleViolation]? {
public func violations(forFile file: String) -> [StyleViolation]? {
guard let lastModification = fileManager.modificationDate(forFileAtPath: file) else {
return nil
}
lock.lock()
guard let filesCache = cache["files"] as? [String: Any],
guard let filesCache = cache[Key.files.rawValue] as? [String: Any],
let entry = filesCache[file] as? [String: Any],
let cacheHash = entry["hash"] as? Int,
cacheHash == hash,
let violations = entry["violations"] as? [[String: Any]] else {
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
}
@ -87,29 +96,46 @@ public final class LinterCache {
private func dictionary(for 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
Key.line.rawValue: violation.location.line ?? NSNull() as Any,
Key.character.rawValue: violation.location.character ?? NSNull() as Any,
Key.severity.rawValue: violation.severity.rawValue,
Key.type.rawValue: violation.ruleDescription.name,
Key.ruleID.rawValue: violation.ruleDescription.identifier,
Key.reason.rawValue: violation.reason
]
}
}
extension LinterCache {
fileprivate enum Key: String {
case character
case configuration
case files
case lastModification = "last_modification"
case line
case reason
case ruleID = "rule_id"
case severity
case type
case version
case violations
}
}
extension StyleViolation {
fileprivate static func from(cache: [String: Any], file: String) -> StyleViolation? {
guard let severity = (cache["severity"] as? String).flatMap({ ViolationSeverity(rawValue: $0) }),
let name = cache["type"] as? String,
let ruleId = cache["rule_id"] as? String,
let reason = cache["reason"] as? String else {
guard let severityString = (cache[LinterCache.Key.severity.rawValue] as? String),
let severity = ViolationSeverity(rawValue: severityString),
let name = cache[LinterCache.Key.type.rawValue] as? String,
let ruleID = cache[LinterCache.Key.ruleID.rawValue] as? String,
let reason = cache[LinterCache.Key.reason.rawValue] as? String else {
return nil
}
let line = cache["line"] as? Int
let character = cache["character"] as? Int
let line = cache[LinterCache.Key.line.rawValue] as? Int
let character = cache[LinterCache.Key.character.rawValue] as? Int
let ruleDescription = RuleDescription(identifier: ruleId, name: name, description: reason)
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)

View File

@ -0,0 +1,11 @@
//
// CacheDescriptionProvider.swift
// SwiftLint
//
// Created by JP Simard on 5/16/17.
// Copyright © 2017 Realm. All rights reserved.
//
internal protocol CacheDescriptionProvider {
var cacheDescription: String { get }
}

View File

@ -21,6 +21,10 @@ extension Rule {
public func isEqualTo(_ rule: Rule) -> Bool {
return type(of: self).description == type(of: rule).description
}
internal var cacheDescription: String {
return (self as? CacheDescriptionProvider)?.cacheDescription ?? configurationDescription
}
}
public protocol OptInRule: Rule {}

View File

@ -6,14 +6,18 @@
// Copyright © 2016 Realm. All rights reserved.
//
import Foundation
public protocol RuleConfiguration {
mutating func apply(configuration: Any) throws
func isEqualTo(_ ruleConfiguration: RuleConfiguration) -> Bool
var consoleDescription: String { get }
}
extension RuleConfiguration {
internal var cacheDescription: String {
return (self as? CacheDescriptionProvider)?.cacheDescription ?? consoleDescription
}
}
extension RuleConfiguration where Self: Equatable {
public func isEqualTo(_ ruleConfiguration: RuleConfiguration) -> Bool {
return self == ruleConfiguration as? Self

View File

@ -17,8 +17,11 @@ private extension Region {
// MARK: - CustomRulesConfiguration
public struct CustomRulesConfiguration: RuleConfiguration, Equatable {
public struct CustomRulesConfiguration: RuleConfiguration, Equatable, CacheDescriptionProvider {
public var consoleDescription: String { return "user-defined" }
internal var cacheDescription: String {
return customRuleConfigurations.map({ $0.cacheDescription }).joined(separator: "\n")
}
public var customRuleConfigurations = [RegexConfiguration]()
public init() {}
@ -42,7 +45,11 @@ public func == (lhs: CustomRulesConfiguration, rhs: CustomRulesConfiguration) ->
// MARK: - CustomRules
public struct CustomRules: Rule, ConfigurationProviderRule {
public struct CustomRules: Rule, ConfigurationProviderRule, CacheDescriptionProvider {
internal var cacheDescription: String {
return configuration.cacheDescription
}
public static let description = RuleDescription(
identifier: "custom_rules",

View File

@ -9,7 +9,7 @@
import Foundation
import SourceKittenFramework
public struct RegexConfiguration: RuleConfiguration, Equatable {
public struct RegexConfiguration: RuleConfiguration, Equatable, CacheDescriptionProvider {
public let identifier: String
public var name: String?
public var message = "Regex matched."
@ -27,6 +27,23 @@ public struct RegexConfiguration: RuleConfiguration, Equatable {
return "\(severity.rawValue): \(regex.pattern)"
}
internal var cacheDescription: String {
var dict = [String: Any]()
dict["identifier"] = identifier
dict["name"] = name
dict["message"] = message
dict["regex"] = regex.pattern
dict["included"] = included?.pattern
dict["excluded"] = excluded?.pattern
dict["match_kinds"] = matchKinds.map { $0.rawValue }
dict["severity"] = severityConfiguration.consoleDescription
if let jsonData = try? JSONSerialization.data(withJSONObject: dict),
let jsonString = String(data: jsonData, encoding: .utf8) {
return jsonString
}
fatalError("Could not serialize regex configuration for cache")
}
public var description: RuleDescription {
return RuleDescription(identifier: identifier, name: name ?? identifier, description: "")
}

View File

@ -140,7 +140,7 @@ struct LintOptions: OptionsProtocol {
// swiftlint:disable line_length
static func create(_ path: String) -> (_ useSTDIN: Bool) -> (_ configurationFile: String) -> (_ strict: Bool) -> (_ lenient: Bool) -> (_ useScriptInputFiles: Bool) -> (_ benchmark: Bool) -> (_ reporter: String) -> (_ quiet: Bool) -> (_ cachePath: String) -> (_ ignoreCache: Bool) -> (_ enableAllRules: Bool) -> LintOptions {
return { useSTDIN in { configurationFile in { strict in { lenient in { useScriptInputFiles in { benchmark in { reporter in { quiet in { cachePath in { ignoreCache in { enableAllRules in
self.init(path: path, useSTDIN: useSTDIN, configurationFile: configurationFile, strict: strict, lenient: lenient, useScriptInputFiles: useScriptInputFiles, benchmark: benchmark, reporter: reporter, quiet: quiet, cachePath: cachePath, ignoreCache: true, enableAllRules: enableAllRules)
self.init(path: path, useSTDIN: useSTDIN, configurationFile: configurationFile, strict: strict, lenient: lenient, useScriptInputFiles: useScriptInputFiles, benchmark: benchmark, reporter: reporter, quiet: quiet, cachePath: cachePath, ignoreCache: ignoreCache, enableAllRules: enableAllRules)
}}}}}}}}}}}
}

View File

@ -16,12 +16,12 @@ extension LinterCache {
return nil
}
let configurationHash = configuration.hash
let configurationDescription = configuration.cacheDescription
let cache: LinterCache
do {
cache = try LinterCache(contentsOf: url, configurationHash: configurationHash)
cache = try LinterCache(contentsOf: url, configurationDescription: configurationDescription)
} catch {
cache = LinterCache(configurationHash: configurationHash)
cache = LinterCache(configurationDescription: configurationDescription)
}
return cache
@ -50,7 +50,7 @@ private func defaultCacheURL(options: LintOptions) -> URL {
#if os(Linux)
let baseURL = URL(fileURLWithPath: "/var/tmp/")
#else
let baseURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
let baseURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
#endif
let fileName = String(rootPath.hash) + ".json"

View File

@ -120,8 +120,8 @@
D44254201DB87CA200492EA4 /* ValidIBInspectableRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */; };
D44254271DB9C15C00492EA4 /* SyntacticSugarRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */; };
D4470D571EB69225008A1B2E /* ImplicitReturnRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4470D561EB69225008A1B2E /* ImplicitReturnRule.swift */; };
D4470D5B1EB76F44008A1B2E /* UnusedOptionalBindingRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4470D5A1EB76F44008A1B2E /* UnusedOptionalBindingRuleTests.swift */; };
D4470D591EB6B4D1008A1B2E /* EmptyEnumArgumentsRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4470D581EB6B4D1008A1B2E /* EmptyEnumArgumentsRule.swift */; };
D4470D5B1EB76F44008A1B2E /* UnusedOptionalBindingRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4470D5A1EB76F44008A1B2E /* UnusedOptionalBindingRuleTests.swift */; };
D44AD2761C0AA5350048F7B0 /* LegacyConstructorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44AD2741C0AA3730048F7B0 /* LegacyConstructorRule.swift */; };
D462021F1E15F52D0027AAD1 /* NumberSeparatorRuleExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = D462021E1E15F52D0027AAD1 /* NumberSeparatorRuleExamples.swift */; };
D46252541DF63FB200BE2CA1 /* NumberSeparatorRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = D46252531DF63FB200BE2CA1 /* NumberSeparatorRule.swift */; };
@ -170,6 +170,7 @@
E315B83C1DFA4BC500621B44 /* DynamicInlineRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E315B83B1DFA4BC500621B44 /* DynamicInlineRule.swift */; };
E57B23C11B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = E57B23C01B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift */; };
E802ED001C56A56000A35AE1 /* Benchmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = E802ECFF1C56A56000A35AE1 /* Benchmark.swift */; };
E80746F61ECB722F00548D31 /* CacheDescriptionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E80746F51ECB722F00548D31 /* CacheDescriptionProvider.swift */; };
E809EDA11B8A71DF00399043 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = E809EDA01B8A71DF00399043 /* Configuration.swift */; };
E809EDA31B8A73FB00399043 /* ConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E809EDA21B8A73FB00399043 /* ConfigurationTests.swift */; };
E80E018D1B92C0F60078EB70 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = E80E018C1B92C0F60078EB70 /* Command.swift */; };
@ -409,8 +410,8 @@
D442541E1DB87C3D00492EA4 /* ValidIBInspectableRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidIBInspectableRule.swift; sourceTree = "<group>"; };
D44254251DB9C12300492EA4 /* SyntacticSugarRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyntacticSugarRule.swift; sourceTree = "<group>"; };
D4470D561EB69225008A1B2E /* ImplicitReturnRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImplicitReturnRule.swift; sourceTree = "<group>"; };
D4470D5A1EB76F44008A1B2E /* UnusedOptionalBindingRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedOptionalBindingRuleTests.swift; sourceTree = "<group>"; };
D4470D581EB6B4D1008A1B2E /* EmptyEnumArgumentsRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyEnumArgumentsRule.swift; sourceTree = "<group>"; };
D4470D5A1EB76F44008A1B2E /* UnusedOptionalBindingRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedOptionalBindingRuleTests.swift; sourceTree = "<group>"; };
D44AD2741C0AA3730048F7B0 /* LegacyConstructorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyConstructorRule.swift; sourceTree = "<group>"; };
D462021E1E15F52D0027AAD1 /* NumberSeparatorRuleExamples.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberSeparatorRuleExamples.swift; sourceTree = "<group>"; };
D46252531DF63FB200BE2CA1 /* NumberSeparatorRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberSeparatorRule.swift; sourceTree = "<group>"; };
@ -460,6 +461,7 @@
E57B23C01B1D8BF000DEA512 /* ReturnArrowWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReturnArrowWhitespaceRule.swift; sourceTree = "<group>"; };
E5A167C81B25A0B000CF2D03 /* OperatorFunctionWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorFunctionWhitespaceRule.swift; sourceTree = "<group>"; };
E802ECFF1C56A56000A35AE1 /* Benchmark.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Benchmark.swift; sourceTree = "<group>"; };
E80746F51ECB722F00548D31 /* CacheDescriptionProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheDescriptionProvider.swift; sourceTree = "<group>"; };
E809EDA01B8A71DF00399043 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
E809EDA21B8A73FB00399043 /* ConfigurationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationTests.swift; sourceTree = "<group>"; };
E80E018C1B92C0F60078EB70 /* Command.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; };
@ -935,6 +937,7 @@
isa = PBXGroup;
children = (
E88DEA8B1B0999A000A66CB0 /* ASTRule.swift */,
E80746F51ECB722F00548D31 /* CacheDescriptionProvider.swift */,
E86396C11BADAAE5002C9E88 /* Reporter.swift */,
E88DEA761B098D0C00A66CB0 /* Rule.swift */,
3B828E521C546468000D180E /* RuleConfiguration.swift */,
@ -1330,6 +1333,7 @@
F22314B01D4FA4D7009AD165 /* LegacyNSGeometryFunctionsRule.swift in Sources */,
E88DEA8C1B0999A000A66CB0 /* ASTRule.swift in Sources */,
1E82D5591D7775C7009553D7 /* ClosureSpacingRule.swift in Sources */,
E80746F61ECB722F00548D31 /* CacheDescriptionProvider.swift in Sources */,
094385041D5D4F7C009168CF /* PrivateOutletRule.swift in Sources */,
E88DEA6B1B0983FE00A66CB0 /* StyleViolation.swift in Sources */,
3BB47D831C514E8100AE6A10 /* RegexConfiguration.swift in Sources */,

View File

@ -131,6 +131,10 @@ class ConfigurationTests: XCTestCase {
XCTFail("Should not be called with path \(path)")
return []
}
public func modificationDate(forFileAtPath path: String) -> Date? {
return nil
}
}
func testExcludedPaths() {

View File

@ -12,6 +12,20 @@ import XCTest
class LinterCacheTests: XCTestCase {
private class TestFileManager: LintableFileManager {
func filesToLint(inPath: String, rootDirectory: String? = nil) -> [String] {
return []
}
internal var stubbedModificationDateByPath: [String: Date] = [:]
public func modificationDate(forFileAtPath path: String) -> Date? {
return stubbedModificationDateByPath[path]
}
}
private let fileManager = TestFileManager()
func testInitThrowsWhenUsingDifferentVersion() {
let cache = ["version": "0.1.0"]
checkError(LinterCacheError.differentVersion) {
@ -27,10 +41,10 @@ class LinterCacheTests: XCTestCase {
}
func testInitThrowsWhenUsingDifferentConfiguration() {
let cache = ["version": "0.1.0", "configuration_hash": 1] as [String : Any]
let cache = ["version": "0.1.0", "configuration": "Configuration 1"] as [String : Any]
checkError(LinterCacheError.differentConfiguration) {
_ = try LinterCache(cache: cache, currentVersion: Version(value: "0.1.0"),
configurationHash: 2)
configurationDescription: "Configuration 2")
}
}
@ -40,15 +54,16 @@ class LinterCacheTests: XCTestCase {
XCTAssertNotNil(linterCache)
}
func testInitSucceedsWithConfigurationHash() {
let cache = ["version": "0.2.0", "configuration_hash": 1] as [String : Any]
func testInitSucceedsWithConfiguration() {
let cache = ["version": "0.2.0", "configuration": "Configuration 1"] as [String : Any]
let linterCache = try? LinterCache(cache: cache, currentVersion: Version(value: "0.2.0"),
configurationHash: 1)
configurationDescription: "Configuration 1")
XCTAssertNotNil(linterCache)
}
func testParsesViolations() {
let cache = LinterCache(currentVersion: Version(value: "0.2.0"))
cache.fileManager = fileManager
let file = "foo.swift"
let ruleDescription = RuleDescription(identifier: "rule", name: "Some rule",
description: "Validates stuff")
@ -62,27 +77,72 @@ class LinterCacheTests: XCTestCase {
location: Location(file: file, line: 5, character: nil),
reason: "Something is wrong.")
]
fileManager.stubbedModificationDateByPath[file] = Date()
cache.cache(violations: violations, forFile: file, fileHash: 1)
let cachedViolations = cache.violations(forFile: file, hash: 1)
cache.cache(violations: violations, forFile: file)
let cachedViolations = cache.violations(forFile: file)
XCTAssertNotNil(cachedViolations)
XCTAssertEqual(cachedViolations!, violations)
}
func testParsesViolationsWithModifiedHash() {
func testParsesViolationsWithEmptyViolations() {
let cache = LinterCache(currentVersion: Version(value: "0.2.0"))
cache.fileManager = fileManager
let file = "foo.swift"
cache.cache(violations: [], forFile: file, fileHash: 1)
let cachedViolations = cache.violations(forFile: file, hash: 2)
fileManager.stubbedModificationDateByPath[file] = Date()
let cachedViolations = cache.violations(forFile: file)
XCTAssertNil(cachedViolations)
}
func testParsesViolationsWithEmptyViolations() {
func testParsesViolationsWithNoDate() {
let cache = LinterCache(currentVersion: Version(value: "0.2.0"))
cache.fileManager = fileManager
let file = "foo.swift"
let cachedViolations = cache.violations(forFile: file, hash: 2)
let ruleDescription = RuleDescription(identifier: "rule", name: "Some rule",
description: "Validates stuff")
let violations = [
StyleViolation(ruleDescription: ruleDescription,
severity: .warning,
location: Location(file: file, line: 10, character: 2),
reason: "Something is not right."),
StyleViolation(ruleDescription: ruleDescription,
severity: .error,
location: Location(file: file, line: 5, character: nil),
reason: "Something is wrong.")
]
fileManager.stubbedModificationDateByPath[file] = Date()
cache.cache(violations: violations, forFile: file)
fileManager.stubbedModificationDateByPath[file] = nil
let cachedViolations = cache.violations(forFile: file)
XCTAssertNil(cachedViolations)
}
func testParsesViolationsWithDifferentDate() {
let cache = LinterCache(currentVersion: Version(value: "0.2.0"))
cache.fileManager = fileManager
let file = "foo.swift"
let ruleDescription = RuleDescription(identifier: "rule", name: "Some rule",
description: "Validates stuff")
let violations = [
StyleViolation(ruleDescription: ruleDescription,
severity: .warning,
location: Location(file: file, line: 10, character: 2),
reason: "Something is not right."),
StyleViolation(ruleDescription: ruleDescription,
severity: .error,
location: Location(file: file, line: 5, character: nil),
reason: "Something is wrong.")
]
fileManager.stubbedModificationDateByPath[file] = Date()
cache.cache(violations: violations, forFile: file)
fileManager.stubbedModificationDateByPath[file] = Date()
let cachedViolations = cache.violations(forFile: file)
XCTAssertNil(cachedViolations)
}
@ -95,10 +155,11 @@ extension LinterCacheTests {
("testInitThrowsWhenUsingInvalidCacheFormat", testInitThrowsWhenUsingInvalidCacheFormat),
("testInitThrowsWhenUsingDifferentConfiguration", testInitThrowsWhenUsingDifferentConfiguration),
("testInitSucceeds", testInitSucceeds),
("testInitSucceedsWithConfigurationHash", testInitSucceedsWithConfigurationHash),
("testInitSucceedsWithConfiguration", testInitSucceedsWithConfiguration),
("testParsesViolations", testParsesViolations),
("testParsesViolationsWithModifiedHash", testParsesViolationsWithModifiedHash),
("testParsesViolationsWithEmptyViolations", testParsesViolationsWithEmptyViolations)
("testParsesViolationsWithEmptyViolations", testParsesViolationsWithEmptyViolations),
("testParsesViolationsWithNoDate", testParsesViolationsWithNoDate),
("testParsesViolationsWithDifferentDate", testParsesViolationsWithDifferentDate)
]
}
}