Skip to content

Commit

Permalink
Add codeclimate reporter (#3425)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkroepke committed Nov 11, 2020
1 parent 2e26e9c commit 874cacb
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 21 deletions.
1 change: 1 addition & 0 deletions .jazzy.yaml
Expand Up @@ -22,6 +22,7 @@ custom_categories:
children:
- CSVReporter
- CheckstyleReporter
- CodeclimateReporter
- EmojiReporter
- GitHubActionsLoggingReporter
- HTMLReporter
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -59,6 +59,11 @@
[Noah Gilmore](https://github.com/noahsark769)
[#3233](https://github.com/realm/SwiftLint/issues/3233)

* Add `codeclimate` reporter to generate JSON reports in codeclimate
format. Could be used for GitLab Code Quality MR Widget.
[jkroepke](https://github.com/jkroepke)
[#3424](https://github.com/realm/SwiftLint/issues/3424)

#### Bug Fixes

* None.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -396,7 +396,7 @@ identifier_name:
- id
- URL
- GlobalAPIKey
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown, github-actions-logging)
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging)
```

You can also use environment variables in your configuration file,
Expand Down
2 changes: 1 addition & 1 deletion README_CN.md
Expand Up @@ -283,7 +283,7 @@ identifier_name:
- id
- URL
- GlobalAPIKey
reporter: "xcode" # 报告类型 (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown, github-actions-logging)
reporter: "xcode" # 报告类型 (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging)
```

#### 定义自定义规则
Expand Down
2 changes: 1 addition & 1 deletion README_KR.md
Expand Up @@ -258,7 +258,7 @@ identifier_name:
- id
- URL
- GlobalAPIKey
reporter: "xcode" # 보고 유형 (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown, github-actions-logging)
reporter: "xcode" # 보고 유형 (xcode, json, csv, codeclimate, checkstyle, junit, html, emoji, sonarqube, markdown, github-actions-logging)
```

#### 커스텀 룰 정의
Expand Down
18 changes: 1 addition & 17 deletions Source/SwiftLintFramework/Extensions/Configuration+Cache.swift
@@ -1,24 +1,8 @@
#if canImport(CommonCrypto)
import CommonCrypto
#else
#if canImport(CryptoSwift)
import CryptoSwift
#endif
import Foundation

#if canImport(CommonCrypto)
private extension String {
func md5() -> String {
let context = UnsafeMutablePointer<CC_MD5_CTX>.allocate(capacity: 1)
var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
CC_MD5_Init(context)
CC_MD5_Update(context, self, CC_LONG(lengthOfBytes(using: .utf8)))
CC_MD5_Final(&digest, context)
context.deallocate()
return digest.reduce(into: "") { $0.append(String(format: "%02x", $1)) }
}
}
#endif

extension Configuration {
// MARK: Caching Configurations By Path (In-Memory)

Expand Down
15 changes: 15 additions & 0 deletions Source/SwiftLintFramework/Extensions/String+md5.swift
@@ -0,0 +1,15 @@
#if canImport(CommonCrypto)
import CommonCrypto

extension String {
internal func md5() -> String {
let context = UnsafeMutablePointer<CC_MD5_CTX>.allocate(capacity: 1)
var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
CC_MD5_Init(context)
CC_MD5_Update(context, self, CC_LONG(lengthOfBytes(using: .utf8)))
CC_MD5_Final(&digest, context)
context.deallocate()
return digest.reduce(into: "") { $0.append(String(format: "%02x", $1)) }
}
}
#endif
2 changes: 2 additions & 0 deletions Source/SwiftLintFramework/Protocols/Reporter.swift
Expand Up @@ -45,6 +45,8 @@ public func reporterFrom(identifier: String) -> Reporter.Type { // swiftlint:dis
return GitHubActionsLoggingReporter.self
case GitLabJUnitReporter.identifier:
return GitLabJUnitReporter.self
case CodeClimateReporter.identifier:
return CodeClimateReporter.self
default:
queuedFatalError("no reporter with identifier '\(identifier)' available.")
}
Expand Down
49 changes: 49 additions & 0 deletions Source/SwiftLintFramework/Reporters/CodeClimateReporter.swift
@@ -0,0 +1,49 @@
#if canImport(CryptoSwift)
import CryptoSwift
#endif
import Foundation
import SourceKittenFramework

/// Reports violations as a JSON array in Code Climate format.
public struct CodeClimateReporter: Reporter {
// MARK: - Reporter Conformance

public static let identifier = "codeclimate"
public static let isRealtime = false

public var description: String {
return "Reports violations as a JSON array in Code Climate format."
}

public static func generateReport(_ violations: [StyleViolation]) -> String {
return toJSON(violations.map(dictionary(for:)))
.replacingOccurrences(of: "\\/", with: "/")
}

// MARK: - Private

private static func dictionary(for violation: StyleViolation) -> [String: Any] {
return [
"check_name": violation.ruleName,
"description": violation.reason,
"engine_name": "SwiftLint",
"fingerprint": generateFingerprint(violation),
"location": [
"path": violation.location.file ?? NSNull() as Any,
"lines": [
"begin": violation.location.line ?? NSNull() as Any,
"end": violation.location.line ?? NSNull() as Any
]
],
"severity": violation.severity == .error ? "MAJOR": "MINOR",
"type": "issue"
]
}

internal static func generateFingerprint(_ violation: StyleViolation) -> String {
return [
"\(violation.location)",
"\(violation.ruleIdentifier)"
].joined().md5()
}
}
3 changes: 2 additions & 1 deletion Tests/LinuxMain.swift
Expand Up @@ -1305,7 +1305,8 @@ extension ReporterTests {
("testJunitReporter", testJunitReporter),
("testHTMLReporter", testHTMLReporter),
("testSonarQubeReporter", testSonarQubeReporter),
("testMarkdownReporter", testMarkdownReporter)
("testMarkdownReporter", testMarkdownReporter),
("testCodeClimateReporter", testCodeClimateReporter)
]
}

Expand Down
7 changes: 7 additions & 0 deletions Tests/SwiftLintFrameworkTests/ReporterTests.swift
Expand Up @@ -10,6 +10,7 @@ class ReporterTests: XCTestCase {
JSONReporter.self,
CSVReporter.self,
CheckstyleReporter.self,
CodeClimateReporter.self,
JUnitReporter.self,
HTMLReporter.self,
EmojiReporter.self,
Expand Down Expand Up @@ -102,6 +103,12 @@ class ReporterTests: XCTestCase {
XCTAssertEqual(result, expectedOutput)
}

func testCodeClimateReporter() {
let expectedOutput = stringFromFile("CannedCodeClimateReporterOutput.json")
let result = CodeClimateReporter.generateReport(generateViolations())
XCTAssertEqual(result, expectedOutput)
}

func testJunitReporter() {
let expectedOutput = stringFromFile("CannedJunitReporterOutput.xml")
let result = JUnitReporter.generateReport(generateViolations())
Expand Down
@@ -0,0 +1,62 @@
[
{
"check_name" : "Line Length",
"description" : "Violation Reason.",
"engine_name" : "SwiftLint",
"fingerprint" : "091a80392f7aebde05dab908162c3629",
"location" : {
"lines" : {
"begin" : 1,
"end" : 1
},
"path" : "filename"
},
"severity" : "MINOR",
"type" : "issue"
},
{
"check_name" : "Line Length",
"description" : "Violation Reason.",
"engine_name" : "SwiftLint",
"fingerprint" : "091a80392f7aebde05dab908162c3629",
"location" : {
"lines" : {
"begin" : 1,
"end" : 1
},
"path" : "filename"
},
"severity" : "MAJOR",
"type" : "issue"
},
{
"check_name" : "Syntactic Sugar",
"description" : "Shorthand syntactic sugar should be used, i.e. [Int] instead of Array<Int>.",
"engine_name" : "SwiftLint",
"fingerprint" : "624a4e77e797c9e6a42c82b6428e9c45",
"location" : {
"lines" : {
"begin" : 1,
"end" : 1
},
"path" : "filename"
},
"severity" : "MAJOR",
"type" : "issue"
},
{
"check_name" : "Colon",
"description" : "Colons should be next to the identifier when specifying a type and next to the key in dictionary literals.",
"engine_name" : "SwiftLint",
"fingerprint" : "1f9389145aa6d7e89a582fce5ae54659",
"location" : {
"lines" : {
"begin" : null,
"end" : null
},
"path" : null
},
"severity" : "MAJOR",
"type" : "issue"
}
]

0 comments on commit 874cacb

Please sign in to comment.