mirror of
https://github.com/realm/SwiftLint.git
synced 2026-06-06 20:18:40 +00:00
This will allow for registering rules that aren't compiled as part of SwiftLintFramework. Specifically this will allow us to split the built-in and extra rules into separate modules, leading to faster incremental compilation when working on rules since the rest of the framework won't need to be rebuilt on every compilation.
127 lines
5.0 KiB
Swift
127 lines
5.0 KiB
Swift
import Foundation
|
|
import SwiftyTextTable
|
|
|
|
/// Reports a summary table of all violations
|
|
public struct SummaryReporter: Reporter {
|
|
// MARK: - Reporter Conformance
|
|
|
|
public static let identifier = "summary"
|
|
public static let isRealtime = false
|
|
|
|
public static let description = "Reports a summary table of all violations."
|
|
|
|
public static func generateReport(_ violations: [StyleViolation]) -> String {
|
|
TextTable(violations: violations).renderWithExtraSeparator()
|
|
}
|
|
}
|
|
|
|
// MARK: - SwiftyTextTable
|
|
|
|
private extension TextTable {
|
|
// swiftlint:disable:next function_body_length
|
|
init(violations: [StyleViolation]) {
|
|
let numberOfWarningsHeader = "warnings"
|
|
let numberOfErrorsHeader = "errors"
|
|
let numberOfViolationsHeader = "total violations"
|
|
let numberOfFilesHeader = "number of files"
|
|
let columns = [
|
|
TextTableColumn(header: "rule identifier"),
|
|
TextTableColumn(header: "opt-in"),
|
|
TextTableColumn(header: "correctable"),
|
|
TextTableColumn(header: "custom"),
|
|
TextTableColumn(header: numberOfWarningsHeader),
|
|
TextTableColumn(header: numberOfErrorsHeader),
|
|
TextTableColumn(header: numberOfViolationsHeader),
|
|
TextTableColumn(header: numberOfFilesHeader)
|
|
]
|
|
self.init(columns: columns)
|
|
|
|
let ruleIdentifiersToViolationsMap = violations.group { $0.ruleIdentifier }
|
|
let sortedRuleIdentifiers = ruleIdentifiersToViolationsMap.keys.sorted {
|
|
let count1 = ruleIdentifiersToViolationsMap[$0]?.count ?? 0
|
|
let count2 = ruleIdentifiersToViolationsMap[$1]?.count ?? 0
|
|
if count1 > count2 {
|
|
return true
|
|
} else if count1 == count2 {
|
|
return $0 < $1
|
|
}
|
|
return false
|
|
}
|
|
|
|
var totalNumberOfWarnings = 0
|
|
var totalNumberOfErrors = 0
|
|
|
|
for ruleIdentifier in sortedRuleIdentifiers {
|
|
guard let ruleIdentifier = ruleIdentifiersToViolationsMap[ruleIdentifier]?.first?.ruleIdentifier else {
|
|
continue
|
|
}
|
|
|
|
let rule = RuleRegistry.shared.rule(forID: ruleIdentifier)
|
|
let violations = ruleIdentifiersToViolationsMap[ruleIdentifier]
|
|
let numberOfWarnings = violations?.filter { $0.severity == .warning }.count ?? 0
|
|
let numberOfErrors = violations?.filter { $0.severity == .error }.count ?? 0
|
|
let numberOfViolations = numberOfWarnings + numberOfErrors
|
|
totalNumberOfWarnings += numberOfWarnings
|
|
totalNumberOfErrors += numberOfErrors
|
|
let ruleViolations = ruleIdentifiersToViolationsMap[ruleIdentifier] ?? []
|
|
let numberOfFiles = Set(ruleViolations.map { $0.location.file }).count
|
|
|
|
addRow(values: [
|
|
ruleIdentifier,
|
|
rule is OptInRule.Type ? "yes" : "no",
|
|
rule is CorrectableRule.Type ? "yes" : "no",
|
|
rule == nil ? "yes" : "no",
|
|
numberOfWarnings.formattedString.leftPadded(forHeader: numberOfWarningsHeader),
|
|
numberOfErrors.formattedString.leftPadded(forHeader: numberOfErrorsHeader),
|
|
numberOfViolations.formattedString.leftPadded(forHeader: numberOfViolationsHeader),
|
|
numberOfFiles.formattedString.leftPadded(forHeader: numberOfFilesHeader)
|
|
])
|
|
}
|
|
|
|
let totalNumberOfViolations = totalNumberOfWarnings + totalNumberOfErrors
|
|
let totalNumberOfFiles = Set(violations.map { $0.location.file }).count
|
|
addRow(values: [
|
|
"Total",
|
|
"",
|
|
"",
|
|
"",
|
|
totalNumberOfWarnings.formattedString.leftPadded(forHeader: numberOfWarningsHeader),
|
|
totalNumberOfErrors.formattedString.leftPadded(forHeader: numberOfErrorsHeader),
|
|
totalNumberOfViolations.formattedString.leftPadded(forHeader: numberOfViolationsHeader),
|
|
totalNumberOfFiles.formattedString.leftPadded(forHeader: numberOfFilesHeader)
|
|
])
|
|
}
|
|
|
|
func renderWithExtraSeparator() -> String {
|
|
var output = render()
|
|
var lines = output.components(separatedBy: "\n")
|
|
if lines.count > 5, let lastLine = lines.last {
|
|
lines.insert(lastLine, at: lines.count - 2)
|
|
output = lines.joined(separator: "\n")
|
|
}
|
|
return output
|
|
}
|
|
}
|
|
|
|
private extension String {
|
|
func leftPadded(forHeader header: String) -> String {
|
|
let headerCount = header.count - self.count
|
|
if headerCount > 0 {
|
|
return String(repeating: " ", count: headerCount) + self
|
|
}
|
|
return self
|
|
}
|
|
}
|
|
|
|
private extension Int {
|
|
private static var numberFormatter: NumberFormatter = {
|
|
let numberFormatter = NumberFormatter()
|
|
numberFormatter.numberStyle = .decimal
|
|
return numberFormatter
|
|
}()
|
|
var formattedString: String {
|
|
// swiftlint:disable:next legacy_objc_type
|
|
Int.numberFormatter.string(from: NSNumber(value: self)) ?? ""
|
|
}
|
|
}
|