import Foundation private let formatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .short return formatter }() /// Reports violations as HTML. public struct HTMLReporter: Reporter { // MARK: - Reporter Conformance public static let identifier = "html" public static let isRealtime = false public static let description = "Reports violations as HTML." public static func generateReport(_ violations: [StyleViolation]) -> String { return 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({ $0.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 """ } }