Skip to content

Commit

Permalink
Introduce baseline tooling api (#5239)
Browse files Browse the repository at this point in the history
  • Loading branch information
arturbosch committed Aug 24, 2022
1 parent 4288574 commit d21596a
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 31 deletions.
Expand Up @@ -11,7 +11,7 @@ class BaselineFacade {

fun transformResult(baselineFile: Path, result: Detektion): Detektion {
return if (baselineExists(baselineFile)) {
BaselineFilteredResult(result, Baseline.load(baselineFile))
BaselineFilteredResult(result, DefaultBaseline.load(baselineFile))
} else {
result
}
Expand All @@ -20,14 +20,15 @@ class BaselineFacade {
fun createOrUpdate(baselineFile: Path, findings: List<Finding>) {
val ids = findings.map { it.baselineId }.toSortedSet()
val oldBaseline = if (baselineExists(baselineFile)) {
Baseline.load(baselineFile)
DefaultBaseline.load(baselineFile)
} else {
Baseline(emptySet(), emptySet())
DefaultBaseline(emptySet(), emptySet())
}
val baseline = oldBaseline.copy(currentIssues = ids)
val baselineFormat = BaselineFormat()
val baseline = baselineFormat.of(oldBaseline.manuallySuppressedIssues, ids)
if (oldBaseline != baseline) {
baselineFile.parent?.let { Files.createDirectories(it) }
BaselineFormat().write(baseline, baselineFile)
baselineFormat.write(baselineFile, baseline)
}
}

Expand Down
@@ -1,12 +1,13 @@
package io.gitlab.arturbosch.detekt.core.baseline

import io.github.detekt.tooling.api.Baseline
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.api.RuleSetId

internal class BaselineFilteredResult(
result: Detektion,
private val baseline: Baseline
private val baseline: Baseline,
) : Detektion by result {

override val findings: Map<RuleSetId, List<Finding>> = result.findings
Expand Down
@@ -1,5 +1,10 @@
package io.gitlab.arturbosch.detekt.core.baseline

import io.github.detekt.tooling.api.Baseline
import io.github.detekt.tooling.api.BaselineProvider
import io.github.detekt.tooling.api.FindingId
import io.github.detekt.tooling.api.FindingsIdList
import io.gitlab.arturbosch.detekt.api.Finding
import org.xml.sax.SAXParseException
import java.nio.file.Files
import java.nio.file.Path
Expand All @@ -8,16 +13,21 @@ import javax.xml.parsers.SAXParserFactory
import javax.xml.stream.XMLStreamException
import javax.xml.stream.XMLStreamWriter

internal class BaselineFormat {
internal class BaselineFormat : BaselineProvider {

private val XMLStreamException.positions
get() = location.lineNumber to location.columnNumber

class InvalidState(msg: String, error: Throwable) : IllegalStateException(msg, error)

fun read(path: Path): Baseline {
override fun id(finding: Finding): FindingId = finding.baselineId

override fun of(manuallySuppressedIssues: FindingsIdList, currentIssues: FindingsIdList): DefaultBaseline =
DefaultBaseline(manuallySuppressedIssues, currentIssues)

override fun read(sourcePath: Path): DefaultBaseline {
try {
Files.newInputStream(path).use {
Files.newInputStream(sourcePath).use {
val reader = SAXParserFactory.newInstance()
.apply {
setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
Expand All @@ -33,9 +43,9 @@ internal class BaselineFormat {
}
}

fun write(baseline: Baseline, path: Path) {
override fun write(targetPath: Path, baseline: Baseline) {
try {
Files.newBufferedWriter(path).addFinalNewLine().use {
Files.newBufferedWriter(targetPath).addFinalNewLine().use {
it.streamXml().prettyPrinter().save(baseline)
}
} catch (error: XMLStreamException) {
Expand Down
Expand Up @@ -11,7 +11,7 @@ internal class BaselineHandler : DefaultHandler() {
private val currentIssues = mutableSetOf<String>()
private val manuallySuppressedIssues = mutableSetOf<String>()

internal fun createBaseline() = Baseline(manuallySuppressedIssues, currentIssues)
internal fun createBaseline() = DefaultBaseline(manuallySuppressedIssues, currentIssues)

override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
when (qName) {
Expand Down
@@ -1,19 +1,19 @@
package io.gitlab.arturbosch.detekt.core.baseline

import io.github.detekt.tooling.api.Baseline
import io.github.detekt.tooling.api.FindingId
import io.github.detekt.tooling.api.FindingsIdList
import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.core.exists
import io.gitlab.arturbosch.detekt.core.isFile
import java.nio.file.Path

internal typealias FindingsIdList = Set<String>
internal typealias FindingId = String
internal data class DefaultBaseline(
override val manuallySuppressedIssues: FindingsIdList,
override val currentIssues: FindingsIdList,
) : Baseline {

internal data class Baseline(
val manuallySuppressedIssues: FindingsIdList,
val currentIssues: FindingsIdList
) {

fun contains(id: FindingId): Boolean =
override fun contains(id: FindingId): Boolean =
currentIssues.contains(id) || manuallySuppressedIssues.contains(id)

companion object {
Expand Down
@@ -0,0 +1 @@
io.gitlab.arturbosch.detekt.core.baseline.BaselineFormat
Expand Up @@ -28,13 +28,13 @@ class BaselineFilteredResultSpec {

@Test
fun `does return the same finding on empty baseline`() {
val actual = BaselineFilteredResult(result, Baseline(emptySet(), emptySet()))
val actual = BaselineFilteredResult(result, DefaultBaseline(emptySet(), emptySet()))
assertThat(actual.findings).hasSize(3)
}

@Test
fun `filters with an existing baseline file`() {
val baseline = Baseline.load(baselineFile)
val baseline = DefaultBaseline.load(baselineFile)
val actual = BaselineFilteredResult(result, baseline)
// Note: Detektion works with Map<RuleSetId, List<Finding>
// but the TestDetektion maps the RuleId as RuleSetId
Expand Down
Expand Up @@ -2,6 +2,8 @@ package io.gitlab.arturbosch.detekt.core.baseline

import io.github.detekt.test.utils.createTempFileForTest
import io.github.detekt.test.utils.resourceAsPath
import io.github.detekt.tooling.api.BaselineProvider
import io.gitlab.arturbosch.detekt.test.createFinding
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatIllegalStateException
import org.assertj.core.api.Assertions.assertThatThrownBy
Expand All @@ -11,6 +13,34 @@ import java.nio.file.Files

class BaselineFormatSpec {

@Nested
inner class `implements tooling api` {

@Test
fun `core implements provider`() {
assertThat(BaselineProvider.load()).isInstanceOf(BaselineFormat::class.java)
}

@Test
fun `provider allows to query finding baseline ids`() {
assertThat(BaselineProvider.load().id(createFinding())).isEqualTo("TestSmell:TestEntitySignature")
}

@Test
fun `reads and writes baselines`() {
val provider = BaselineProvider.load()
val path = resourceAsPath("/baseline_feature/valid-baseline.xml")

val referenceBaseline = provider.read(path)
val tempFile = createTempFileForTest("baseline1", ".xml")
provider.write(tempFile, referenceBaseline)
val actualBaseline = provider.read(path)

assertThat(actualBaseline.currentIssues).containsAll(referenceBaseline.currentIssues)
assertThat(actualBaseline.manuallySuppressedIssues).containsAll(referenceBaseline.manuallySuppressedIssues)
}
}

@Nested
inner class `read a baseline file` {

Expand Down Expand Up @@ -57,14 +87,14 @@ class BaselineFormatSpec {
@Nested
inner class `writes a baseline file` {

private val savedBaseline = Baseline(setOf("4", "2", "2"), setOf("1", "2", "3"))
private val savedBaseline = DefaultBaseline(setOf("4", "2", "2"), setOf("1", "2", "3"))

@Test
fun `has a new line at the end of the written baseline file`() {
val tempFile = createTempFileForTest("baseline1", ".xml")

val format = BaselineFormat()
format.write(savedBaseline, tempFile)
format.write(tempFile, savedBaseline)
val bytes = Files.readAllBytes(tempFile)
val content = String(bytes, Charsets.UTF_8)

Expand All @@ -76,7 +106,7 @@ class BaselineFormatSpec {
val tempFile = createTempFileForTest("baseline-saved", ".xml")

val format = BaselineFormat()
format.write(savedBaseline, tempFile)
format.write(tempFile, savedBaseline)
val loadedBaseline = format.read(tempFile)

assertThat(loadedBaseline).isEqualTo(savedBaseline)
Expand Down
Expand Up @@ -55,29 +55,29 @@ class BaselineResultMappingSpec {

@Test
fun `should not update an existing baseline file if option configured as false`() {
val existing = Baseline.load(existingBaselineFile)
val existing = DefaultBaseline.load(existingBaselineFile)
val mapping = resultMapping(
baselineFile = existingBaselineFile,
createBaseline = false,
)

mapping.transformFindings(findings)

val changed = Baseline.load(existingBaselineFile)
val changed = DefaultBaseline.load(existingBaselineFile)
assertThat(existing).isEqualTo(changed)
}

@Test
fun `should not update an existing baseline file if option is not configured`() {
val existing = Baseline.load(existingBaselineFile)
val existing = DefaultBaseline.load(existingBaselineFile)
val mapping = resultMapping(
baselineFile = existingBaselineFile,
createBaseline = null,
)

mapping.transformFindings(findings)

val changed = Baseline.load(existingBaselineFile)
val changed = DefaultBaseline.load(existingBaselineFile)
assertThat(existing).isEqualTo(changed)
}

Expand Down Expand Up @@ -108,15 +108,15 @@ class BaselineResultMappingSpec {
@Test
fun `should update an existing baseline file if a file is configured`() {
Files.copy(existingBaselineFile, baselineFile)
val existing = Baseline.load(baselineFile)
val existing = DefaultBaseline.load(baselineFile)
val mapping = resultMapping(
baselineFile = baselineFile,
createBaseline = true,
)

mapping.transformFindings(findings)

val changed = Baseline.load(baselineFile)
val changed = DefaultBaseline.load(baselineFile)
assertThat(existing).isNotEqualTo(changed)
}
}
Expand Down
19 changes: 19 additions & 0 deletions detekt-tooling/api/detekt-tooling.api
Expand Up @@ -7,6 +7,25 @@ public final class io/github/detekt/tooling/api/AnalysisResultKt {
public static final fun exitCode (Lio/github/detekt/tooling/api/AnalysisResult;)I
}

public abstract interface class io/github/detekt/tooling/api/Baseline {
public abstract fun contains (Ljava/lang/String;)Z
public abstract fun getCurrentIssues ()Ljava/util/Set;
public abstract fun getManuallySuppressedIssues ()Ljava/util/Set;
}

public abstract interface class io/github/detekt/tooling/api/BaselineProvider {
public static final field Companion Lio/github/detekt/tooling/api/BaselineProvider$Companion;
public abstract fun id (Lio/gitlab/arturbosch/detekt/api/Finding;)Ljava/lang/String;
public abstract fun of (Ljava/util/Set;Ljava/util/Set;)Lio/github/detekt/tooling/api/Baseline;
public abstract fun read (Ljava/nio/file/Path;)Lio/github/detekt/tooling/api/Baseline;
public abstract fun write (Ljava/nio/file/Path;Lio/github/detekt/tooling/api/Baseline;)V
}

public final class io/github/detekt/tooling/api/BaselineProvider$Companion {
public final fun load (Ljava/lang/ClassLoader;)Lio/github/detekt/tooling/api/BaselineProvider;
public static synthetic fun load$default (Lio/github/detekt/tooling/api/BaselineProvider$Companion;Ljava/lang/ClassLoader;ILjava/lang/Object;)Lio/github/detekt/tooling/api/BaselineProvider;
}

public abstract interface class io/github/detekt/tooling/api/DefaultConfigurationProvider {
public static final field Companion Lio/github/detekt/tooling/api/DefaultConfigurationProvider$Companion;
public abstract fun copy (Ljava/nio/file/Path;)V
Expand Down
@@ -0,0 +1,31 @@
package io.github.detekt.tooling.api

import io.gitlab.arturbosch.detekt.api.Finding
import java.nio.file.Path
import java.util.ServiceLoader

interface BaselineProvider {

fun id(finding: Finding): FindingId
fun of(manuallySuppressedIssues: FindingsIdList, currentIssues: FindingsIdList): Baseline
fun read(sourcePath: Path): Baseline
fun write(targetPath: Path, baseline: Baseline)

companion object {

fun load(
classLoader: ClassLoader = BaselineProvider::class.java.classLoader,
): BaselineProvider = ServiceLoader.load(BaselineProvider::class.java, classLoader).first()
}
}

typealias FindingsIdList = Set<String>
typealias FindingId = String

interface Baseline {

val manuallySuppressedIssues: FindingsIdList
val currentIssues: FindingsIdList

fun contains(id: FindingId): Boolean
}

0 comments on commit d21596a

Please sign in to comment.