Repository: detekt/detekt (kotlin)

Issue:
Populate partialFingerprints for results in SARIF reports
## Expected Behavior
`partialFingerprints` is populated for results in SARIF reports.

## Current Behavior
No fingerprint is generated.

## Context
SARIF report should provide all data that's "required" by GitHub.

This attribute is useful:
* [to avoid seeing duplicate alerts](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning#including-data-for-fingerprint-generation)
* [to accurately identify which results are the same across commits and branches](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning#result-object)

Pull request:
Closes #7683

We can use as `partialFingerprint` the same value that we use for our `baseline`. We didn't have too many issues related with our `baseline` so `signature` should be a good enough `partialFingerprint`. I'm using `sha1` to avoid really long signatures in the sarif file.

---

Old PR description:

~There are 2 things that I have not clear about this implementation:~

~First: What do we use to generate the partial fingerprint?~

~My proposal is to use the `singature`. That's the same to what the Baseline uses. But it's not perfect. You can have multiple issues with the same signature. We could also add `line:column` to the mix or even `filename:line:column`. That will reduce the collisions but the partial fingerprints could change a lot between executions. Unrelated changes with the issue (for example add an import to a file) would change their fingerprint. For the baseline this is a huge problem but I'm not sure if that's a problem on a SARIF report.{

~Second: Which hash algorithm should we use?~

~In this PR I'm using `String.hashCode()`. But I'm not sure if that will be enought to avoid collisions. Other options are sha1 or just don't hash it at all.~

Patch:
diff --git a/detekt-report-sarif/src/main/kotlin/io/github/detekt/report/sarif/Results.kt b/detekt-report-sarif/src/main/kotlin/io/github/detekt/report/sarif/Results.kt
index bc8bec324cfe..3ad619b8902d 100644
--- a/detekt-report-sarif/src/main/kotlin/io/github/detekt/report/sarif/Results.kt
+++ b/detekt-report-sarif/src/main/kotlin/io/github/detekt/report/sarif/Results.kt
@@ -9,6 +9,7 @@ import io.gitlab.arturbosch.detekt.api.Detektion
 import io.gitlab.arturbosch.detekt.api.Issue
 import io.gitlab.arturbosch.detekt.api.Severity
 import io.gitlab.arturbosch.detekt.api.suppressed
+import java.security.MessageDigest
 import kotlin.io.path.invariantSeparatorsPathString
 
 internal fun toResults(detektion: Detektion): List<io.github.detekt.sarif4k.Result> =
@@ -25,7 +26,10 @@ private fun Issue.toResult(): io.github.detekt.sarif4k.Result =
         ruleID = "detekt.${ruleInstance.ruleSetId}.${ruleInstance.id}",
         level = severity.toResultLevel(),
         locations = (listOf(location) + references.map { it.location }).map { it.toLocation() }.distinct(),
-        message = Message(text = message)
+        message = Message(text = message),
+        partialFingerprints = mapOf(
+            "signature/v1" to entity.signature.sha1(),
+        )
     )
 
 private fun Issue.Location.toLocation(): io.github.detekt.sarif4k.Location =
@@ -43,3 +47,8 @@ private fun Issue.Location.toLocation(): io.github.detekt.sarif4k.Location =
             )
         )
     )
+
+private fun String.sha1(): String = MessageDigest
+    .getInstance("SHA-1")
+    .digest(toByteArray())
+    .joinToString("") { it.toUByte().toString(radix = 16).padStart(2, '0') }


Test patch:
diff --git a/detekt-report-sarif/src/test/kotlin/io/github/detekt/report/sarif/SarifOutputReportSpec.kt b/detekt-report-sarif/src/test/kotlin/io/github/detekt/report/sarif/SarifOutputReportSpec.kt
index 715d59a85dee..c6ecbde0738f 100644
--- a/detekt-report-sarif/src/test/kotlin/io/github/detekt/report/sarif/SarifOutputReportSpec.kt
+++ b/detekt-report-sarif/src/test/kotlin/io/github/detekt/report/sarif/SarifOutputReportSpec.kt
@@ -26,23 +26,35 @@ class SarifOutputReportSpec {
         val result = TestDetektion(
             createIssue(
                 ruleInstance = createRuleInstance("TestSmellA/id", "RuleSet1"),
-                entity = createEntity(location = createLocation(position = 1 to 1, endPosition = 2 to 3)),
+                entity = createEntity(
+                    signature = "one",
+                    location = createLocation(position = 1 to 1, endPosition = 2 to 3),
+                ),
                 severity = Severity.Error
             ),
             createIssue(
                 ruleInstance = createRuleInstance("TestSmellD/id", "RuleSet1"),
-                entity = createEntity(location = createLocation(position = 1 to 1, endPosition = 2 to 3)),
+                entity = createEntity(
+                    signature = "two",
+                    location = createLocation(position = 1 to 1, endPosition = 2 to 3),
+                ),
                 severity = Severity.Error,
                 suppressReasons = listOf("suppress")
             ),
             createIssue(
                 ruleInstance = createRuleInstance("TestSmellB/id", "RuleSet2"),
-                entity = createEntity(location = createLocation(position = 3 to 5, endPosition = 3 to 5)),
+                entity = createEntity(
+                    signature = "three",
+                    location = createLocation(position = 3 to 5, endPosition = 3 to 5),
+                ),
                 severity = Severity.Warning
             ),
             createIssue(
                 ruleInstance = createRuleInstance("TestSmellC/id", "RuleSet2"),
-                entity = createEntity(location = createLocation(position = 2 to 1, endPosition = 3 to 1)),
+                entity = createEntity(
+                    signature = "four",
+                    location = createLocation(position = 2 to 1, endPosition = 3 to 1),
+                ),
                 severity = Severity.Info
             )
         )
diff --git a/detekt-report-sarif/src/test/resources/rule_warning.sarif.json b/detekt-report-sarif/src/test/resources/rule_warning.sarif.json
index fa700c8eb734..af9ba5d2e868 100644
--- a/detekt-report-sarif/src/test/resources/rule_warning.sarif.json
+++ b/detekt-report-sarif/src/test/resources/rule_warning.sarif.json
@@ -25,6 +25,9 @@
           "message": {
             "text": "TestMessage"
           },
+          "partialFingerprints": {
+            "signature/v1": "8bf90cc9da5c16a3f305920231c90a373b9dc0b3"
+          },
           "ruleId": "detekt.RuleSet1.TestSmellA/id"
         },
         {
@@ -48,6 +51,9 @@
           "message": {
             "text": "TestMessage"
           },
+          "partialFingerprints": {
+            "signature/v1": "8bf90cc9da5c16a3f305920231c90a373b9dc0b3"
+          },
           "ruleId": "detekt.RuleSet2.TestSmellB/id"
         },
         {
@@ -71,6 +77,9 @@
           "message": {
             "text": "TestMessage"
           },
+          "partialFingerprints": {
+            "signature/v1": "8bf90cc9da5c16a3f305920231c90a373b9dc0b3"
+          },
           "ruleId": "detekt.RuleSet2.TestSmellC/id"
         }
       ],
diff --git a/detekt-report-sarif/src/test/resources/vanilla.sarif.json b/detekt-report-sarif/src/test/resources/vanilla.sarif.json
index a2452c6c217e..0df6be460698 100644
--- a/detekt-report-sarif/src/test/resources/vanilla.sarif.json
+++ b/detekt-report-sarif/src/test/resources/vanilla.sarif.json
@@ -25,6 +25,9 @@
           "message": {
             "text": "TestMessage"
           },
+          "partialFingerprints": {
+            "signature/v1": "fe05bcdcdc4928012781a5f1a2a77cbb5398e106"
+          },
           "ruleId": "detekt.RuleSet1.TestSmellA/id"
         },
         {
@@ -48,6 +51,9 @@
           "message": {
             "text": "TestMessage"
           },
+          "partialFingerprints": {
+            "signature/v1": "b802f384302cb24fbab0a44997e820bf2e8507bb"
+          },
           "ruleId": "detekt.RuleSet2.TestSmellB/id"
         },
         {
@@ -71,6 +77,9 @@
           "message": {
             "text": "TestMessage"
           },
+          "partialFingerprints": {
+            "signature/v1": "9f8f7eec5dea5ac43738721939c120318cbff1df"
+          },
           "ruleId": "detekt.RuleSet2.TestSmellC/id"
         }
       ],