Skip to content

Commit

Permalink
Support sarif as a report type - #3045 (#3132)
Browse files Browse the repository at this point in the history
* Support sarif as a report type - #3045

* Integrate sarif feedback from @lgolding

* Test the whole sarif report instead of some json paths

* Provide only the short description; we do not have access to the long one programmatically

* Use the plain rule id as sarif rule name; the rule set id is encoded in the sarif rule id

* Remove need for casting by using a when expression
  • Loading branch information
arturbosch committed Nov 16, 2020
1 parent 5c03562 commit 415ca77
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.gitlab.arturbosch.detekt.test

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.SetupContext
import io.gitlab.arturbosch.detekt.api.UnstableApi
import java.net.URI

@OptIn(UnstableApi::class)
class EmptySetupContext : SetupContext {
override val configUris: Collection<URI> = emptyList()
override val config: Config = Config.empty
override val outputChannel: Appendable = StringBuilder()
override val errorChannel: Appendable = StringBuilder()
override val properties: MutableMap<String, Any?> = HashMap()
override fun register(key: String, value: Any) {
properties[key] = value
}
}
24 changes: 24 additions & 0 deletions detekt-report-sarif/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
repositories {
mavenLocal()
}

dependencies {
compileOnly(project(":detekt-api"))
compileOnly(project(":detekt-tooling"))
implementation("io.github.detekt.sarif4j:sarif4j:1.0.0")
testImplementation(project(":detekt-tooling"))
testImplementation(project(":detekt-test-utils"))
testImplementation(testFixtures(project(":detekt-api")))
}

tasks.withType<Jar>().configureEach {
dependsOn(configurations.runtimeClasspath)
from({
configurations.runtimeClasspath.get()
.asSequence()
.filterNot { "org.jetbrains" in it.toString() }
.filterNot { "org.intellij" in it.toString() }
.map { if (it.isDirectory) it else zipTree(it) }
.toList()
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.github.detekt.report.sarif

import io.github.detekt.sarif4j.MultiformatMessageString
import io.github.detekt.sarif4j.ReportingDescriptor
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.MultiRule
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.RuleSetId
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
import java.net.URI
import java.util.ServiceLoader

fun ruleDescriptors(config: Config): HashMap<String, ReportingDescriptor> {
val sets = ServiceLoader.load(RuleSetProvider::class.java)
.map { it.instance(config.subConfig(it.ruleSetId)) }
val descriptors = HashMap<String, ReportingDescriptor>()
for (ruleSet in sets) {
for (rule in ruleSet.rules) {
when (rule) {
is MultiRule -> {
descriptors.putAll(rule.toDescriptors(ruleSet.id).associateBy { it.name })
}
is Rule -> {
val descriptor = rule.toDescriptor(ruleSet.id)
descriptors[descriptor.name] = descriptor
}
}
}
}
return descriptors
}

fun descriptor(init: ReportingDescriptor.() -> Unit) = ReportingDescriptor().apply(init)

fun MultiRule.toDescriptors(ruleSetId: RuleSetId): List<ReportingDescriptor> =
this.rules.map { it.toDescriptor(ruleSetId) }

fun Rule.toDescriptor(ruleSetId: RuleSetId): ReportingDescriptor = descriptor {
id = "detekt.$ruleSetId.$ruleId"
name = ruleId
shortDescription = MultiformatMessageString().apply { text = issue.description }
helpUri = URI.create("https://detekt.github.io/detekt/${ruleSetId.toLowerCase()}.html#${ruleId.toLowerCase()}")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.github.detekt.report.sarif

import io.github.detekt.sarif4j.Run
import io.github.detekt.sarif4j.SarifSchema210
import io.github.detekt.sarif4j.Tool
import io.github.detekt.sarif4j.ToolComponent
import io.github.detekt.tooling.api.VersionProvider
import io.gitlab.arturbosch.detekt.api.Config
import java.net.URI

const val SCHEMA_URL = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"

fun sarif(init: SarifSchema210.() -> Unit): SarifSchema210 = SarifSchema210()
.`with$schema`(URI.create(SCHEMA_URL))
.withVersion(SarifSchema210.Version._2_1_0)
.withRuns(ArrayList())
.apply(init)

typealias SarifIssue = io.github.detekt.sarif4j.Result

fun result(init: SarifIssue.() -> Unit): SarifIssue = SarifIssue().withLocations(ArrayList()).apply(init)

fun tool(init: Tool.() -> Unit): Tool = Tool().apply(init)

fun component(init: ToolComponent.() -> Unit): ToolComponent = ToolComponent().apply(init)

fun SarifSchema210.withDetektRun(config: Config, init: Run.() -> Unit) {
runs.add(
Run()
.withResults(ArrayList())
.withTool(tool {
driver = component {
guid = "022ca8c2-f6a2-4c95-b107-bb72c43263f3"
name = "detekt"
fullName = name
organization = name
language = "en"
version = VersionProvider.load().current()
semanticVersion = version
downloadUri = URI.create("https://github.com/detekt/detekt/releases/download/v$version/detekt")
informationUri = URI.create("https://detekt.github.io/detekt")
rules = ruleDescriptors(config).values.toSet()
}
})
.apply(init)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.github.detekt.report.sarif

import io.github.detekt.sarif4j.ArtifactLocation
import io.github.detekt.sarif4j.JacksonSarifWriter
import io.github.detekt.sarif4j.Location
import io.github.detekt.sarif4j.Message
import io.github.detekt.sarif4j.PhysicalLocation
import io.github.detekt.sarif4j.Region
import io.github.detekt.sarif4j.Result
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.api.OutputReport
import io.gitlab.arturbosch.detekt.api.RuleSetId
import io.gitlab.arturbosch.detekt.api.SetupContext
import io.gitlab.arturbosch.detekt.api.SingleAssign
import io.gitlab.arturbosch.detekt.api.UnstableApi
import java.net.URI

class SarifOutputReport : OutputReport() {

override val ending: String = "sarif"
override val id: String = "sarif"
override val name = "SARIF: a standard format for the output of static analysis tools"

private var config: Config by SingleAssign()

@OptIn(UnstableApi::class)
override fun init(context: SetupContext) {
this.config = context.config
}

override fun render(detektion: Detektion): String {
val report = sarif {
withDetektRun(config) {
for ((ruleSetId, issues) in detektion.findings) {
for (issue in issues) {
results.add(issue.toIssue(ruleSetId))
}
}
}
}
return JacksonSarifWriter().toJson(report)
}
}

fun Finding.toIssue(ruleSetId: RuleSetId): SarifIssue = result {
ruleId = "detekt.$ruleSetId.$id"
level = Result.Level.WARNING
for (location in listOf(location) + references.map { it.location }) {
locations.add(Location().apply {
physicalLocation = PhysicalLocation().apply {
region = Region().apply {
startLine = location.source.line
startColumn = location.source.column
}
artifactLocation = ArtifactLocation().apply {
uri = URI.create(location.file).toString()
}
}
})
}
message = Message().apply { text = messageOrDescription() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.github.detekt.report.sarif.SarifOutputReport
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.github.detekt.report.sarif

import io.github.detekt.test.utils.readResourceContent
import io.github.detekt.tooling.api.VersionProvider
import io.gitlab.arturbosch.detekt.test.EmptySetupContext
import io.gitlab.arturbosch.detekt.test.TestDetektion
import io.gitlab.arturbosch.detekt.test.createFinding
import org.assertj.core.api.Assertions.assertThat
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe

class SarifOutputReportSpec : Spek({

describe("sarif output report") {

val expectedReport by memoized {
readResourceContent("expected.sarif.json").stripWhitespace()
}

it("renders multiple issues") {
val result = TestDetektion(
createFinding(ruleName = "TestSmellA"),
createFinding(ruleName = "TestSmellB"),
createFinding(ruleName = "TestSmellC")
)

val report = SarifOutputReport().apply { init(EmptySetupContext()) }
.render(result)
.stripWhitespace()

assertThat(report).isEqualTo(expectedReport)
}
}
})

internal fun String.stripWhitespace() = replace(Regex("\\s"), "")

internal class TestVersionProvider : VersionProvider {

override fun current(): String = "1.0.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.github.detekt.report.sarif.TestVersionProvider
81 changes: 81 additions & 0 deletions detekt-report-sarif/src/test/resources/expected.sarif.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"guid": "022ca8c2-f6a2-4c95-b107-bb72c43263f3",
"name": "detekt",
"organization": "detekt",
"fullName": "detekt",
"version": "1.0.0",
"semanticVersion": "1.0.0",
"downloadUri": "https://github.com/detekt/detekt/releases/download/v1.0.0/detekt",
"informationUri": "https://detekt.github.io/detekt",
"rules": [],
"language": "en"
}
},
"results": [
{
"ruleId": "detekt.TestSmellA.TestSmellA",
"message": {
"text": "TestMessage"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "TestFile.kt"
},
"region": {
"startLine": 1,
"startColumn": 1
}
}
}
]
},
{
"ruleId": "detekt.TestSmellB.TestSmellB",
"message": {
"text": "TestMessage"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "TestFile.kt"
},
"region": {
"startLine": 1,
"startColumn": 1
}
}
}
]
},
{
"ruleId": "detekt.TestSmellC.TestSmellC",
"message": {
"text": "TestMessage"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "TestFile.kt"
},
"region": {
"startLine": 1,
"startColumn": 1
}
}
}
]
}
]
}
]
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ include(
"detekt-parser",
"detekt-psi-utils",
"detekt-report-html",
"detekt-report-sarif",
"detekt-report-txt",
"detekt-report-xml",
"detekt-rules",
Expand Down

0 comments on commit 415ca77

Please sign in to comment.