Add rules to SARIF exporter (#6502)

Co-authored-by: Danny Mösch <danny.moesch@icloud.com>
This commit is contained in:
Ahmad Alfy
2026-02-23 12:08:49 +02:00
committed by GitHub
parent 5707e17dff
commit 21d66e5dc7
5 changed files with 152 additions and 95 deletions
+5
View File
@@ -20,6 +20,11 @@
[claudeaceae](https://github.com/claudeaceae)
[#6487](https://github.com/realm/SwiftLint/issues/6487)
* Add `rules` array to SARIF reporter output, providing metadata for all
built-in rules in accordance with the SARIF specification.
[ahmadalfy](https://github.com/ahmadalfy)
[#6499](https://github.com/realm/SwiftLint/issues/6499)
### Bug Fixes
* Ensure that disable commands work for `redundant_nil_coalescing` rule.
@@ -23,6 +23,9 @@ struct SARIFReporter: Reporter {
"name": "SwiftLint",
"semanticVersion": Version.current.value,
"informationUri": swiftlintVersion,
"rules": RuleRegistry.shared.list.list.values
.sorted { $0.identifier < $1.identifier }
.map(dictionary(for:)),
],
],
"results": violations.map(dictionary(for:)),
@@ -35,6 +38,20 @@ struct SARIFReporter: Reporter {
// MARK: - Private
private static func dictionary(for ruleType: any Rule.Type) -> [String: Any] {
let description = ruleType.description
return [
"id": description.identifier,
"shortDescription": [
"text": description.name
],
"fullDescription": [
"text": description.description
],
"helpUri": "https://realm.github.io/SwiftLint/\(description.identifier).html",
]
}
private static func dictionary(for violation: StyleViolation) -> [String: Any] {
[
"level": violation.severity.rawValue,
@@ -117,11 +117,46 @@ final class ReporterTests: SwiftLintTestCase {
func testSARIFReporter() throws {
try assertEqualContent(
referenceFile: "CannedSARIFReporterOutput.json",
reporterType: SARIFReporter.self
referenceFile: "CannedSARIFReporterOutput.sarif",
reporterType: SARIFReporter.self,
stringConverter: { try sarifValueWithoutRules($0) }
)
}
private func sarifValueWithoutRules(_ jsonString: String) throws -> NSObject {
let data = jsonString.data(using: .utf8)!
guard var json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
queuedFatalError("Expected dictionary in SARIF JSON")
}
// Extract and verify rules before removing them
if var runs = json["runs"] as? [[String: Any]],
var firstRun = runs.first,
var tool = firstRun["tool"] as? [String: Any],
var driver = tool["driver"] as? [String: Any],
let rules = driver["rules"] as? [[String: Any]] {
// Verify rules array is not empty
XCTAssertFalse(rules.isEmpty, "Rules array should not be empty")
// Verify rule structure
if let firstRule = rules.first {
XCTAssertNotNil(firstRule["id"], "Rule should have id")
XCTAssertNotNil(firstRule["shortDescription"], "Rule should have shortDescription")
XCTAssertNotNil(firstRule["fullDescription"], "Rule should have fullDescription")
XCTAssertNotNil(firstRule["helpUri"], "Rule should have helpUri")
}
// Remove rules array for comparison
driver.removeValue(forKey: "rules")
tool["driver"] = driver
firstRun["tool"] = tool
runs[0] = firstRun
json["runs"] = runs
}
return json.bridge()
}
func testJunitReporter() throws {
try assertEqualContent(
referenceFile: "CannedJunitReporterOutput.xml",
@@ -1,93 +0,0 @@
{
"$schema" : "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json",
"runs" : [
{
"results" : [
{
"level" : "warning",
"locations" : [
{
"physicalLocation" : {
"artifactLocation" : {
"uri" : "filename"
},
"region" : {
"startColumn" : 1,
"startLine" : 1
}
}
}
],
"message" : {
"text" : "Violation Reason 1"
},
"ruleId" : "line_length"
},
{
"level" : "error",
"locations" : [
{
"physicalLocation" : {
"artifactLocation" : {
"uri" : "filename"
},
"region" : {
"startColumn" : 1,
"startLine" : 1
}
}
}
],
"message" : {
"text" : "Violation Reason 2"
},
"ruleId" : "line_length"
},
{
"level" : "error",
"locations" : [
{
"physicalLocation" : {
"artifactLocation" : {
"uri" : "path/file.swift"
},
"region" : {
"startColumn" : 2,
"startLine" : 1
}
}
}
],
"message" : {
"text" : "Shorthand syntactic sugar should be used, i.e. [Int] instead of Array<Int>"
},
"ruleId" : "syntactic_sugar"
},
{
"level" : "error",
"locations" : [
{
"physicalLocation" : {
"artifactLocation" : {
"uri" : ""
}
}
}
],
"message" : {
"text" : "Colons should be next to the identifier when specifying a type and next to the key in dictionary literals"
},
"ruleId" : "colon"
}
],
"tool" : {
"driver" : {
"informationUri" : "https://github.com/realm/SwiftLint/blob/${SWIFTLINT_VERSION}/README.md",
"name" : "SwiftLint",
"semanticVersion" : "${SWIFTLINT_VERSION}"
}
}
}
],
"version" : "2.1.0"
}
@@ -0,0 +1,93 @@
{
"$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json",
"runs": [
{
"results": [
{
"level": "warning",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "filename"
},
"region": {
"startColumn": 1,
"startLine": 1
}
}
}
],
"message": {
"text": "Violation Reason 1"
},
"ruleId": "line_length"
},
{
"level": "error",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "filename"
},
"region": {
"startColumn": 1,
"startLine": 1
}
}
}
],
"message": {
"text": "Violation Reason 2"
},
"ruleId": "line_length"
},
{
"level": "error",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "path/file.swift"
},
"region": {
"startColumn": 2,
"startLine": 1
}
}
}
],
"message": {
"text": "Shorthand syntactic sugar should be used, i.e. [Int] instead of Array<Int>"
},
"ruleId": "syntactic_sugar"
},
{
"level": "error",
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": ""
}
}
}
],
"message": {
"text": "Colons should be next to the identifier when specifying a type and next to the key in dictionary literals"
},
"ruleId": "colon"
}
],
"tool": {
"driver": {
"informationUri": "https://github.com/realm/SwiftLint/blob/${SWIFTLINT_VERSION}/README.md",
"name": "SwiftLint",
"semanticVersion": "${SWIFTLINT_VERSION}"
}
}
}
],
"version": "2.1.0"
}