diff --git a/CHANGELOG.md b/CHANGELOG.md index 93f7a183c1..27c5679fe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ [PaulTaykalo](https://github.com/PaulTaykalo) [#2918](https://github.com/realm/SwiftLint/issues/2918) +* Add GitHub Actions Logging reporter (`github-actions-logging`). + [Norio Nomura](https://github.com/norio-nomura) + #### Bug Fixes * None. diff --git a/Source/SwiftLintFramework/Protocols/Reporter.swift b/Source/SwiftLintFramework/Protocols/Reporter.swift index 03c7635f2b..7449a0c2f3 100644 --- a/Source/SwiftLintFramework/Protocols/Reporter.swift +++ b/Source/SwiftLintFramework/Protocols/Reporter.swift @@ -5,7 +5,7 @@ public protocol Reporter: CustomStringConvertible { static func generateReport(_ violations: [StyleViolation]) -> String } -public func reporterFrom(identifier: String) -> Reporter.Type { +public func reporterFrom(identifier: String) -> Reporter.Type { // swiftlint:disable:this cyclomatic_complexity switch identifier { case XcodeReporter.identifier: return XcodeReporter.self @@ -25,6 +25,8 @@ public func reporterFrom(identifier: String) -> Reporter.Type { return SonarQubeReporter.self case MarkdownReporter.identifier: return MarkdownReporter.self + case GitHubActionsLoggingReporter.identifier: + return GitHubActionsLoggingReporter.self default: queuedFatalError("no reporter with identifier '\(identifier)' available.") } diff --git a/Source/SwiftLintFramework/Reporters/GitHubActionsLoggingReporter.swift b/Source/SwiftLintFramework/Reporters/GitHubActionsLoggingReporter.swift new file mode 100644 index 0000000000..752880de30 --- /dev/null +++ b/Source/SwiftLintFramework/Reporters/GitHubActionsLoggingReporter.swift @@ -0,0 +1,26 @@ +public struct GitHubActionsLoggingReporter: Reporter { + public static let identifier = "github-actions-logging" + public static let isRealtime = true + + public var description: String { + return "Reports violations in the format GitHub-hosted virtual machine for Actions can recognize as messages." + } + + public static func generateReport(_ violations: [StyleViolation]) -> String { + return violations.map(generateForSingleViolation).joined(separator: "\n") + } + + internal static func generateForSingleViolation(_ violation: StyleViolation) -> String { + // swiftlint:disable:next line_length + // https://help.github.com/en/github/automating-your-workflow-with-github-actions/development-tools-for-github-actions#logging-commands + // ::(warning|error) file={relative_path_to_file},line={:line},col={:character}::{content} + return [ + "::\(violation.severity.rawValue) ", + "file=\(violation.location.relativeFile ?? ""),", + "line=\(violation.location.line ?? 1),", + "col=\(violation.location.character ?? 1)::", + violation.reason, + " (\(violation.ruleDescription.identifier))" + ].joined() + } +} diff --git a/SwiftLint.xcodeproj/project.pbxproj b/SwiftLint.xcodeproj/project.pbxproj index 44d4e324c2..728a6a390f 100644 --- a/SwiftLint.xcodeproj/project.pbxproj +++ b/SwiftLint.xcodeproj/project.pbxproj @@ -130,6 +130,8 @@ 67EB4DFC1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB4DFB1E4CD7F5004E9ACD /* CyclomaticComplexityRuleTests.swift */; }; 69F88BF71BDA38A6005E7CAE /* OpeningBraceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 692B1EB11BD7E00F00EAABFF /* OpeningBraceRule.swift */; }; 6BE79EB12204EC0700B5A2FE /* RequiredDeinitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BE79EB02204EC0700B5A2FE /* RequiredDeinitRule.swift */; }; + 6C15818D237026AC00F582A2 /* GitHubActionsLoggingReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C15818C237026AC00F582A2 /* GitHubActionsLoggingReporter.swift */; }; + 6C15818F23702DCE00F582A2 /* CannedGitHubActionsLoggingReporterOutput.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6C15818E23702DCE00F582A2 /* CannedGitHubActionsLoggingReporterOutput.txt */; }; 6C1D763221A4E69600DEF783 /* Request+DisableSourceKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C1D763121A4E69600DEF783 /* Request+DisableSourceKit.swift */; }; 6C7045441C6ADA450003F15A /* SourceKitCrashTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C7045431C6ADA450003F15A /* SourceKitCrashTests.swift */; }; 6CB514E91C760C6900FA02C4 /* Structure+SwiftLint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CB514E81C760C6900FA02C4 /* Structure+SwiftLint.swift */; }; @@ -616,6 +618,8 @@ 692B60AB1BD8F2E700C7AA22 /* StatementPositionRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementPositionRule.swift; sourceTree = ""; }; 695BE9CE1BDFD92B0071E985 /* CommaRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommaRule.swift; sourceTree = ""; }; 6BE79EB02204EC0700B5A2FE /* RequiredDeinitRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequiredDeinitRule.swift; sourceTree = ""; }; + 6C15818C237026AC00F582A2 /* GitHubActionsLoggingReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubActionsLoggingReporter.swift; sourceTree = ""; }; + 6C15818E23702DCE00F582A2 /* CannedGitHubActionsLoggingReporterOutput.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CannedGitHubActionsLoggingReporterOutput.txt; sourceTree = ""; }; 6C1D763121A4E69600DEF783 /* Request+DisableSourceKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Request+DisableSourceKit.swift"; sourceTree = ""; }; 6C27B5FC2079D33F00353E17 /* Mac-XCTest.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "Mac-XCTest.xcconfig"; sourceTree = ""; }; 6C7045431C6ADA450003F15A /* SourceKitCrashTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourceKitCrashTests.swift; sourceTree = ""; }; @@ -986,6 +990,7 @@ B39359A325FE84B7EDD1C455 /* CannedJunitReporterOutput.xml */, B39356DE1F73BDA1CA21C504 /* CannedCheckstyleReporterOutput.xml */, B3935939C8366514D2694722 /* CannedCSVReporterOutput.csv */, + 6C15818E23702DCE00F582A2 /* CannedGitHubActionsLoggingReporterOutput.txt */, B39352E4EA2A06BE66BD661A /* CannedXcodeReporterOutput.txt */, B3935001033261E5A70CE101 /* CannedEmojiReporterOutput.txt */, B39350463894A3FC1338E0AF /* CannedJSONReporterOutput.json */, @@ -1567,6 +1572,7 @@ E8EA41161C2D1DBE004F9930 /* CheckstyleReporter.swift */, E86396CA1BADB519002C9E88 /* CSVReporter.swift */, 2E336D191DF08AF200CCFE77 /* EmojiReporter.swift */, + 6C15818C237026AC00F582A2 /* GitHubActionsLoggingReporter.swift */, 4A9A3A391DC1D75F00DF5183 /* HTMLReporter.swift */, E86396C81BADB2B9002C9E88 /* JSONReporter.swift */, 57ED82791CF65183002B3513 /* JUnitReporter.swift */, @@ -1807,6 +1813,7 @@ B3935522DC192D38D4852FA3 /* CannedXcodeReporterOutput.txt in Resources */, B39358AA2D2AF5219D3FD7C0 /* CannedEmojiReporterOutput.txt in Resources */, B3935A1C3BCA03A6B902E7AF /* CannedJSONReporterOutput.json in Resources */, + 6C15818F23702DCE00F582A2 /* CannedGitHubActionsLoggingReporterOutput.txt in Resources */, D495B1A221165DAA00E2CD7B /* FileNameRuleFixtures in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2206,6 +2213,7 @@ 827169B31F488181003FB9AF /* ExplicitEnumRawValueRule.swift in Sources */, D466B620233D229F0068190B /* FlatMapOverMapReduceRule.swift in Sources */, D41985E921FAB62F003BE2B7 /* DeploymentTargetRule.swift in Sources */, + 6C15818D237026AC00F582A2 /* GitHubActionsLoggingReporter.swift in Sources */, 62FE5D32200CABDD00F68793 /* DiscouragedOptionalCollectionExamples.swift in Sources */, D49896F12026B36C00814A83 /* RedundantSetAccessControlRule.swift in Sources */, 29FFC37A1F15764D007E4825 /* FileLengthRuleConfiguration.swift in Sources */, diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index ab2a25a9b6..0bc8dfc002 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1140,6 +1140,7 @@ extension ReporterTests { ("testReporterFromString", testReporterFromString), ("testXcodeReporter", testXcodeReporter), ("testEmojiReporter", testEmojiReporter), + ("testGitHubActionsLoggingReporter", testGitHubActionsLoggingReporter), ("testJSONReporter", testJSONReporter), ("testCSVReporter", testCSVReporter), ("testCheckstyleReporter", testCheckstyleReporter), diff --git a/Tests/SwiftLintFrameworkTests/ReporterTests.swift b/Tests/SwiftLintFrameworkTests/ReporterTests.swift index 08babd9936..47a7e9b2c5 100644 --- a/Tests/SwiftLintFrameworkTests/ReporterTests.swift +++ b/Tests/SwiftLintFrameworkTests/ReporterTests.swift @@ -14,7 +14,8 @@ class ReporterTests: XCTestCase { HTMLReporter.self, EmojiReporter.self, SonarQubeReporter.self, - MarkdownReporter.self + MarkdownReporter.self, + GitHubActionsLoggingReporter.self ] for reporter in reporters { XCTAssertEqual(reporter.identifier, reporterFrom(identifier: reporter.identifier).identifier) @@ -59,6 +60,12 @@ class ReporterTests: XCTestCase { XCTAssertEqual(result, expectedOutput) } + func testGitHubActionsLoggingReporter() { + let expectedOutput = stringFromFile("CannedGitHubActionsLoggingReporterOutput.txt") + let result = GitHubActionsLoggingReporter.generateReport(generateViolations()) + XCTAssertEqual(result, expectedOutput) + } + private func jsonValue(_ jsonString: String) throws -> NSObject { let data = jsonString.data(using: .utf8)! let result = try JSONSerialization.jsonObject(with: data, options: []) diff --git a/Tests/SwiftLintFrameworkTests/Resources/CannedGitHubActionsLoggingReporterOutput.txt b/Tests/SwiftLintFrameworkTests/Resources/CannedGitHubActionsLoggingReporterOutput.txt new file mode 100644 index 0000000000..631e0cb8d8 --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/Resources/CannedGitHubActionsLoggingReporterOutput.txt @@ -0,0 +1,4 @@ +::warning file=filename,line=1,col=2::Violation Reason. (line_length) +::error file=filename,line=1,col=2::Violation Reason. (line_length) +::error file=filename,line=1,col=2::Shorthand syntactic sugar should be used, i.e. [Int] instead of Array. (syntactic_sugar) +::error file=,line=1,col=1::Colons should be next to the identifier when specifying a type and next to the key in dictionary literals. (colon) \ No newline at end of file