Skip to content

Commit

Permalink
- introduce new configuration option to ignore by pattern
Browse files Browse the repository at this point in the history
- introduce tests to verify excluding by pattern works
  • Loading branch information
mikepenz committed May 31, 2024
1 parent 3a70033 commit 5ae0584
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 2 deletions.
99 changes: 99 additions & 0 deletions src/functionalTest/kotlin/kotlinx/validation/test/IgnoredTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2016-2020 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/

package kotlinx.validation.test

import kotlinx.validation.api.*
import kotlinx.validation.api.BaseKotlinGradleTest
import kotlinx.validation.api.assertTaskSuccess
import kotlinx.validation.api.buildGradleKts
import kotlinx.validation.api.kotlin
import kotlinx.validation.api.readFileList
import kotlinx.validation.api.resolve
import kotlinx.validation.api.runner
import kotlinx.validation.api.test
import org.assertj.core.api.Assertions
import org.junit.Test
import kotlin.test.assertTrue

internal class IgnoredTests : BaseKotlinGradleTest() {

@Test
fun `apiCheck should succeed, when given class is not in api-File, but is ignored via ignored pattern`() {
val runner = test {
buildGradleKts {
resolve("/examples/gradle/base/withPlugin.gradle.kts")
resolve("/examples/gradle/configuration/ignored/oneValidPattern.gradle.kts")
}

kotlin("BuildConfig.kt") {
resolve("/examples/classes/BuildConfig.kt")
}

emptyApiFile(projectName = rootProjectDir.name)

runner {
arguments.add(":apiCheck")
}
}

runner.build().apply {
assertTaskSuccess(":apiCheck")
}
}

@Test
fun `apiCheck should succeed, when given class is not in api-File, but is ignored via ignored pattern (based on package)`() {
val runner = test {
buildGradleKts {
resolve("/examples/gradle/base/withPlugin.gradle.kts")
resolve("/examples/gradle/configuration/ignored/oneValidPackagePattern.gradle.kts")
}

kotlin("BuildConfig.kt") {
resolve("/examples/classes/BuildConfig.kt")
}

emptyApiFile(projectName = rootProjectDir.name)

runner {
arguments.add(":apiCheck")
}
}

runner.build().apply {
assertTaskSuccess(":apiCheck")
}
}

@Test
fun `apiDump should not dump ignored classes, when class is excluded via ignored pattern`() {
val runner = test {
buildGradleKts {
resolve("/examples/gradle/base/withPlugin.gradle.kts")
resolve("/examples/gradle/configuration/ignored/oneValidPatternPackageClass.gradle.kts")
}
kotlin("BuildConfig.kt") {
resolve("/examples/classes/BuildConfig.kt")
}
kotlin("AnotherBuildConfig.kt") {
resolve("/examples/classes/AnotherBuildConfig.kt")
}

runner {
arguments.add(":apiDump")
}
}

runner.build().apply {
assertTaskSuccess(":apiDump")

assertTrue(rootProjectApiDump.exists(), "api dump file should exist")

val expected = readFileList("/examples/classes/AnotherBuildConfig.dump")
Assertions.assertThat(rootProjectApiDump.readText()).isEqualToIgnoringNewLines(expected)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright 2016-2024 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/

configure<kotlinx.validation.ApiValidationExtension> {
ignored.add("com\\/company\\/.+")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
configure<kotlinx.validation.ApiValidationExtension> {
ignored.add(".+\\/BuildConfig")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
configure<kotlinx.validation.ApiValidationExtension> {
ignored.add(".+\\/company\\/BuildConfig")
}
8 changes: 8 additions & 0 deletions src/main/kotlin/ApiValidationExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ public open class ApiValidationExtension {
*/
public var ignoredClasses: MutableSet<String> = HashSet()

/**
* Defines a regex pattern used to define classes and packages that are ignored by the API check.
*
* Example of such a package could be `.+\/internal\/.+`.
* An example of such a class could be `.+\/BuildConfig`
*/
public var ignored: MutableSet<String> = HashSet()

/**
* Fully qualified names of annotations that can be used to explicitly mark public declarations.
* If at least one of [publicMarkers], [publicPackages] or [publicClasses] is defined,
Expand Down
7 changes: 7 additions & 0 deletions src/main/kotlin/BuildTaskBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import java.io.File
import java.util.regex.Pattern

public abstract class BuildTaskBase : DefaultTask() {
private val extension = project.apiValidationExtensionOrNull
Expand All @@ -35,6 +36,12 @@ public abstract class BuildTaskBase : DefaultTask() {
get() = _ignoredClasses ?: extension?.ignoredClasses ?: emptySet()
set(value) { _ignoredClasses = value }

private var _ignored: Set<Pattern>? = null
@get:Input
public var ignored : Set<Pattern>
get() = _ignored ?: extension?.ignored?.map { Pattern.compile(it) }?.toSet() ?: emptySet()
set(value) { _ignored = value }

private var _publicPackages: Set<String>? = null
@get:Input
public var publicPackages: Set<String>
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/KotlinApiBuildTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public open class KotlinApiBuildTask @Inject constructor(
val filteredSignatures = signatures
.retainExplicitlyIncludedIfDeclared(publicPackages + publicPackagesNames,
publicClasses, publicMarkers)
.filterOutNonPublic(ignoredPackages + ignoredPackagesNames, ignoredClasses)
.filterOutNonPublic(ignored, ignoredPackages + ignoredPackagesNames, ignoredClasses)
.filterOutAnnotated(nonPublicMarkers.map(::replaceDots).toSet())

outputApiFile.bufferedWriter().use { writer ->
Expand Down
6 changes: 5 additions & 1 deletion src/main/kotlin/api/KotlinSignaturesLoading.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.objectweb.asm.tree.*
import java.io.*
import java.util.*
import java.util.jar.*
import java.util.regex.Pattern

@ExternalApi
@Suppress("unused")
Expand Down Expand Up @@ -247,6 +248,7 @@ public fun List<ClassBinarySignature>.extractAnnotatedPackages(targetAnnotations

@ExternalApi
public fun List<ClassBinarySignature>.filterOutNonPublic(
nonPublic: Collection<Pattern> = emptyList(),
nonPublicPackages: Collection<String> = emptyList(),
nonPublicClasses: Collection<String> = emptyList()
): List<ClassBinarySignature> {
Expand All @@ -255,6 +257,8 @@ public fun List<ClassBinarySignature>.filterOutNonPublic(

val classByName = associateBy { it.name }

fun ClassBinarySignature.isNonPublic() = nonPublic.any { it.matcher(name).matches() }

fun ClassBinarySignature.isPublicAndAccessible(): Boolean =
isEffectivelyPublic &&
(outerName == null || classByName[outerName]?.let { outerClass ->
Expand All @@ -281,7 +285,7 @@ public fun List<ClassBinarySignature>.filterOutNonPublic(
}

return filter {
!it.isInPackages(nonPublicPackagePaths) && !it.isInClasses(excludedClasses) && it.isPublicAndAccessible()
!it.isInPackages(nonPublicPackagePaths) && !it.isInClasses(excludedClasses) && it.isPublicAndAccessible() && !it.isNonPublic()
}
.map { it.flattenNonPublicBases() }
.filterNot { it.isNotUsedWhenEmpty && it.memberSignatures.isEmpty() }
Expand Down

0 comments on commit 5ae0584

Please sign in to comment.