diff --git a/CHANGELOG.md b/CHANGELOG.md index bb890a142..3348c4e3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/Source/SwiftLintFramework/Reporters/SARIFReporter.swift b/Source/SwiftLintFramework/Reporters/SARIFReporter.swift index 108e4fb2d..b5b516fd0 100644 --- a/Source/SwiftLintFramework/Reporters/SARIFReporter.swift +++ b/Source/SwiftLintFramework/Reporters/SARIFReporter.swift @@ -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, diff --git a/Tests/FileSystemAccessTests/ReporterTests.swift b/Tests/FileSystemAccessTests/ReporterTests.swift index a9083b9ef..cebf5082a 100644 --- a/Tests/FileSystemAccessTests/ReporterTests.swift +++ b/Tests/FileSystemAccessTests/ReporterTests.swift @@ -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", diff --git a/Tests/FileSystemAccessTests/Resources/CannedSARIFReporterOutput.json b/Tests/FileSystemAccessTests/Resources/CannedSARIFReporterOutput.json deleted file mode 100644 index 5e0240522..000000000 --- a/Tests/FileSystemAccessTests/Resources/CannedSARIFReporterOutput.json +++ /dev/null @@ -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" - }, - "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" -} \ No newline at end of file diff --git a/Tests/FileSystemAccessTests/Resources/CannedSARIFReporterOutput.sarif b/Tests/FileSystemAccessTests/Resources/CannedSARIFReporterOutput.sarif new file mode 100644 index 000000000..d43f7e814 --- /dev/null +++ b/Tests/FileSystemAccessTests/Resources/CannedSARIFReporterOutput.sarif @@ -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" + }, + "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" +} \ No newline at end of file