mirror of
https://github.com/realm/SwiftLint.git
synced 2026-05-07 20:12:49 +00:00
Implement baseline functionality
This commit is contained in:
committed by
Paul Taykalo
parent
ddfba6ea5d
commit
79c30afefa
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user