SwiftLint/Source/swiftlint/Helpers/LintOrAnalyzeCommand.swift

194 lines
7.3 KiB
Swift

import Commandant
import Dispatch
import Foundation
import Result
import SourceKittenFramework
import SwiftLintFramework
enum LintOrAnalyzeMode {
case lint, analyze
var verb: String {
switch self {
case .lint:
return "linting"
case .analyze:
return "analyzing"
}
}
}
struct LintOrAnalyzeCommand {
static func run(_ options: LintOrAnalyzeOptions) -> Result<(), CommandantError<()>> {
var fileBenchmark = Benchmark(name: "files")
var ruleBenchmark = Benchmark(name: "rules")
var violations = [StyleViolation]()
let storage = RuleStorage()
let configuration = Configuration(options: options)
let reporter = reporterFrom(optionsReporter: options.reporter, configuration: configuration)
let cache = options.ignoreCache ? nil : LinterCache(configuration: configuration)
let visitorMutationQueue = DispatchQueue(label: "io.realm.swiftlint.lintVisitorMutation")
return configuration.visitLintableFiles(options: options, cache: cache, storage: storage) { linter in
let currentViolations: [StyleViolation]
if options.benchmark {
let start = Date()
let (violationsBeforeLeniency, currentRuleTimes) = linter.styleViolationsAndRuleTimes(using: storage)
currentViolations = applyLeniency(options: options, violations: violationsBeforeLeniency)
visitorMutationQueue.sync {
fileBenchmark.record(file: linter.file, from: start)
currentRuleTimes.forEach { ruleBenchmark.record(id: $0, time: $1) }
violations += currentViolations
}
} else {
currentViolations = applyLeniency(options: options, violations: linter.styleViolations(using: storage))
visitorMutationQueue.sync {
violations += currentViolations
}
}
linter.file.invalidateCache()
reporter.report(violations: currentViolations, realtimeCondition: true)
}.flatMap { files in
if isWarningThresholdBroken(configuration: configuration, violations: violations)
&& !options.lenient {
violations.append(createThresholdViolation(threshold: configuration.warningThreshold!))
reporter.report(violations: [violations.last!], realtimeCondition: true)
}
reporter.report(violations: violations, realtimeCondition: false)
let numberOfSeriousViolations = violations.filter({ $0.severity == .error }).count
if !options.quiet {
printStatus(violations: violations, files: files, serious: numberOfSeriousViolations,
verb: options.verb)
}
if options.benchmark {
fileBenchmark.save()
ruleBenchmark.save()
}
try? cache?.save()
return successOrExit(numberOfSeriousViolations: numberOfSeriousViolations,
strictWithViolations: options.strict && !violations.isEmpty)
}
}
private static func successOrExit(numberOfSeriousViolations: Int,
strictWithViolations: Bool) -> Result<(), CommandantError<()>> {
if numberOfSeriousViolations > 0 {
exit(2)
} else if strictWithViolations {
exit(3)
}
return .success(())
}
private static func printStatus(violations: [StyleViolation], files: [File], serious: Int, verb: String) {
let pluralSuffix = { (collection: [Any]) -> String in
return collection.count != 1 ? "s" : ""
}
queuedPrintError(
"Done \(verb)! Found \(violations.count) violation\(pluralSuffix(violations)), " +
"\(serious) serious in \(files.count) file\(pluralSuffix(files))."
)
}
private static func isWarningThresholdBroken(configuration: Configuration,
violations: [StyleViolation]) -> Bool {
guard let warningThreshold = configuration.warningThreshold else { return false }
let numberOfWarningViolations = violations.filter({ $0.severity == .warning }).count
return numberOfWarningViolations >= warningThreshold
}
private static func createThresholdViolation(threshold: Int) -> StyleViolation {
let description = RuleDescription(
identifier: "warning_threshold",
name: "Warning Threshold",
description: "Number of warnings thrown is above the threshold.",
kind: .lint
)
return StyleViolation(
ruleDescription: description,
severity: .error,
location: Location(file: "", line: 0, character: 0),
reason: "Number of warnings exceeded threshold of \(threshold).")
}
private static func applyLeniency(options: LintOrAnalyzeOptions, violations: [StyleViolation]) -> [StyleViolation] {
if !options.lenient {
return violations
}
return violations.map {
if $0.severity == .error {
return StyleViolation(ruleDescription: $0.ruleDescription,
severity: .warning,
location: $0.location,
reason: $0.reason)
} else {
return $0
}
}
}
}
struct LintOrAnalyzeOptions {
let mode: LintOrAnalyzeMode
let paths: [String]
let useSTDIN: Bool
let configurationFile: String
let strict: Bool
let lenient: Bool
let forceExclude: Bool
let useScriptInputFiles: Bool
let benchmark: Bool
let reporter: String
let quiet: Bool
let cachePath: String
let ignoreCache: Bool
let enableAllRules: Bool
let autocorrect: Bool
let compilerLogPath: String
init(_ options: LintOptions) {
mode = .lint
paths = options.paths
useSTDIN = options.useSTDIN
configurationFile = options.configurationFile
strict = options.strict
lenient = options.lenient
forceExclude = options.forceExclude
useScriptInputFiles = options.useScriptInputFiles
benchmark = options.benchmark
reporter = options.reporter
quiet = options.quiet
cachePath = options.cachePath
ignoreCache = options.ignoreCache
enableAllRules = options.enableAllRules
autocorrect = false
compilerLogPath = ""
}
init(_ options: AnalyzeOptions) {
mode = .analyze
paths = options.paths
useSTDIN = false
configurationFile = options.configurationFile
strict = options.strict
lenient = options.lenient
forceExclude = options.forceExclude
useScriptInputFiles = options.useScriptInputFiles
benchmark = options.benchmark
reporter = options.reporter
quiet = options.quiet
cachePath = ""
ignoreCache = true
enableAllRules = options.enableAllRules
autocorrect = options.autocorrect
compilerLogPath = options.compilerLogPath
}
var verb: String {
if autocorrect {
return "correcting"
} else {
return mode.verb
}
}
}