Skip to content

Commit b91b8e1

Browse files
committed
Slightly refactor the code, add integration test
1 parent 06cf7c8 commit b91b8e1

File tree

9 files changed

+120
-32
lines changed

9 files changed

+120
-32
lines changed

src/functionalTest/kotlin/kotlinx/validation/api/BaseKotlinGradleTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import org.junit.Rule
99
import org.junit.rules.TemporaryFolder
1010
import java.io.File
1111

12-
internal open class BaseKotlinGradleTest {
12+
public open class BaseKotlinGradleTest {
1313
@Rule
1414
@JvmField
1515
internal val testProjectDir: TemporaryFolder = TemporaryFolder()
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2016-2022 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.validation.test
7+
8+
import kotlinx.validation.api.*
9+
import org.junit.*
10+
11+
class NonPublicMarkersTest : BaseKotlinGradleTest() {
12+
13+
@Test
14+
fun testIgnoredMarkersOnProperties() {
15+
val runner = test {
16+
buildGradleKts {
17+
resolve("examples/gradle/base/withPlugin.gradle.kts")
18+
resolve("examples/gradle/configuration/nonPublicMarkers/markers.gradle.kts")
19+
}
20+
21+
kotlin("Properties.kt") {
22+
resolve("examples/classes/Properties.kt")
23+
}
24+
25+
apiFile(projectName = rootProjectDir.name) {
26+
resolve("examples/classes/Properties.dump")
27+
}
28+
29+
runner {
30+
arguments.add(":apiCheck")
31+
}
32+
}
33+
34+
runner.build().apply {
35+
assertTaskSuccess(":apiCheck")
36+
}
37+
}
38+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
public final class foo/ClassWithProperties {
2+
public fun <init> ()V
3+
}
4+
5+
public abstract interface annotation class foo/HiddenField : java/lang/annotation/Annotation {
6+
}
7+
8+
public abstract interface annotation class foo/HiddenProperty : java/lang/annotation/Annotation {
9+
}
10+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2016-2022 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
package foo
6+
7+
@Target(AnnotationTarget.FIELD)
8+
annotation class HiddenField
9+
10+
@Target(AnnotationTarget.PROPERTY)
11+
annotation class HiddenProperty
12+
13+
public class ClassWithProperties {
14+
@HiddenField
15+
var bar1 = 42
16+
17+
@HiddenProperty
18+
var bar2 = 42
19+
}
20+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* Copyright 2016-2022 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
configure<kotlinx.validation.ApiValidationExtension> {
7+
nonPublicMarkers.add("foo.HiddenField")
8+
nonPublicMarkers.add("foo.HiddenProperty")
9+
}

src/main/kotlin/api/KotlinMetadataSignature.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,12 @@ data class MethodBinarySignature(
106106
}
107107
}
108108

109-
fun MethodNode.toMethodBinarySignature(propertyAnnotations: List<AnnotationNode>?) =
109+
fun MethodNode.toMethodBinarySignature(propertyAnnotations: List<AnnotationNode>) =
110110
MethodBinarySignature(
111111
JvmMethodSignature(name, desc),
112112
isPublishedApi(),
113113
AccessFlags(access),
114-
visibleAnnotations.orEmpty() + invisibleAnnotations.orEmpty() + propertyAnnotations.orEmpty()
114+
visibleAnnotations.orEmpty() + invisibleAnnotations.orEmpty() + propertyAnnotations
115115
)
116116

117117
data class FieldBinarySignature(

src/main/kotlin/api/KotlinMetadataVisibilities.kt

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ package kotlinx.validation.api
88
import kotlinx.metadata.*
99
import kotlinx.metadata.jvm.*
1010
import kotlinx.metadata.jvm.KotlinClassHeader.Companion.COMPATIBLE_METADATA_VERSION
11-
import org.objectweb.asm.tree.ClassNode
11+
import org.objectweb.asm.tree.*
1212

1313
class ClassVisibility(
1414
val name: String,
@@ -31,7 +31,11 @@ data class MemberVisibility(
3131
val member: JvmMemberSignature,
3232
val visibility: Flags?,
3333
val isReified: Boolean,
34-
val annotationHolders: PropertyAnnotationHolders
34+
/*
35+
* This property includes both annotations on the member itself,
36+
* **and**, if the member is a property, annotations on a field itself
37+
*/
38+
val propertyAnnotation: PropertyAnnotationHolders? = null
3539
)
3640

3741
private fun isPublic(visibility: Flags?, isPublishedApi: Boolean) =
@@ -81,14 +85,11 @@ fun KotlinClassMetadata?.isFileOrMultipartFacade() =
8185

8286
fun KotlinClassMetadata?.isSyntheticClass() = this is KotlinClassMetadata.SyntheticClass
8387

88+
// Auxiliary class that stores signatures of corresponding field and method for a property.
8489
class PropertyAnnotationHolders(
8590
val field: JvmMemberSignature?,
8691
val method: JvmMethodSignature?,
87-
) {
88-
companion object {
89-
val None = PropertyAnnotationHolders(null, null)
90-
}
91-
}
92+
)
9293

9394
fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility {
9495
var flags: Flags? = null
@@ -99,10 +100,10 @@ fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility
99100
signature: JvmMemberSignature?,
100101
flags: Flags,
101102
isReified: Boolean,
102-
annotationHolders: PropertyAnnotationHolders
103+
propertyAnnotation: PropertyAnnotationHolders? = null
103104
) {
104105
if (signature != null) {
105-
members.add(MemberVisibility(signature, flags, isReified, annotationHolders))
106+
members.add(MemberVisibility(signature, flags, isReified, propertyAnnotation))
106107
}
107108
}
108109

@@ -112,7 +113,7 @@ fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility
112113
flags = klass.flags
113114

114115
for (constructor in klass.constructors) {
115-
addMember(constructor.signature, constructor.flags, isReified = false, annotationHolders = PropertyAnnotationHolders.None)
116+
addMember(constructor.signature, constructor.flags, isReified = false)
116117
}
117118
}
118119
is KotlinClassMetadata.FileFacade ->
@@ -126,22 +127,23 @@ fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility
126127
fun List<KmTypeParameter>.containsReified() = any { Flag.TypeParameter.IS_REIFIED(it.flags) }
127128

128129
for (function in container.functions) {
129-
addMember(function.signature, function.flags, function.typeParameters.containsReified(), PropertyAnnotationHolders.None)
130+
addMember(function.signature, function.flags, function.typeParameters.containsReified())
130131
}
131132

132133
for (property in container.properties) {
133134
val isReified = property.typeParameters.containsReified()
134-
val annotationDelegates = PropertyAnnotationHolders(property.fieldSignature, property.syntheticMethodForAnnotations)
135+
val propertyAnnotations =
136+
PropertyAnnotationHolders(property.fieldSignature, property.syntheticMethodForAnnotations)
135137

136-
addMember(property.getterSignature, property.getterFlags, isReified, annotationDelegates)
137-
addMember(property.setterSignature, property.setterFlags, isReified, annotationDelegates)
138+
addMember(property.getterSignature, property.getterFlags, isReified, propertyAnnotations)
139+
addMember(property.setterSignature, property.setterFlags, isReified, propertyAnnotations)
138140

139141
val fieldVisibility = when {
140142
Flag.Property.IS_LATEINIT(property.flags) -> property.setterFlags
141143
property.getterSignature == null && property.setterSignature == null -> property.flags // JvmField or const case
142144
else -> flagsOf(Flag.IS_PRIVATE)
143145
}
144-
addMember(property.fieldSignature, fieldVisibility, isReified = false, annotationHolders = PropertyAnnotationHolders.None)
146+
addMember(property.fieldSignature, fieldVisibility, isReified = false)
145147
}
146148
}
147149

src/main/kotlin/api/KotlinSignaturesLoading.kt

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55

66
package kotlinx.validation.api
77

8-
import kotlinx.metadata.jvm.JvmFieldSignature
9-
import kotlinx.metadata.jvm.JvmMethodSignature
10-
import kotlinx.metadata.jvm.KotlinClassMetadata
8+
import kotlinx.metadata.jvm.*
119
import kotlinx.validation.*
1210
import org.objectweb.asm.*
1311
import org.objectweb.asm.tree.*
@@ -50,7 +48,7 @@ public fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String
5048
it.isEffectivelyPublic(classAccess, mVisibility)
5149
}.filter {
5250
/*
53-
* Filter out 'public static final Companion' field that doesn't constitutes public API.
51+
* Filter out 'public static final Companion' field that doesn't constitute public API.
5452
* For that we first check if field corresponds to the 'Companion' class and then
5553
* if companion is effectively public by itself, so the 'Companion' field has the same visibility.
5654
*/
@@ -63,21 +61,31 @@ public fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String
6361
companionClass.isEffectivelyPublic(visibility)
6462
}
6563

64+
// NB: this 'map' is O(methods + properties * methods) which may accidentally be quadratic
6665
val allMethods = methods.map {
6766
/**
6867
* For getters/setters, pull the annotations from the property
69-
*
70-
* This is either on the field if any or in a '$annotations' synthetic function
68+
* This is either on the field if any or in a '$annotations' synthetic function.
7169
*/
72-
val annotationHolders = mVisibility?.members?.get(JvmMethodSignature(it.name, it.desc))?.annotationHolders
70+
val annotationHolders =
71+
mVisibility?.members?.get(JvmMethodSignature(it.name, it.desc))?.propertyAnnotation
72+
val foundAnnotations = ArrayList<AnnotationNode>()
73+
// lookup annotations from $annotations()
74+
val syntheticPropertyMethod = annotationHolders?.method
75+
if (syntheticPropertyMethod != null) {
76+
foundAnnotations += methods
77+
.firstOrNull { it.name == syntheticPropertyMethod.name && it.desc == syntheticPropertyMethod.desc }
78+
?.visibleAnnotations ?: emptyList()
79+
}
7380

74-
val propertyAnnotations = annotationHolders?.method?.let { memberSignature->
75-
methods?.firstOrNull { it.name == memberSignature.name && it.desc == memberSignature.desc }?.visibleAnnotations
76-
}.orEmpty() + annotationHolders?.field?.let { memberSignature->
77-
fields?.firstOrNull { it.name == memberSignature.name && it.desc == memberSignature.desc }?.visibleAnnotations
78-
}.orEmpty()
81+
val backingField = annotationHolders?.field
82+
if (backingField != null) {
83+
foundAnnotations += fields
84+
.firstOrNull { it.name == backingField.name && it.desc == backingField.desc }
85+
?.visibleAnnotations ?: emptyList()
86+
}
7987

80-
it.toMethodBinarySignature(propertyAnnotations)
88+
it.toMethodBinarySignature(foundAnnotations)
8189
}
8290
// Signatures marked with @PublishedApi
8391
val publishedApiSignatures = allMethods.filter {

src/test/kotlin/tests/CasesPublicAPITest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class CasesPublicAPITest {
2121
val baseOutputPath = File("src/test/kotlin/cases")
2222
}
2323

24-
@[Rule JvmField]
24+
@Rule
25+
@JvmField
2526
val testName = TestName()
2627

2728
@Test fun annotations() { snapshotAPIAndCompare(testName.methodName) }

0 commit comments

Comments
 (0)