import Foundation private let formatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .short return formatter }() /// Reports violations as HTML. struct HTMLReporter: Reporter { // MARK: - Reporter Conformance static let identifier = "html" static let isRealtime = false static let description = "Reports violations as HTML." static func generateReport(_ violations: [StyleViolation]) -> String { generateReport(violations, swiftlintVersion: Version.current.value, dateString: formatter.string(from: Date())) } // MARK: - Internal // swiftlint:disable:next function_body_length internal static func generateReport(_ violations: [StyleViolation], swiftlintVersion: String, dateString: String) -> String { let rows = violations.enumerated() .map { generateSingleRow(for: $1, at: $0 + 1) } .joined(separator: "\n") let fileCount = Set(violations.compactMap(\.location.file)).count let warningCount = violations.filter({ $0.severity == .warning }).count let errorCount = violations.filter({ $0.severity == .error }).count return """ \t \t\t \t\t \t\t \t\t \t\t \t\tSwiftLint Report \t \t \t\t

SwiftLint Report

\t\t \t\t
\t\t \t\t

Violations

\t\t \t\t \t\t\t \t\t\t\t \t\t\t\t\t \t\t\t\t\t \t\t\t\t\t \t\t\t\t\t \t\t\t\t\t \t\t\t\t \t\t\t \t\t\t \(rows) \t\t\t \t\t
\t\t\t\t\t\tSerial No. \t\t\t\t\t \t\t\t\t\t\tFile \t\t\t\t\t \t\t\t\t\t\tLocation \t\t\t\t\t \t\t\t\t\t\tSeverity \t\t\t\t\t \t\t\t\t\t\tMessage \t\t\t\t\t
\t\t \t\t
\t\t \t\t

Summary

\t\t \t\t \t\t\t \t\t\t\t \t\t\t\t\t \t\t\t\t\t \t\t\t\t \t\t\t\t \t\t\t\t\t \t\t\t\t\t \t\t\t\t \t\t\t\t \t\t\t\t\t \t\t\t\t\t \t\t\t\t \t\t\t \t\t
Total files with violations\(fileCount)
Total warnings\(warningCount)
Total errors\(errorCount)
\t\t \t\t
\t\t \t\t

\t\t\tCreated with \t\t\tSwiftLint \t\t\t\(swiftlintVersion) on \(dateString) \t\t

\t """ } // MARK: - Private private static func generateSingleRow(for violation: StyleViolation, at index: Int) -> String { let severity: String = violation.severity.rawValue.capitalized let location = violation.location let file: String = (violation.location.relativeFile ?? "").escapedForXML() let line: Int = location.line ?? 0 let character: Int = location.character ?? 0 return """ \t\t\t\t \t\t\t\t\t\(index) \t\t\t\t\t\(file) \t\t\t\t\t\(line):\(character) \t\t\t\t\t\(severity) \t\t\t\t\t\(violation.reason.escapedForXML()) \t\t\t\t """ } }