Skip to content

Commit

Permalink
Slightly refactor the code, add integration test
Browse files Browse the repository at this point in the history
  • Loading branch information
qwwdfsad committed May 17, 2022
1 parent 06cf7c8 commit b91b8e1
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.junit.Rule
import org.junit.rules.TemporaryFolder
import java.io.File

internal open class BaseKotlinGradleTest {
public open class BaseKotlinGradleTest {
@Rule
@JvmField
internal val testProjectDir: TemporaryFolder = TemporaryFolder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2016-2022 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 org.junit.*

class NonPublicMarkersTest : BaseKotlinGradleTest() {

@Test
fun testIgnoredMarkersOnProperties() {
val runner = test {
buildGradleKts {
resolve("examples/gradle/base/withPlugin.gradle.kts")
resolve("examples/gradle/configuration/nonPublicMarkers/markers.gradle.kts")
}

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

apiFile(projectName = rootProjectDir.name) {
resolve("examples/classes/Properties.dump")
}

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

runner.build().apply {
assertTaskSuccess(":apiCheck")
}
}
}
10 changes: 10 additions & 0 deletions src/functionalTest/resources/examples/classes/Properties.dump
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
public final class foo/ClassWithProperties {
public fun <init> ()V
}

public abstract interface annotation class foo/HiddenField : java/lang/annotation/Annotation {
}

public abstract interface annotation class foo/HiddenProperty : java/lang/annotation/Annotation {
}

20 changes: 20 additions & 0 deletions src/functionalTest/resources/examples/classes/Properties.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2016-2022 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 foo

@Target(AnnotationTarget.FIELD)
annotation class HiddenField

@Target(AnnotationTarget.PROPERTY)
annotation class HiddenProperty

public class ClassWithProperties {
@HiddenField
var bar1 = 42

@HiddenProperty
var bar2 = 42
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright 2016-2022 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> {
nonPublicMarkers.add("foo.HiddenField")
nonPublicMarkers.add("foo.HiddenProperty")
}
4 changes: 2 additions & 2 deletions src/main/kotlin/api/KotlinMetadataSignature.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ data class MethodBinarySignature(
}
}

fun MethodNode.toMethodBinarySignature(propertyAnnotations: List<AnnotationNode>?) =
fun MethodNode.toMethodBinarySignature(propertyAnnotations: List<AnnotationNode>) =
MethodBinarySignature(
JvmMethodSignature(name, desc),
isPublishedApi(),
AccessFlags(access),
visibleAnnotations.orEmpty() + invisibleAnnotations.orEmpty() + propertyAnnotations.orEmpty()
visibleAnnotations.orEmpty() + invisibleAnnotations.orEmpty() + propertyAnnotations
)

data class FieldBinarySignature(
Expand Down
32 changes: 17 additions & 15 deletions src/main/kotlin/api/KotlinMetadataVisibilities.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ package kotlinx.validation.api
import kotlinx.metadata.*
import kotlinx.metadata.jvm.*
import kotlinx.metadata.jvm.KotlinClassHeader.Companion.COMPATIBLE_METADATA_VERSION
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.*

class ClassVisibility(
val name: String,
Expand All @@ -31,7 +31,11 @@ data class MemberVisibility(
val member: JvmMemberSignature,
val visibility: Flags?,
val isReified: Boolean,
val annotationHolders: PropertyAnnotationHolders
/*
* This property includes both annotations on the member itself,
* **and**, if the member is a property, annotations on a field itself
*/
val propertyAnnotation: PropertyAnnotationHolders? = null
)

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

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

// Auxiliary class that stores signatures of corresponding field and method for a property.
class PropertyAnnotationHolders(
val field: JvmMemberSignature?,
val method: JvmMethodSignature?,
) {
companion object {
val None = PropertyAnnotationHolders(null, null)
}
}
)

fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility {
var flags: Flags? = null
Expand All @@ -99,10 +100,10 @@ fun KotlinClassMetadata.toClassVisibility(classNode: ClassNode): ClassVisibility
signature: JvmMemberSignature?,
flags: Flags,
isReified: Boolean,
annotationHolders: PropertyAnnotationHolders
propertyAnnotation: PropertyAnnotationHolders? = null
) {
if (signature != null) {
members.add(MemberVisibility(signature, flags, isReified, annotationHolders))
members.add(MemberVisibility(signature, flags, isReified, propertyAnnotation))
}
}

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

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

for (function in container.functions) {
addMember(function.signature, function.flags, function.typeParameters.containsReified(), PropertyAnnotationHolders.None)
addMember(function.signature, function.flags, function.typeParameters.containsReified())
}

for (property in container.properties) {
val isReified = property.typeParameters.containsReified()
val annotationDelegates = PropertyAnnotationHolders(property.fieldSignature, property.syntheticMethodForAnnotations)
val propertyAnnotations =
PropertyAnnotationHolders(property.fieldSignature, property.syntheticMethodForAnnotations)

addMember(property.getterSignature, property.getterFlags, isReified, annotationDelegates)
addMember(property.setterSignature, property.setterFlags, isReified, annotationDelegates)
addMember(property.getterSignature, property.getterFlags, isReified, propertyAnnotations)
addMember(property.setterSignature, property.setterFlags, isReified, propertyAnnotations)

val fieldVisibility = when {
Flag.Property.IS_LATEINIT(property.flags) -> property.setterFlags
property.getterSignature == null && property.setterSignature == null -> property.flags // JvmField or const case
else -> flagsOf(Flag.IS_PRIVATE)
}
addMember(property.fieldSignature, fieldVisibility, isReified = false, annotationHolders = PropertyAnnotationHolders.None)
addMember(property.fieldSignature, fieldVisibility, isReified = false)
}
}

Expand Down
34 changes: 21 additions & 13 deletions src/main/kotlin/api/KotlinSignaturesLoading.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@

package kotlinx.validation.api

import kotlinx.metadata.jvm.JvmFieldSignature
import kotlinx.metadata.jvm.JvmMethodSignature
import kotlinx.metadata.jvm.KotlinClassMetadata
import kotlinx.metadata.jvm.*
import kotlinx.validation.*
import org.objectweb.asm.*
import org.objectweb.asm.tree.*
Expand Down Expand Up @@ -50,7 +48,7 @@ public fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String
it.isEffectivelyPublic(classAccess, mVisibility)
}.filter {
/*
* Filter out 'public static final Companion' field that doesn't constitutes public API.
* Filter out 'public static final Companion' field that doesn't constitute public API.
* For that we first check if field corresponds to the 'Companion' class and then
* if companion is effectively public by itself, so the 'Companion' field has the same visibility.
*/
Expand All @@ -63,21 +61,31 @@ public fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String
companionClass.isEffectivelyPublic(visibility)
}

// NB: this 'map' is O(methods + properties * methods) which may accidentally be quadratic
val allMethods = methods.map {
/**
* For getters/setters, pull the annotations from the property
*
* This is either on the field if any or in a '$annotations' synthetic function
* This is either on the field if any or in a '$annotations' synthetic function.
*/
val annotationHolders = mVisibility?.members?.get(JvmMethodSignature(it.name, it.desc))?.annotationHolders
val annotationHolders =
mVisibility?.members?.get(JvmMethodSignature(it.name, it.desc))?.propertyAnnotation
val foundAnnotations = ArrayList<AnnotationNode>()
// lookup annotations from $annotations()
val syntheticPropertyMethod = annotationHolders?.method
if (syntheticPropertyMethod != null) {
foundAnnotations += methods
.firstOrNull { it.name == syntheticPropertyMethod.name && it.desc == syntheticPropertyMethod.desc }
?.visibleAnnotations ?: emptyList()
}

val propertyAnnotations = annotationHolders?.method?.let { memberSignature->
methods?.firstOrNull { it.name == memberSignature.name && it.desc == memberSignature.desc }?.visibleAnnotations
}.orEmpty() + annotationHolders?.field?.let { memberSignature->
fields?.firstOrNull { it.name == memberSignature.name && it.desc == memberSignature.desc }?.visibleAnnotations
}.orEmpty()
val backingField = annotationHolders?.field
if (backingField != null) {
foundAnnotations += fields
.firstOrNull { it.name == backingField.name && it.desc == backingField.desc }
?.visibleAnnotations ?: emptyList()
}

it.toMethodBinarySignature(propertyAnnotations)
it.toMethodBinarySignature(foundAnnotations)
}
// Signatures marked with @PublishedApi
val publishedApiSignatures = allMethods.filter {
Expand Down
3 changes: 2 additions & 1 deletion src/test/kotlin/tests/CasesPublicAPITest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ class CasesPublicAPITest {
val baseOutputPath = File("src/test/kotlin/cases")
}

@[Rule JvmField]
@Rule
@JvmField
val testName = TestName()

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

0 comments on commit b91b8e1

Please sign in to comment.