Implement baseline functionality

This commit is contained in:
Przemysław Olszacki
2019-01-21 14:26:05 +01:00
committed by Paul Taykalo
parent ddfba6ea5d
commit 79c30afefa
4 changed files with 109 additions and 5 deletions
@@ -0,0 +1,58 @@
import Foundation
public class Baseline {
private let baselinePath: String
private var baselineViolations = [BaselineViolation]()
public init(baselinePath: String) {
self.baselinePath = baselinePath
}
public func isInBaseline(violation: StyleViolation) -> Bool {
let baselineViolation = BaselineViolation(
ruleIdentifier: violation.ruleIdentifier,
location: violation.location.description,
reason: violation.reason
)
let contains = baselineViolations.contains(baselineViolation)
return contains
}
public func saveBaseline(violations: [StyleViolation]) {
let fileContent = violations.map(generateForSingleViolation).joined(separator: "\n")
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: baselinePath) {
fileManager.createFile(atPath: baselinePath, contents: fileContent.data(using: .utf8))
}
}
public func readBaseline() {
let fileManager = FileManager.default
guard let fileContent = fileManager.contents(atPath: baselinePath),
let stringContent = String(data: fileContent, encoding: .utf8) else {
return
}
stringContent.enumerateLines { [weak self] line, _ in
guard let self = self else { return }
let violation = self.parseLine(line: line)
self.baselineViolations.append(violation)
}
}
private func parseLine(line: String) -> BaselineViolation {
let components = line.components(separatedBy: ";")
let location = components[0]
let reason = components[1]
let ruleIdentifier = components[2]
return BaselineViolation(
ruleIdentifier: ruleIdentifier,
location: location,
reason: reason
)
}
private func generateForSingleViolation(_ violation: StyleViolation) -> String {
return "\(violation.location);\(violation.reason);\(violation.ruleIdentifier)"
}
}
@@ -0,0 +1,13 @@
import Foundation
struct BaselineViolation: Equatable {
let ruleIdentifier: String
let location: String
let reason: String
static func == (lhs: BaselineViolation, rhs: BaselineViolation) -> Bool {
return lhs.ruleIdentifier == rhs.ruleIdentifier &&
lhs.location == rhs.location &&
lhs.reason == rhs.reason
}
}
+8 -4
View File
@@ -24,19 +24,20 @@ struct LintOptions: OptionsProtocol {
let cachePath: String
let ignoreCache: Bool
let enableAllRules: Bool
let useBaseline: Bool
// swiftlint:disable line_length
static func create(_ path: String) -> (_ useSTDIN: Bool) -> (_ configurationFile: String) -> (_ strict: Bool) -> (_ lenient: Bool) -> (_ forceExclude: Bool) -> (_ excludeByPrefix: Bool) -> (_ useScriptInputFiles: Bool) -> (_ benchmark: Bool) -> (_ reporter: String) -> (_ quiet: Bool) -> (_ cachePath: String) -> (_ ignoreCache: Bool) -> (_ enableAllRules: Bool) -> (_ paths: [String]) -> LintOptions {
return { useSTDIN in { configurationFile in { strict in { lenient in { forceExclude in { excludeByPrefix in { useScriptInputFiles in { benchmark in { reporter in { quiet in { cachePath in { ignoreCache in { enableAllRules in { paths in
static func create(_ path: String) -> (_ useSTDIN: Bool) -> (_ configurationFile: String) -> (_ strict: Bool) -> (_ lenient: Bool) -> (_ forceExclude: Bool) -> (_ excludeByPrefix: Bool) -> (_ useScriptInputFiles: Bool) -> (_ benchmark: Bool) -> (_ reporter: String) -> (_ quiet: Bool) -> (_ cachePath: String) -> (_ ignoreCache: Bool) -> (_ enableAllRules: Bool) -> (_ useBaseline: Bool) -> (_ paths: [String]) -> LintOptions {
return { useSTDIN in { configurationFile in { strict in { lenient in { forceExclude in { excludeByPrefix in { useScriptInputFiles in { benchmark in { reporter in { quiet in { cachePath in { ignoreCache in { enableAllRules in { useBaseline in { paths in
let allPaths: [String]
if !path.isEmpty {
allPaths = [path]
} else {
allPaths = paths
}
return self.init(paths: allPaths, useSTDIN: useSTDIN, configurationFile: configurationFile, strict: strict, lenient: lenient, forceExclude: forceExclude, excludeByPrefix: excludeByPrefix, useScriptInputFiles: useScriptInputFiles, benchmark: benchmark, reporter: reporter, quiet: quiet, cachePath: cachePath, ignoreCache: ignoreCache, enableAllRules: enableAllRules)
return self.init(paths: allPaths, useSTDIN: useSTDIN, configurationFile: configurationFile, strict: strict, lenient: lenient, forceExclude: forceExclude, excludeByPrefix: excludeByPrefix, useScriptInputFiles: useScriptInputFiles, benchmark: benchmark, reporter: reporter, quiet: quiet, cachePath: cachePath, ignoreCache: ignoreCache, enableAllRules: enableAllRules, useBaseline: useBaseline)
// swiftlint:enable line_length
}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}
}
static func evaluate(_ mode: CommandMode) -> Result<LintOptions, CommandantError<CommandantError<()>>> {
@@ -65,6 +66,9 @@ struct LintOptions: OptionsProtocol {
usage: "ignore cache when linting")
<*> mode <| Option(key: "enable-all-rules", defaultValue: false,
usage: "run all rules, even opt-in and disabled ones, ignoring `only_rules`")
<*> mode <| Option(key: "useBaseline", defaultValue: false,
usage: "save a list of violations if there is no baseline file, and then use the list " +
"as a baseline to ignore old issues and only report new ones")
// This should go last to avoid eating other args
<*> mode <| pathsArgument(action: "lint")
}
@@ -17,6 +17,7 @@ enum LintOrAnalyzeMode {
}
struct LintOrAnalyzeCommand {
// swiftlint:disable:next function_body_length
static func run(_ options: LintOrAnalyzeOptions) -> Result<(), CommandantError<()>> {
var fileBenchmark = Benchmark(name: "files")
var ruleBenchmark = Benchmark(name: "rules")
@@ -26,12 +27,20 @@ struct LintOrAnalyzeCommand {
let reporter = reporterFrom(optionsReporter: options.reporter, configuration: configuration)
let cache = options.ignoreCache ? nil : LinterCache(configuration: configuration)
let visitorMutationQueue = DispatchQueue(label: "io.realm.swiftlint.lintVisitorMutation")
let rootPath = options.paths.first?.absolutePathStandardized() ?? ""
let baseline = Baseline(baselinePath: rootPath)
if options.useBaseline {
baseline.readBaseline()
}
return configuration.visitLintableFiles(options: options, cache: cache, storage: storage) { linter in
let currentViolations: [StyleViolation]
var currentViolations: [StyleViolation]
if options.benchmark {
let start = Date()
let (violationsBeforeLeniency, currentRuleTimes) = linter.styleViolationsAndRuleTimes(using: storage)
currentViolations = applyLeniency(options: options, violations: violationsBeforeLeniency)
if options.useBaseline {
currentViolations = filteredViolations(baseline: baseline, currentViolations: currentViolations)
}
visitorMutationQueue.sync {
fileBenchmark.record(file: linter.file, from: start)
currentRuleTimes.forEach { ruleBenchmark.record(id: $0, time: $1) }
@@ -39,6 +48,9 @@ struct LintOrAnalyzeCommand {
}
} else {
currentViolations = applyLeniency(options: options, violations: linter.styleViolations(using: storage))
if options.useBaseline {
currentViolations = filteredViolations(baseline: baseline, currentViolations: currentViolations)
}
visitorMutationQueue.sync {
violations += currentViolations
}
@@ -46,6 +58,9 @@ struct LintOrAnalyzeCommand {
linter.file.invalidateCache()
reporter.report(violations: currentViolations, realtimeCondition: true)
}.flatMap { files in
if options.useBaseline {
baseline.saveBaseline(violations: violations)
}
if isWarningThresholdBroken(configuration: configuration, violations: violations)
&& !options.lenient {
violations.append(createThresholdViolation(threshold: configuration.warningThreshold!))
@@ -67,6 +82,17 @@ struct LintOrAnalyzeCommand {
}
}
private static func filteredViolations(baseline: Baseline,
currentViolations: [StyleViolation]) -> [StyleViolation] {
var filteredViolations = [StyleViolation]()
for violation in currentViolations {
if !baseline.isInBaseline(violation: violation) {
filteredViolations.append(violation)
}
}
return filteredViolations
}
private static func printStatus(violations: [StyleViolation], files: [SwiftLintFile], serious: Int, verb: String) {
let pluralSuffix = { (collection: [Any]) -> String in
return collection.count != 1 ? "s" : ""
@@ -143,6 +169,7 @@ struct LintOrAnalyzeOptions {
let cachePath: String
let ignoreCache: Bool
let enableAllRules: Bool
let useBaseline: Bool
let autocorrect: Bool
let compilerLogPath: String
let compileCommands: String
@@ -163,6 +190,7 @@ struct LintOrAnalyzeOptions {
cachePath = options.cachePath
ignoreCache = options.ignoreCache
enableAllRules = options.enableAllRules
useBaseline = options.useBaseline
autocorrect = false
compilerLogPath = ""
compileCommands = ""
@@ -184,6 +212,7 @@ struct LintOrAnalyzeOptions {
cachePath = ""
ignoreCache = true
enableAllRules = options.enableAllRules
useBaseline = false
autocorrect = options.autocorrect
compilerLogPath = options.compilerLogPath
compileCommands = options.compileCommands