Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce baseline tooling api #5239

Merged
merged 1 commit into from Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
}