diff options
Diffstat (limited to 'interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet')
9 files changed, 3343 insertions, 0 deletions
diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFile.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFile.kt new file mode 100644 index 00000000..6eb40a88 --- /dev/null +++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFile.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:JvmName("FacadeFile") +@file:FileAnnotation("file annotations!") + +package com.squareup.kotlinpoet.metadata.specs + +import kotlin.annotation.AnnotationTarget.FILE + +@Target(FILE) +annotation class FileAnnotation(val value: String) + +@JvmName("jvmStaticFunction") +fun jvmNameFunction() { +} + +fun regularFun() { +} + +@Synchronized +fun synchronizedFun() { +} + +@JvmOverloads +fun jvmOverloads( + param1: String, + optionalParam2: String = "", + nullableParam3: String? = null, +) { +} + +val BOOL_PROP = false +val BINARY_PROP = 0b00001011 +val INT_PROP = 1 +val UNDERSCORES_PROP = 1_000_000 +val HEX_PROP = 0x0F +val UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E +val LONG_PROP = 1L +val FLOAT_PROP = 1.0f +val DOUBLE_PROP = 1.0 +val STRING_PROP = "prop" +var VAR_BOOL_PROP = false +var VAR_BINARY_PROP = 0b00001011 +var VAR_INT_PROP = 1 +var VAR_UNDERSCORES_PROP = 1_000_000 +var VAR_HEX_PROP = 0x0F +var VAR_UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E +var VAR_LONG_PROP = 1L +var VAR_FLOAT_PROP = 1.0f +var VAR_DOUBLE_PROP = 1.0 +var VAR_STRING_PROP = "prop" + +const val CONST_BOOL_PROP = false +const val CONST_BINARY_PROP = 0b00001011 +const val CONST_INT_PROP = 1 +const val CONST_UNDERSCORES_PROP = 1_000_000 +const val CONST_HEX_PROP = 0x0F +const val CONST_UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E +const val CONST_LONG_PROP = 1L +const val CONST_FLOAT_PROP = 1.0f +const val CONST_DOUBLE_PROP = 1.0 +const val CONST_STRING_PROP = "prop" + +@JvmField +@JvmSynthetic +val syntheticFieldProperty: kotlin.String? = null + +@field:JvmSynthetic +val syntheticProperty: kotlin.String? = null + +@get:JvmSynthetic +val syntheticPropertyGet: kotlin.String? = null + +@get:JvmSynthetic +@set:JvmSynthetic +var syntheticPropertyGetAndSet: kotlin.String? = null + +@set:JvmSynthetic +var syntheticPropertySet: kotlin.String? = null + +typealias FacadeTypeAliasName = String +typealias FacadeGenericTypeAlias = List<String> +typealias FacadeNestedTypeAlias = List<GenericTypeAlias> diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFileTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFileTest.kt new file mode 100644 index 00000000..adea853f --- /dev/null +++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/FacadeFileTest.kt @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2021 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.kotlinpoet.metadata.specs + +import com.google.common.truth.Truth.assertThat +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview +import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType.ELEMENTS +import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType.REFLECTIVE +import org.junit.Test + +@KotlinPoetMetadataPreview +class FacadeFileTest : MultiClassInspectorTest() { + + @IgnoreForHandlerType( + handlerType = ELEMENTS, + reason = "Elements can detect JvmOverloads, JvmName not possible in reflection", + ) + @Test + fun facadeFile_reflective() { + val fileSpec = Class.forName( + "com.squareup.kotlinpoet.metadata.specs.FacadeFile", + ).kotlin.toFileSpecWithTestHandler() + assertThat(fileSpec.name).isEqualTo("FacadeFile") + //language=kotlin + assertThat(fileSpec.trimmedToString()).isEqualTo( + """ + @file:JvmName(name = "FacadeFile") + @file:FileAnnotation(value = "file annotations!") + + package com.squareup.kotlinpoet.metadata.specs + + import com.squareup.kotlinpoet.metadata.specs.FileAnnotation + import kotlin.Boolean + import kotlin.Double + import kotlin.Float + import kotlin.Int + import kotlin.Long + import kotlin.String + import kotlin.Unit + import kotlin.collections.List + import kotlin.jvm.JvmField + import kotlin.jvm.JvmName + import kotlin.jvm.JvmSynthetic + import kotlin.jvm.Synchronized + + @JvmName(name = "jvmStaticFunction") + public fun jvmNameFunction(): Unit { + } + + public fun jvmOverloads( + param1: String, + optionalParam2: String = throw NotImplementedError("Stub!"), + nullableParam3: String? = throw NotImplementedError("Stub!"), + ): Unit { + } + + public fun regularFun(): Unit { + } + + @Synchronized + public fun synchronizedFun(): Unit { + } + + public val BINARY_PROP: Int = 11 + + public val BOOL_PROP: Boolean = false + + public const val CONST_BINARY_PROP: Int = 11 + + public const val CONST_BOOL_PROP: Boolean = false + + public const val CONST_DOUBLE_PROP: Double = 1.0 + + public const val CONST_FLOAT_PROP: Float = 1.0F + + public const val CONST_HEX_PROP: Int = 15 + + public const val CONST_INT_PROP: Int = 1 + + public const val CONST_LONG_PROP: Long = 1L + + public const val CONST_STRING_PROP: String = "prop" + + public const val CONST_UNDERSCORES_HEX_PROP: Long = 4_293_713_502L + + public const val CONST_UNDERSCORES_PROP: Int = 1_000_000 + + public val DOUBLE_PROP: Double = 1.0 + + public val FLOAT_PROP: Float = 1.0F + + public val HEX_PROP: Int = 15 + + public val INT_PROP: Int = 1 + + public val LONG_PROP: Long = 1L + + public val STRING_PROP: String = "prop" + + public val UNDERSCORES_HEX_PROP: Long = 4_293_713_502L + + public val UNDERSCORES_PROP: Int = 1_000_000 + + public var VAR_BINARY_PROP: Int = throw NotImplementedError("Stub!") + + public var VAR_BOOL_PROP: Boolean = throw NotImplementedError("Stub!") + + public var VAR_DOUBLE_PROP: Double = throw NotImplementedError("Stub!") + + public var VAR_FLOAT_PROP: Float = throw NotImplementedError("Stub!") + + public var VAR_HEX_PROP: Int = throw NotImplementedError("Stub!") + + public var VAR_INT_PROP: Int = throw NotImplementedError("Stub!") + + public var VAR_LONG_PROP: Long = throw NotImplementedError("Stub!") + + public var VAR_STRING_PROP: String = throw NotImplementedError("Stub!") + + public var VAR_UNDERSCORES_HEX_PROP: Long = throw NotImplementedError("Stub!") + + public var VAR_UNDERSCORES_PROP: Int = throw NotImplementedError("Stub!") + + @field:JvmSynthetic + @JvmField + public val syntheticFieldProperty: String? = null + + @field:JvmSynthetic + public val syntheticProperty: String? = null + + @get:JvmSynthetic + public val syntheticPropertyGet: String? = null + + @get:JvmSynthetic + @set:JvmSynthetic + public var syntheticPropertyGetAndSet: String? = null + + @set:JvmSynthetic + public var syntheticPropertySet: String? = null + + public typealias FacadeGenericTypeAlias = List<String> + + public typealias FacadeNestedTypeAlias = List<GenericTypeAlias> + + public typealias FacadeTypeAliasName = String + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + handlerType = REFLECTIVE, + reason = "Elements can detect JvmOverloads, JvmName not possible in reflection", + ) + @Test + fun facadeFile_elements() { + val fileSpec = Class.forName( + "com.squareup.kotlinpoet.metadata.specs.FacadeFile", + ).kotlin.toFileSpecWithTestHandler() + assertThat(fileSpec.name).isEqualTo("FacadeFile") + //language=kotlin + assertThat(fileSpec.trimmedToString()).isEqualTo( + """ + @file:FileAnnotation(value = "file annotations!") + @file:JvmName(name = "FacadeFile") + + package com.squareup.kotlinpoet.metadata.specs + + import com.squareup.kotlinpoet.metadata.specs.FileAnnotation + import kotlin.Boolean + import kotlin.Double + import kotlin.Float + import kotlin.Int + import kotlin.Long + import kotlin.String + import kotlin.Unit + import kotlin.collections.List + import kotlin.jvm.JvmName + import kotlin.jvm.JvmOverloads + import kotlin.jvm.JvmSynthetic + import kotlin.jvm.Synchronized + + @JvmName(name = "jvmStaticFunction") + public fun jvmNameFunction(): Unit { + } + + @JvmOverloads + public fun jvmOverloads( + param1: String, + optionalParam2: String = throw NotImplementedError("Stub!"), + nullableParam3: String? = throw NotImplementedError("Stub!"), + ): Unit { + } + + public fun regularFun(): Unit { + } + + @Synchronized + public fun synchronizedFun(): Unit { + } + + public val BINARY_PROP: Int = throw NotImplementedError("Stub!") + + public val BOOL_PROP: Boolean = throw NotImplementedError("Stub!") + + public const val CONST_BINARY_PROP: Int = 11 + + public const val CONST_BOOL_PROP: Boolean = false + + public const val CONST_DOUBLE_PROP: Double = 1.0 + + public const val CONST_FLOAT_PROP: Float = 1.0F + + public const val CONST_HEX_PROP: Int = 15 + + public const val CONST_INT_PROP: Int = 1 + + public const val CONST_LONG_PROP: Long = 1L + + public const val CONST_STRING_PROP: String = "prop" + + public const val CONST_UNDERSCORES_HEX_PROP: Long = 4_293_713_502L + + public const val CONST_UNDERSCORES_PROP: Int = 1_000_000 + + public val DOUBLE_PROP: Double = throw NotImplementedError("Stub!") + + public val FLOAT_PROP: Float = throw NotImplementedError("Stub!") + + public val HEX_PROP: Int = throw NotImplementedError("Stub!") + + public val INT_PROP: Int = throw NotImplementedError("Stub!") + + public val LONG_PROP: Long = throw NotImplementedError("Stub!") + + public val STRING_PROP: String = throw NotImplementedError("Stub!") + + public val UNDERSCORES_HEX_PROP: Long = throw NotImplementedError("Stub!") + + public val UNDERSCORES_PROP: Int = throw NotImplementedError("Stub!") + + public var VAR_BINARY_PROP: Int = throw NotImplementedError("Stub!") + + public var VAR_BOOL_PROP: Boolean = throw NotImplementedError("Stub!") + + public var VAR_DOUBLE_PROP: Double = throw NotImplementedError("Stub!") + + public var VAR_FLOAT_PROP: Float = throw NotImplementedError("Stub!") + + public var VAR_HEX_PROP: Int = throw NotImplementedError("Stub!") + + public var VAR_INT_PROP: Int = throw NotImplementedError("Stub!") + + public var VAR_LONG_PROP: Long = throw NotImplementedError("Stub!") + + public var VAR_STRING_PROP: String = throw NotImplementedError("Stub!") + + public var VAR_UNDERSCORES_HEX_PROP: Long = throw NotImplementedError("Stub!") + + public var VAR_UNDERSCORES_PROP: Int = throw NotImplementedError("Stub!") + + @field:JvmSynthetic + public val syntheticFieldProperty: String? = null + + @field:JvmSynthetic + public val syntheticProperty: String? = null + + @get:JvmSynthetic + public val syntheticPropertyGet: String? = null + + @get:JvmSynthetic + @set:JvmSynthetic + public var syntheticPropertyGetAndSet: String? = null + + @set:JvmSynthetic + public var syntheticPropertySet: String? = null + + public typealias FacadeGenericTypeAlias = List<String> + + public typealias FacadeNestedTypeAlias = List<GenericTypeAlias> + + public typealias FacadeTypeAliasName = String + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + handlerType = ELEMENTS, + reason = "JvmName not possible in reflection", + ) + @Test + fun noJvmName_reflective() { + val fileSpec = Class.forName( + "com.squareup.kotlinpoet.metadata.specs.NoJvmNameFacadeFileKt", + ).kotlin.toFileSpecWithTestHandler() + assertThat(fileSpec.name).isEqualTo("NoJvmNameFacadeFile") + //language=kotlin + assertThat(fileSpec.trimmedToString()).isEqualTo( + """ + package com.squareup.kotlinpoet.metadata.specs + + import kotlin.String + + public val prop: String = "" + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + handlerType = REFLECTIVE, + reason = "JvmName not possible in reflection", + ) + @Test + fun noJvmName_elements() { + val fileSpec = Class.forName( + "com.squareup.kotlinpoet.metadata.specs.NoJvmNameFacadeFileKt", + ).kotlin.toFileSpecWithTestHandler() + assertThat(fileSpec.name).isEqualTo("NoJvmNameFacadeFile") + //language=kotlin + assertThat(fileSpec.trimmedToString()).isEqualTo( + """ + package com.squareup.kotlinpoet.metadata.specs + + import kotlin.String + + public val prop: String = throw NotImplementedError("Stub!") + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + handlerType = ELEMENTS, + reason = "JvmName not possible in reflection", + ) + @Test + fun jvmName_with_kt_reflective() { + val fileSpec = Class.forName( + "com.squareup.kotlinpoet.metadata.specs.JvmNameKt", + ).kotlin.toFileSpecWithTestHandler() + assertThat(fileSpec.name).isEqualTo("JvmName") + //language=kotlin + assertThat(fileSpec.trimmedToString()).isEqualTo( + """ + package com.squareup.kotlinpoet.metadata.specs + + import kotlin.String + + public val prop2: String = "" + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + handlerType = REFLECTIVE, + reason = "JvmName not possible in reflection", + ) + @Test + fun jvmName_with_kt_elements() { + val fileSpec = Class.forName( + "com.squareup.kotlinpoet.metadata.specs.JvmNameKt", + ).kotlin.toFileSpecWithTestHandler() + assertThat(fileSpec.name).isEqualTo("JvmName") + //language=kotlin + assertThat(fileSpec.trimmedToString()).isEqualTo( + """ + @file:JvmName(name = "JvmNameKt") + + package com.squareup.kotlinpoet.metadata.specs + + import kotlin.String + import kotlin.jvm.JvmName + + public val prop2: String = throw NotImplementedError("Stub!") + """.trimIndent(), + ) + } +} + +private fun FileSpec.trimmedToString(): String { + return buildString { writeTo(this) }.trim() +} diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmNameWithKtFacadeFile.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmNameWithKtFacadeFile.kt new file mode 100644 index 00000000..008b36f1 --- /dev/null +++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmNameWithKtFacadeFile.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:JvmName("JvmNameKt") + +package com.squareup.kotlinpoet.metadata.specs + +val prop2: String = "" diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KmAnnotationsTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KmAnnotationsTest.kt new file mode 100644 index 00000000..c7928b4e --- /dev/null +++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KmAnnotationsTest.kt @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2021 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.kotlinpoet.metadata.specs + +import com.google.common.truth.Truth.assertThat +import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview +import kotlin.test.Test +import kotlinx.metadata.KmAnnotation +import kotlinx.metadata.KmAnnotationArgument.AnnotationValue +import kotlinx.metadata.KmAnnotationArgument.ArrayValue +import kotlinx.metadata.KmAnnotationArgument.BooleanValue +import kotlinx.metadata.KmAnnotationArgument.ByteValue +import kotlinx.metadata.KmAnnotationArgument.CharValue +import kotlinx.metadata.KmAnnotationArgument.DoubleValue +import kotlinx.metadata.KmAnnotationArgument.EnumValue +import kotlinx.metadata.KmAnnotationArgument.FloatValue +import kotlinx.metadata.KmAnnotationArgument.IntValue +import kotlinx.metadata.KmAnnotationArgument.KClassValue +import kotlinx.metadata.KmAnnotationArgument.LongValue +import kotlinx.metadata.KmAnnotationArgument.ShortValue +import kotlinx.metadata.KmAnnotationArgument.StringValue +import kotlinx.metadata.KmAnnotationArgument.UByteValue +import kotlinx.metadata.KmAnnotationArgument.UIntValue +import kotlinx.metadata.KmAnnotationArgument.ULongValue +import kotlinx.metadata.KmAnnotationArgument.UShortValue + +@OptIn(ExperimentalUnsignedTypes::class) +@KotlinPoetMetadataPreview +class KmAnnotationsTest { + + @Test fun noMembers() { + val annotation = KmAnnotation("test/NoMembersAnnotation", emptyMap()) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.NoMembersAnnotation + """.trimIndent(), + ) + } + + @Test fun byteValue() { + val annotation = KmAnnotation( + "test/ByteValueAnnotation", + mapOf("value" to ByteValue(2)), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.ByteValueAnnotation(value = 2) + """.trimIndent(), + ) + } + + @Test fun charValue() { + val annotation = KmAnnotation( + "test/CharValueAnnotation", + mapOf("value" to CharValue('2')), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.CharValueAnnotation(value = '2') + """.trimIndent(), + ) + } + + @Test fun shortValue() { + val annotation = KmAnnotation( + "test/ShortValueAnnotation", + mapOf("value" to ShortValue(2)), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.ShortValueAnnotation(value = 2) + """.trimIndent(), + ) + } + + @Test fun intValue() { + val annotation = KmAnnotation( + "test/IntValueAnnotation", + mapOf("value" to IntValue(2)), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.IntValueAnnotation(value = 2) + """.trimIndent(), + ) + } + + @Test fun longValue() { + val annotation = KmAnnotation( + "test/LongValueAnnotation", + mapOf("value" to LongValue(2L)), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.LongValueAnnotation(value = 2L) + """.trimIndent(), + ) + } + + @Test fun floatValue() { + val annotation = KmAnnotation( + "test/FloatValueAnnotation", + mapOf("value" to FloatValue(2.0F)), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.FloatValueAnnotation(value = 2.0F) + """.trimIndent(), + ) + } + + @Test fun doubleValue() { + val annotation = KmAnnotation( + "test/DoubleValueAnnotation", + mapOf("value" to DoubleValue(2.0)), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.DoubleValueAnnotation(value = 2.0) + """.trimIndent(), + ) + } + + @Test fun booleanValue() { + val annotation = KmAnnotation( + "test/BooleanValueAnnotation", + mapOf("value" to BooleanValue(true)), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.BooleanValueAnnotation(value = true) + """.trimIndent(), + ) + } + + @Test fun uByteValue() { + val annotation = KmAnnotation( + "test/UByteValueAnnotation", + mapOf("value" to UByteValue(2u)), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.UByteValueAnnotation(value = 2u) + """.trimIndent(), + ) + } + + @Test fun uShortValue() { + val annotation = KmAnnotation( + "test/UShortValueAnnotation", + mapOf("value" to UShortValue(2u)), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.UShortValueAnnotation(value = 2u) + """.trimIndent(), + ) + } + + @Test fun uIntValue() { + val annotation = KmAnnotation( + "test/UIntValueAnnotation", + mapOf("value" to UIntValue(2u)), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.UIntValueAnnotation(value = 2u) + """.trimIndent(), + ) + } + + @Test fun uLongValue() { + val annotation = KmAnnotation( + "test/ULongValueAnnotation", + mapOf("value" to ULongValue(2u)), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.ULongValueAnnotation(value = 2u) + """.trimIndent(), + ) + } + + @Test fun stringValue() { + val annotation = KmAnnotation( + "test/StringValueAnnotation", + mapOf("value" to StringValue("taco")), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.StringValueAnnotation(value = "taco") + """.trimIndent(), + ) + } + + @Test fun kClassValue() { + val annotation = KmAnnotation( + "test/KClassValueAnnotation", + mapOf("value" to KClassValue("test/OtherClass", 0)), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.KClassValueAnnotation(value = test.OtherClass::class) + """.trimIndent(), + ) + } + + @Test fun enumValue() { + val annotation = KmAnnotation( + "test/EnumValueAnnotation", + mapOf("value" to EnumValue("test/OtherClass", "VALUE")), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.EnumValueAnnotation(value = test.OtherClass.VALUE) + """.trimIndent(), + ) + } + + @Test fun annotationValue() { + val annotation = KmAnnotation( + "test/AnnotationValueAnnotation", + mapOf( + "value" to AnnotationValue( + KmAnnotation("test/OtherAnnotation", mapOf("value" to StringValue("Hello!"))), + ), + ), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.AnnotationValueAnnotation(value = test.OtherAnnotation(value = "Hello!")) + """.trimIndent(), + ) + } + + @Test fun arrayValue() { + val annotation = KmAnnotation( + "test/ArrayValueAnnotation", + mapOf("value" to ArrayValue(listOf(IntValue(1), IntValue(2), IntValue(3)))), + ) + assertThat(annotation.toAnnotationSpec().toString()).isEqualTo( + """ + @test.ArrayValueAnnotation(value = [1, 2, 3]) + """.trimIndent(), + ) + } +} diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecsTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecsTest.kt new file mode 100644 index 00000000..9198dbcb --- /dev/null +++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecsTest.kt @@ -0,0 +1,2250 @@ +/* + * Copyright (C) 2019 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:OptIn(KotlinPoetMetadataPreview::class) +@file:Suppress( + "DEPRECATION", + "NOTHING_TO_INLINE", + "RedundantSuspendModifier", + "RedundantUnitReturnType", + "RedundantVisibilityModifier", + "RemoveEmptyPrimaryConstructor", + "RemoveRedundantQualifierName", + "UNUSED_PARAMETER", + "unused", +) + +package com.squareup.kotlinpoet.metadata.specs + +import com.google.common.truth.Truth.assertThat +import com.squareup.kotlinpoet.LIST +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.STRING +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview +import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType.ELEMENTS +import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType.REFLECTIVE +import com.squareup.kotlinpoet.tag +import com.squareup.kotlinpoet.tags.TypeAliasTag +import kotlin.annotation.AnnotationRetention.RUNTIME +import kotlin.annotation.AnnotationTarget.TYPE +import kotlin.annotation.AnnotationTarget.TYPE_PARAMETER +import kotlin.properties.Delegates +import kotlin.test.fail +import kotlinx.metadata.KmClass +import kotlinx.metadata.KmConstructor +import kotlinx.metadata.KmFunction +import kotlinx.metadata.KmProperty +import kotlinx.metadata.KmTypeParameter +import kotlinx.metadata.KmValueParameter +import org.junit.Ignore +import org.junit.Test + +class KotlinPoetMetadataSpecsTest : MultiClassInspectorTest() { + + @Test + fun constructorData() { + val typeSpec = ConstructorClass::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class ConstructorClass( + public val foo: kotlin.String, + vararg bar: kotlin.Int, + ) { + public constructor(bar: kotlin.Int) + } + """.trimIndent(), + ) + } + + class ConstructorClass(val foo: String, vararg bar: Int) { + // Secondary constructors are ignored, so we expect this constructor to not be the one picked + // up in the test. + constructor(bar: Int) : this("defaultFoo") + } + + @Test + fun supertype() { + val typeSpec = Supertype::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Supertype() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.BaseType(), com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.BaseInterface + """.trimIndent(), + ) + } + + abstract class BaseType + interface BaseInterface + class Supertype : BaseType(), BaseInterface + + @IgnoreForHandlerType( + reason = "Elements properly resolves the string constant", + handlerType = ELEMENTS, + ) + @Test + fun propertiesReflective() { + val typeSpec = Properties::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Properties() { + public var aList: kotlin.collections.List<kotlin.Int> = throw NotImplementedError("Stub!") + + public val bar: kotlin.String? = null + + public var baz: kotlin.Int = throw NotImplementedError("Stub!") + + public val foo: kotlin.String = throw NotImplementedError("Stub!") + } + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + reason = "Elements properly resolves the string constant", + handlerType = REFLECTIVE, + ) + @Test + fun propertiesElements() { + val typeSpec = Properties::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Properties() { + public var aList: kotlin.collections.List<kotlin.Int> = throw NotImplementedError("Stub!") + + public val bar: kotlin.String? = null + + public var baz: kotlin.Int = throw NotImplementedError("Stub!") + + public val foo: kotlin.String = throw NotImplementedError("Stub!") + } + """.trimIndent(), + ) + } + + class Properties { + val foo: String = "" + val bar: String? = null + var baz: Int = 0 + var aList: List<Int> = emptyList() + } + + @Test + fun companionObject() { + val typeSpec = CompanionObject::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class CompanionObject() { + public companion object + } + """.trimIndent(), + ) + } + + class CompanionObject { + companion object + } + + @Test + fun namedCompanionObject() { + val typeSpec = NamedCompanionObject::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class NamedCompanionObject() { + public companion object Named + } + """.trimIndent(), + ) + } + + class NamedCompanionObject { + companion object Named + } + + @Test + fun generics() { + val typeSpec = Generics::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Generics<out T, in R, V>( + public val genericInput: T, + ) + """.trimIndent(), + ) + } + + class Generics<out T, in R, V>(val genericInput: T) + + @Test + fun typeAliases() { + val typeSpec = TypeAliases::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class TypeAliases( + public val foo: com.squareup.kotlinpoet.metadata.specs.TypeAliasName, + public val bar: com.squareup.kotlinpoet.metadata.specs.GenericTypeAlias, + ) + """.trimIndent(), + ) + + val fooPropertyType = typeSpec.propertySpecs.first { it.name == "foo" }.type + val fooAliasData = fooPropertyType.tag<TypeAliasTag>() + checkNotNull(fooAliasData) + assertThat(fooAliasData.abbreviatedType).isEqualTo(STRING) + + val barPropertyType = typeSpec.propertySpecs.first { it.name == "bar" }.type + val barAliasData = barPropertyType.tag<TypeAliasTag>() + checkNotNull(barAliasData) + assertThat(barAliasData.abbreviatedType).isEqualTo(LIST.parameterizedBy(STRING)) + } + + class TypeAliases(val foo: TypeAliasName, val bar: GenericTypeAlias) + + @Test + fun propertyMutability() { + val typeSpec = PropertyMutability::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class PropertyMutability( + public val foo: kotlin.String, + public var mutableFoo: kotlin.String, + ) + """.trimIndent(), + ) + } + + class PropertyMutability(val foo: String, var mutableFoo: String) + + @Test + fun collectionMutability() { + val typeSpec = CollectionMutability::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class CollectionMutability( + public val immutableList: kotlin.collections.List<kotlin.String>, + public val mutableList: kotlin.collections.MutableList<kotlin.String>, + ) + """.trimIndent(), + ) + } + + class CollectionMutability(val immutableList: List<String>, val mutableList: MutableList<String>) + + @Test + fun suspendTypes() { + val typeSpec = SuspendTypes::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class SuspendTypes() { + public val testProp: suspend (kotlin.Int, kotlin.Long) -> kotlin.String = throw NotImplementedError("Stub!") + + public suspend fun testComplexSuspendFun(body: suspend (kotlin.Int, suspend (kotlin.Long) -> kotlin.String) -> kotlin.String): kotlin.Unit { + } + + public fun testFun(body: suspend (kotlin.Int, kotlin.Long) -> kotlin.String): kotlin.Unit { + } + + public suspend fun testSuspendFun(param1: kotlin.String): kotlin.Unit { + } + } + """.trimIndent(), + ) + } + + class SuspendTypes { + val testProp: suspend (Int, Long) -> String = { _, _ -> "" } + + fun testFun(body: suspend (Int, Long) -> String) { + } + + suspend fun testSuspendFun(param1: String) { + } + + suspend fun testComplexSuspendFun(body: suspend (Int, suspend (Long) -> String) -> String) { + } + } + + @Test + fun parameters() { + val typeSpec = Parameters::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Parameters() { + public inline fun hasDefault(param1: kotlin.String = throw NotImplementedError("Stub!")): kotlin.Unit { + } + + public inline fun `inline`(crossinline param1: () -> kotlin.String): kotlin.Unit { + } + + public inline fun `noinline`(noinline param1: () -> kotlin.String): kotlin.String = throw NotImplementedError("Stub!") + } + """.trimIndent(), + ) + } + + class Parameters { + inline fun inline(crossinline param1: () -> String) { + } + + inline fun noinline(noinline param1: () -> String): String { + return "" + } + + inline fun hasDefault(param1: String = "Nope") { + } + } + + @Test + fun lambdaReceiver() { + val typeSpec = LambdaReceiver::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class LambdaReceiver() { + public fun lambdaReceiver(block: kotlin.String.() -> kotlin.Unit): kotlin.Unit { + } + + public fun lambdaReceiver2(block: kotlin.String.(kotlin.Int) -> kotlin.Unit): kotlin.Unit { + } + + public fun lambdaReceiver3(block: kotlin.String.(kotlin.Int, kotlin.String) -> kotlin.Unit): kotlin.Unit { + } + } + """.trimIndent(), + ) + } + + class LambdaReceiver { + fun lambdaReceiver(block: String.() -> Unit) { + } + fun lambdaReceiver2(block: String.(Int) -> Unit) { + } + fun lambdaReceiver3(block: String.(Int, String) -> Unit) { + } + } + + @Test + fun nestedTypeAlias() { + val typeSpec = NestedTypeAliasTest::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class NestedTypeAliasTest() { + public val prop: com.squareup.kotlinpoet.metadata.specs.NestedTypeAlias = throw NotImplementedError("Stub!") + } + """.trimIndent(), + ) + } + + class NestedTypeAliasTest { + val prop: NestedTypeAlias = listOf(listOf("")) + } + + @Test + fun inlineClass() { + val typeSpec = InlineClass::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + @kotlin.jvm.JvmInline + public value class InlineClass( + public val `value`: kotlin.String, + ) + """.trimIndent(), + ) + } + + @Test + fun valueClass() { + val typeSpec = ValueClass::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + @kotlin.jvm.JvmInline + public value class ValueClass( + public val `value`: kotlin.String, + ) + """.trimIndent(), + ) + } + + @Test + fun functionReferencingTypeParam() { + val typeSpec = FunctionsReferencingTypeParameters::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class FunctionsReferencingTypeParameters<T>() { + public fun test(`param`: T): kotlin.Unit { + } + } + """.trimIndent(), + ) + } + + class FunctionsReferencingTypeParameters<T> { + fun test(param: T) { + } + } + + @Test + fun overriddenThings() { + val typeSpec = OverriddenThings::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public abstract class OverriddenThings() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.OverriddenThingsBase(), com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.OverriddenThingsInterface { + public override var openProp: kotlin.String = throw NotImplementedError("Stub!") + + public override var openPropInterface: kotlin.String = throw NotImplementedError("Stub!") + + public override fun openFunction(): kotlin.Unit { + } + + public override fun openFunctionInterface(): kotlin.Unit { + } + } + """.trimIndent(), + ) + } + + abstract class OverriddenThingsBase { + abstract var openProp: String + + abstract fun openFunction() + } + + interface OverriddenThingsInterface { + var openPropInterface: String + + fun openFunctionInterface() + } + + abstract class OverriddenThings : OverriddenThingsBase(), OverriddenThingsInterface { + override var openProp: String = "" + override var openPropInterface: String = "" + + override fun openFunction() { + } + + override fun openFunctionInterface() { + } + } + + @Test + fun delegatedProperties() { + val typeSpec = DelegatedProperties::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class DelegatedProperties() { + /** + * Note: delegation is ABI stub only and not guaranteed to match source code. + */ + public val immutable: kotlin.String by kotlin.lazy { throw NotImplementedError("Stub!") } + + /** + * Note: delegation is ABI stub only and not guaranteed to match source code. + */ + public val immutableNullable: kotlin.String? by kotlin.lazy { throw NotImplementedError("Stub!") } + + /** + * Note: delegation is ABI stub only and not guaranteed to match source code. + */ + public var mutable: kotlin.String by kotlin.properties.Delegates.notNull() + + /** + * Note: delegation is ABI stub only and not guaranteed to match source code. + */ + public var mutableNullable: kotlin.String? by kotlin.properties.Delegates.observable(null) { _, _, _ -> } + } + """.trimIndent(), + ) + } + + class DelegatedProperties { + val immutable: String by lazy { "" } + val immutableNullable: String? by lazy { "" } + var mutable: String by Delegates.notNull() + var mutableNullable: String? by Delegates.observable(null) { _, _, _ -> } + } + + @Ignore("Need to be able to know about class delegation in metadata") + @Test + fun classDelegation() { + val typeSpec = ClassDelegation::class.toTypeSpecWithTestHandler() + + // TODO Assert this also excludes functions handled by the delegate + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class ClassDelegation<T>( + delegate: List<T> + ): List<T> by delegate + """.trimIndent(), + ) + } + + class ClassDelegation<T>(delegate: List<T>) : List<T> by delegate + + @Test + fun simpleEnum() { + val typeSpec = SimpleEnum::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public enum class SimpleEnum() { + FOO, + BAR, + BAZ, + } + """.trimIndent(), + ) + } + + enum class SimpleEnum { + FOO, BAR, BAZ + } + + @Test + fun complexEnum() { + val typeSpec = ComplexEnum::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public enum class ComplexEnum( + public val `value`: kotlin.String, + ) { + FOO { + public override fun toString(): kotlin.String = throw NotImplementedError("Stub!") + }, + BAR { + public override fun toString(): kotlin.String = throw NotImplementedError("Stub!") + }, + BAZ { + public override fun toString(): kotlin.String = throw NotImplementedError("Stub!") + }, + ; + } + """.trimIndent(), + ) + } + + enum class ComplexEnum(val value: String) { + FOO("foo") { + override fun toString(): String { + return "foo1" + } + }, + BAR("bar") { + override fun toString(): String { + return "bar1" + } + }, + BAZ("baz") { + override fun toString(): String { + return "baz1" + } + }, + } + + @Test + fun enumWithAnnotation() { + val typeSpec = EnumWithAnnotation::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public enum class EnumWithAnnotation() { + FOO, + @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.FieldAnnotation + BAR, + BAZ, + } + """.trimIndent(), + ) + } + + enum class EnumWithAnnotation { + FOO, @FieldAnnotation + BAR, BAZ + } + + @Test + fun complexEnumWithAnnotation() { + val typeSpec = ComplexEnumWithAnnotation::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public enum class ComplexEnumWithAnnotation( + public val `value`: kotlin.String, + ) { + FOO { + public override fun toString(): kotlin.String = throw NotImplementedError("Stub!") + }, + @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.FieldAnnotation + BAR { + public override fun toString(): kotlin.String = throw NotImplementedError("Stub!") + }, + BAZ { + public override fun toString(): kotlin.String = throw NotImplementedError("Stub!") + }, + ; + } + """.trimIndent(), + ) + } + + enum class ComplexEnumWithAnnotation(val value: String) { + FOO("foo") { + override fun toString(): String { + return "foo1" + } + }, + + @FieldAnnotation + BAR("bar") { + override fun toString(): String { + return "bar1" + } + }, + BAZ("baz") { + override fun toString(): String { + return "baz1" + } + }, + } + + @Test + fun interfaces() { + val testInterfaceSpec = TestInterface::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(testInterfaceSpec.trimmedToString()).isEqualTo( + """ + public interface TestInterface { + public fun complex(input: kotlin.String, input2: kotlin.String = throw NotImplementedError("Stub!")): kotlin.String = throw NotImplementedError("Stub!") + + public fun hasDefault(): kotlin.Unit { + } + + public fun hasDefaultMultiParam(input: kotlin.String, input2: kotlin.String): kotlin.String = throw NotImplementedError("Stub!") + + public fun hasDefaultSingleParam(input: kotlin.String): kotlin.String = throw NotImplementedError("Stub!") + + @kotlin.jvm.JvmDefault + public fun hasJvmDefault(): kotlin.Unit { + } + + public fun noDefault(): kotlin.Unit + + public fun noDefaultWithInput(input: kotlin.String): kotlin.Unit + + public fun noDefaultWithInputDefault(input: kotlin.String = throw NotImplementedError("Stub!")): kotlin.Unit + } + """.trimIndent(), + ) + + val subInterfaceSpec = SubInterface::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(subInterfaceSpec.trimmedToString()).isEqualTo( + """ + public interface SubInterface : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TestInterface { + public override fun hasDefault(): kotlin.Unit { + } + + @kotlin.jvm.JvmDefault + public override fun hasJvmDefault(): kotlin.Unit { + } + + public fun subInterfaceFunction(): kotlin.Unit { + } + } + """.trimIndent(), + ) + + val implSpec = TestSubInterfaceImpl::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(implSpec.trimmedToString()).isEqualTo( + """ + public class TestSubInterfaceImpl() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.SubInterface { + public override fun noDefault(): kotlin.Unit { + } + + public override fun noDefaultWithInput(input: kotlin.String): kotlin.Unit { + } + + public override fun noDefaultWithInputDefault(input: kotlin.String): kotlin.Unit { + } + } + """.trimIndent(), + ) + } + + interface TestInterface { + + fun noDefault() + + fun noDefaultWithInput(input: String) + + fun noDefaultWithInputDefault(input: String = "") + + @JvmDefault + fun hasJvmDefault() { + } + + fun hasDefault() { + } + + fun hasDefaultSingleParam(input: String): String { + return "1234" + } + + fun hasDefaultMultiParam(input: String, input2: String): String { + return "1234" + } + + fun complex(input: String, input2: String = ""): String { + return "5678" + } + } + + interface SubInterface : TestInterface { + fun subInterfaceFunction() { + } + + @JvmDefault + override fun hasJvmDefault() { + super.hasJvmDefault() + } + + override fun hasDefault() { + super.hasDefault() + } + } + + class TestSubInterfaceImpl : SubInterface { + override fun noDefault() { + } + + override fun noDefaultWithInput(input: String) { + } + + override fun noDefaultWithInputDefault(input: String) { + } + } + + @Test + fun backwardReferencingTypeVars() { + val typeSpec = BackwardReferencingTypeVars::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public interface BackwardReferencingTypeVars<T> : kotlin.collections.List<kotlin.collections.Set<T>> + """.trimIndent(), + ) + } + + interface BackwardReferencingTypeVars<T> : List<Set<T>> + + @Test + fun taggedTypes() { + val typeSpec = TaggedTypes::class.toTypeSpecWithTestHandler() + assertThat(typeSpec.tag<KmClass>()).isNotNull() + + val constructorSpec = typeSpec.primaryConstructor ?: fail("No constructor found!") + assertThat(constructorSpec.tag<KmConstructor>()).isNotNull() + + val parameterSpec = constructorSpec.parameters[0] + assertThat(parameterSpec.tag<KmValueParameter>()).isNotNull() + + val typeVar = typeSpec.typeVariables[0] + assertThat(typeVar.tag<KmTypeParameter>()).isNotNull() + + val funSpec = typeSpec.funSpecs[0] + assertThat(funSpec.tag<KmFunction>()).isNotNull() + + val propertySpec = typeSpec.propertySpecs[0] + assertThat(propertySpec.tag<KmProperty>()).isNotNull() + } + + class TaggedTypes<T>(val param: T) { + val property: String = "" + + fun function() { + } + } + + @Test + fun annotations() { + val typeSpec = MyAnnotation::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public annotation class MyAnnotation( + public val `value`: kotlin.String, + ) + """.trimIndent(), + ) + } + + annotation class MyAnnotation(val value: String) + + @Test + fun functionTypeArgsSupersedeClass() { + val typeSpec = GenericClass::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class GenericClass<T>() { + public fun <T> functionAlsoWithT(`param`: T): kotlin.Unit { + } + + public fun <R> functionWithADifferentType(`param`: R): kotlin.Unit { + } + + public fun functionWithT(`param`: T): kotlin.Unit { + } + + /** + * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing. + */ + public inline fun <reified T> `reified`(`param`: T): kotlin.Unit { + } + } + """.trimIndent(), + ) + + val func1TypeVar = typeSpec.funSpecs.find { it.name == "functionAlsoWithT" }!!.typeVariables.first() + val classTypeVar = typeSpec.typeVariables.first() + + assertThat(func1TypeVar).isNotSameInstanceAs(classTypeVar) + } + + class GenericClass<T> { + fun functionWithT(param: T) { + } + fun <T> functionAlsoWithT(param: T) { + } + fun <R> functionWithADifferentType(param: R) { + } + + // Regression for https://github.com/square/kotlinpoet/issues/829 + inline fun <reified T> reified(param: T) { + } + } + + @Test + fun complexCompanionObject() { + val typeSpec = ComplexCompanionObject::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class ComplexCompanionObject() { + public companion object ComplexObject : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CompanionBase(), com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CompanionInterface + } + """.trimIndent(), + ) + } + + interface CompanionInterface + open class CompanionBase + + class ComplexCompanionObject { + companion object ComplexObject : CompanionBase(), CompanionInterface + } + + @IgnoreForHandlerType( + reason = "TODO Synthetic methods that hold annotations aren't visible in these tests", + handlerType = ELEMENTS, + ) + @Test + fun annotationsAreCopied() { + val typeSpec = AnnotationHolders::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class AnnotationHolders @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.ConstructorAnnotation constructor() { + @field:com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.FieldAnnotation + public var `field`: kotlin.String? = null + + @get:com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.GetterAnnotation + public var getter: kotlin.String? = null + + @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.HolderAnnotation + @kotlin.jvm.JvmField + public var holder: kotlin.String? = null + + @set:com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.SetterAnnotation + public var setter: kotlin.String? = null + + @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.ConstructorAnnotation + public constructor(`value`: kotlin.String) + + @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.FunctionAnnotation + public fun function(): kotlin.Unit { + } + } + """.trimIndent(), + ) + } + + class AnnotationHolders @ConstructorAnnotation constructor() { + + @ConstructorAnnotation + constructor(value: String) : this() + + @field:FieldAnnotation + var field: String? = null + + @get:GetterAnnotation + var getter: String? = null + + @set:SetterAnnotation + var setter: String? = null + + @HolderAnnotation + @JvmField + var holder: String? = null + + @FunctionAnnotation + fun function() { + } + } + + @Retention(RUNTIME) + annotation class ConstructorAnnotation + + @Retention(RUNTIME) + annotation class FieldAnnotation + + @Retention(RUNTIME) + annotation class GetterAnnotation + + @Retention(RUNTIME) + annotation class SetterAnnotation + + @Retention(RUNTIME) + annotation class HolderAnnotation + + @Retention(RUNTIME) + annotation class FunctionAnnotation + + @IgnoreForHandlerType( + reason = "Elements properly resolves the regular properties + JvmStatic, but reflection will not", + handlerType = REFLECTIVE, + ) + @Test + fun constantValuesElements() { + val typeSpec = Constants::class.toTypeSpecWithTestHandler() + + // Note: formats like hex/binary/underscore are not available as formatted at runtime + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Constants( + public val `param`: kotlin.String = throw NotImplementedError("Stub!"), + ) { + public val binaryProp: kotlin.Int = throw NotImplementedError("Stub!") + + public val boolProp: kotlin.Boolean = throw NotImplementedError("Stub!") + + public val doubleProp: kotlin.Double = throw NotImplementedError("Stub!") + + public val floatProp: kotlin.Float = throw NotImplementedError("Stub!") + + public val hexProp: kotlin.Int = throw NotImplementedError("Stub!") + + public val intProp: kotlin.Int = throw NotImplementedError("Stub!") + + public val longProp: kotlin.Long = throw NotImplementedError("Stub!") + + public val stringProp: kotlin.String = throw NotImplementedError("Stub!") + + public val underscoresHexProp: kotlin.Long = throw NotImplementedError("Stub!") + + public val underscoresProp: kotlin.Int = throw NotImplementedError("Stub!") + + public companion object { + public const val CONST_BINARY_PROP: kotlin.Int = 11 + + public const val CONST_BOOL_PROP: kotlin.Boolean = false + + public const val CONST_DOUBLE_PROP: kotlin.Double = 1.0 + + public const val CONST_FLOAT_PROP: kotlin.Float = 1.0F + + public const val CONST_HEX_PROP: kotlin.Int = 15 + + public const val CONST_INT_PROP: kotlin.Int = 1 + + public const val CONST_LONG_PROP: kotlin.Long = 1L + + public const val CONST_STRING_PROP: kotlin.String = "prop" + + public const val CONST_UNDERSCORES_HEX_PROP: kotlin.Long = 4_293_713_502L + + public const val CONST_UNDERSCORES_PROP: kotlin.Int = 1_000_000 + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_BINARY_PROP: kotlin.Int = throw NotImplementedError("Stub!") + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_BOOL_PROP: kotlin.Boolean = throw NotImplementedError("Stub!") + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_DOUBLE_PROP: kotlin.Double = throw NotImplementedError("Stub!") + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_FLOAT_PROP: kotlin.Float = throw NotImplementedError("Stub!") + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_HEX_PROP: kotlin.Int = throw NotImplementedError("Stub!") + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_INT_PROP: kotlin.Int = throw NotImplementedError("Stub!") + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_LONG_PROP: kotlin.Long = throw NotImplementedError("Stub!") + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_STRING_PROP: kotlin.String = throw NotImplementedError("Stub!") + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_UNDERSCORES_HEX_PROP: kotlin.Long = throw NotImplementedError("Stub!") + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_UNDERSCORES_PROP: kotlin.Int = throw NotImplementedError("Stub!") + } + } + """.trimIndent(), + ) + + // TODO check with objects + } + + @IgnoreForHandlerType( + reason = "Elements properly resolves the regular properties + JvmStatic, but reflection will not", + handlerType = ELEMENTS, + ) + @Test + fun constantValuesReflective() { + val typeSpec = Constants::class.toTypeSpecWithTestHandler() + + // Note: formats like hex/binary/underscore are not available as formatted in elements + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Constants( + public val `param`: kotlin.String = throw NotImplementedError("Stub!"), + ) { + public val binaryProp: kotlin.Int = throw NotImplementedError("Stub!") + + public val boolProp: kotlin.Boolean = throw NotImplementedError("Stub!") + + public val doubleProp: kotlin.Double = throw NotImplementedError("Stub!") + + public val floatProp: kotlin.Float = throw NotImplementedError("Stub!") + + public val hexProp: kotlin.Int = throw NotImplementedError("Stub!") + + public val intProp: kotlin.Int = throw NotImplementedError("Stub!") + + public val longProp: kotlin.Long = throw NotImplementedError("Stub!") + + public val stringProp: kotlin.String = throw NotImplementedError("Stub!") + + public val underscoresHexProp: kotlin.Long = throw NotImplementedError("Stub!") + + public val underscoresProp: kotlin.Int = throw NotImplementedError("Stub!") + + public companion object { + public const val CONST_BINARY_PROP: kotlin.Int = 11 + + public const val CONST_BOOL_PROP: kotlin.Boolean = false + + public const val CONST_DOUBLE_PROP: kotlin.Double = 1.0 + + public const val CONST_FLOAT_PROP: kotlin.Float = 1.0F + + public const val CONST_HEX_PROP: kotlin.Int = 15 + + public const val CONST_INT_PROP: kotlin.Int = 1 + + public const val CONST_LONG_PROP: kotlin.Long = 1L + + public const val CONST_STRING_PROP: kotlin.String = "prop" + + public const val CONST_UNDERSCORES_HEX_PROP: kotlin.Long = 4_293_713_502L + + public const val CONST_UNDERSCORES_PROP: kotlin.Int = 1_000_000 + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_BINARY_PROP: kotlin.Int = 11 + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_BOOL_PROP: kotlin.Boolean = false + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_DOUBLE_PROP: kotlin.Double = 1.0 + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_FLOAT_PROP: kotlin.Float = 1.0F + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_HEX_PROP: kotlin.Int = 15 + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_INT_PROP: kotlin.Int = 1 + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_LONG_PROP: kotlin.Long = 1L + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_STRING_PROP: kotlin.String = "prop" + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_UNDERSCORES_HEX_PROP: kotlin.Long = 4_293_713_502L + + @kotlin.jvm.JvmStatic + public val STATIC_CONST_UNDERSCORES_PROP: kotlin.Int = 1_000_000 + } + } + """.trimIndent(), + ) + } + + class Constants( + val param: String = "param", + ) { + val boolProp = false + val binaryProp = 0b00001011 + val intProp = 1 + val underscoresProp = 1_000_000 + val hexProp = 0x0F + val underscoresHexProp = 0xFF_EC_DE_5E + val longProp = 1L + val floatProp = 1.0F + val doubleProp = 1.0 + val stringProp = "prop" + + companion object { + @JvmStatic val STATIC_CONST_BOOL_PROP = false + + @JvmStatic val STATIC_CONST_BINARY_PROP = 0b00001011 + + @JvmStatic val STATIC_CONST_INT_PROP = 1 + + @JvmStatic val STATIC_CONST_UNDERSCORES_PROP = 1_000_000 + + @JvmStatic val STATIC_CONST_HEX_PROP = 0x0F + + @JvmStatic val STATIC_CONST_UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E + + @JvmStatic val STATIC_CONST_LONG_PROP = 1L + + @JvmStatic val STATIC_CONST_FLOAT_PROP = 1.0f + + @JvmStatic val STATIC_CONST_DOUBLE_PROP = 1.0 + + @JvmStatic val STATIC_CONST_STRING_PROP = "prop" + + const val CONST_BOOL_PROP = false + const val CONST_BINARY_PROP = 0b00001011 + const val CONST_INT_PROP = 1 + const val CONST_UNDERSCORES_PROP = 1_000_000 + const val CONST_HEX_PROP = 0x0F + const val CONST_UNDERSCORES_HEX_PROP = 0xFF_EC_DE_5E + const val CONST_LONG_PROP = 1L + const val CONST_FLOAT_PROP = 1.0f + const val CONST_DOUBLE_PROP = 1.0 + const val CONST_STRING_PROP = "prop" + } + } + + @Test + fun jvmAnnotations() { + val typeSpec = JvmAnnotations::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class JvmAnnotations() { + @get:kotlin.jvm.Synchronized + public val synchronizedGetProp: kotlin.String? = null + + @set:kotlin.jvm.Synchronized + public var synchronizedSetProp: kotlin.String? = null + + @kotlin.jvm.Transient + public val transientProp: kotlin.String? = null + + @kotlin.jvm.Volatile + public var volatileProp: kotlin.String? = null + + @kotlin.jvm.Synchronized + public fun synchronizedFun(): kotlin.Unit { + } + } + """.trimIndent(), + ) + + val interfaceSpec = JvmAnnotationsInterface::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(interfaceSpec.trimmedToString()).isEqualTo( + """ + public interface JvmAnnotationsInterface { + @kotlin.jvm.JvmDefault + public fun defaultMethod(): kotlin.Unit { + } + + public fun notDefaultMethod(): kotlin.Unit + } + """.trimIndent(), + ) + } + + class JvmAnnotations { + @Transient val transientProp: String? = null + + @Volatile var volatileProp: String? = null + + @get:Synchronized val synchronizedGetProp: String? = null + + @set:Synchronized var synchronizedSetProp: String? = null + + @Synchronized + fun synchronizedFun() { + } + } + + interface JvmAnnotationsInterface { + @JvmDefault + fun defaultMethod() { + } + fun notDefaultMethod() + } + + @Test + fun nestedClasses() { + val typeSpec = NestedClasses::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class NestedClasses() { + public abstract class NestedClass<T>() : kotlin.collections.List<T> + + public inner class NestedInnerClass() + } + """.trimIndent(), + ) + } + + class NestedClasses { + abstract class NestedClass<T> : List<T> + inner class NestedInnerClass + } + + @IgnoreForHandlerType( + reason = "Reflection properly resolves companion properties + JvmStatic + JvmName, but " + + "elements will not", + handlerType = ELEMENTS, + ) + @Test + fun jvmNamesReflective() { + val typeSpec = JvmNameData::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class JvmNameData( + @get:kotlin.jvm.JvmName(name = "jvmParam") + public val `param`: kotlin.String, + ) { + @get:kotlin.jvm.JvmName(name = "jvmPropertyGet") + public val propertyGet: kotlin.String? = null + + @get:kotlin.jvm.JvmName(name = "jvmPropertyGetAndSet") + @set:kotlin.jvm.JvmName(name = "jvmPropertyGetAndSet") + public var propertyGetAndSet: kotlin.String? = null + + @set:kotlin.jvm.JvmName(name = "jvmPropertySet") + public var propertySet: kotlin.String? = null + + @kotlin.jvm.JvmName(name = "jvmFunction") + public fun function(): kotlin.Unit { + } + + public interface InterfaceWithJvmName { + public companion object { + @get:kotlin.jvm.JvmName(name = "fooBoolJvm") + @kotlin.jvm.JvmStatic + public val FOO_BOOL: kotlin.Boolean = false + + @kotlin.jvm.JvmName(name = "jvmStaticFunction") + @kotlin.jvm.JvmStatic + public fun staticFunction(): kotlin.Unit { + } + } + } + } + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + reason = "Reflection properly resolves companion properties + JvmStatic + JvmName, but " + + "elements will not", + handlerType = REFLECTIVE, + ) + @Test + fun jvmNamesElements() { + val typeSpec = JvmNameData::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class JvmNameData( + @get:kotlin.jvm.JvmName(name = "jvmParam") + public val `param`: kotlin.String, + ) { + @get:kotlin.jvm.JvmName(name = "jvmPropertyGet") + public val propertyGet: kotlin.String? = null + + @get:kotlin.jvm.JvmName(name = "jvmPropertyGetAndSet") + @set:kotlin.jvm.JvmName(name = "jvmPropertyGetAndSet") + public var propertyGetAndSet: kotlin.String? = null + + @set:kotlin.jvm.JvmName(name = "jvmPropertySet") + public var propertySet: kotlin.String? = null + + @kotlin.jvm.JvmName(name = "jvmFunction") + public fun function(): kotlin.Unit { + } + + public interface InterfaceWithJvmName { + public companion object { + @get:kotlin.jvm.JvmName(name = "fooBoolJvm") + @kotlin.jvm.JvmStatic + public val FOO_BOOL: kotlin.Boolean = throw NotImplementedError("Stub!") + + @kotlin.jvm.JvmName(name = "jvmStaticFunction") + @kotlin.jvm.JvmStatic + public fun staticFunction(): kotlin.Unit { + } + } + } + } + """.trimIndent(), + ) + } + + class JvmNameData( + @get:JvmName("jvmParam") val param: String, + ) { + + @get:JvmName("jvmPropertyGet") + val propertyGet: String? = null + + @set:JvmName("jvmPropertySet") + var propertySet: String? = null + + @set:JvmName("jvmPropertyGetAndSet") + @get:JvmName("jvmPropertyGetAndSet") + var propertyGetAndSet: String? = null + + @JvmName("jvmFunction") + fun function() { + } + + // Interfaces can't have JvmName, but covering a potential edge case of having a companion + // object with JvmName elements. Also covers an edge case where constants have getters + interface InterfaceWithJvmName { + companion object { + @JvmStatic + @get:JvmName("fooBoolJvm") + val FOO_BOOL = false + + @JvmName("jvmStaticFunction") + @JvmStatic + fun staticFunction() { + } + } + } + } + + @IgnoreForHandlerType( + reason = "JvmOverloads is not runtime retained and thus not visible to reflection.", + handlerType = REFLECTIVE, + ) + @Test + fun overloads() { + val typeSpec = Overloads::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Overloads @kotlin.jvm.JvmOverloads constructor( + public val param1: kotlin.String, + public val optionalParam2: kotlin.String = throw NotImplementedError("Stub!"), + public val nullableParam3: kotlin.String? = throw NotImplementedError("Stub!"), + ) { + @kotlin.jvm.JvmOverloads + public fun testFunction( + param1: kotlin.String, + optionalParam2: kotlin.String = throw NotImplementedError("Stub!"), + nullableParam3: kotlin.String? = throw NotImplementedError("Stub!"), + ): kotlin.Unit { + } + } + """.trimIndent(), + ) + } + + class Overloads @JvmOverloads constructor( + val param1: String, + val optionalParam2: String = "", + val nullableParam3: String? = null, + ) { + @JvmOverloads + fun testFunction( + param1: String, + optionalParam2: String = "", + nullableParam3: String? = null, + ) { + } + } + + @IgnoreForHandlerType( + reason = "Elements generates initializer values.", + handlerType = ELEMENTS, + ) + @Test + fun jvmFields_reflective() { + val typeSpec = Fields::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Fields( + @property:kotlin.jvm.JvmField + public val param1: kotlin.String, + ) { + @kotlin.jvm.JvmField + public val fieldProp: kotlin.String = throw NotImplementedError("Stub!") + + public companion object { + @kotlin.jvm.JvmField + public val companionProp: kotlin.String = "" + + public const val constCompanionProp: kotlin.String = "" + + @kotlin.jvm.JvmStatic + public val staticCompanionProp: kotlin.String = "" + } + } + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + reason = "Elements generates initializer values.", + handlerType = REFLECTIVE, + ) + @Test + fun jvmFields_elements() { + val typeSpec = Fields::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Fields( + @property:kotlin.jvm.JvmField + public val param1: kotlin.String, + ) { + @kotlin.jvm.JvmField + public val fieldProp: kotlin.String = throw NotImplementedError("Stub!") + + public companion object { + @kotlin.jvm.JvmField + public val companionProp: kotlin.String = throw NotImplementedError("Stub!") + + public const val constCompanionProp: kotlin.String = "" + + @kotlin.jvm.JvmStatic + public val staticCompanionProp: kotlin.String = throw NotImplementedError("Stub!") + } + } + """.trimIndent(), + ) + } + + class Fields( + @JvmField val param1: String, + ) { + @JvmField val fieldProp: String = "" + + companion object { + @JvmField val companionProp: String = "" + + @JvmStatic val staticCompanionProp: String = "" + const val constCompanionProp: String = "" + } + } + + @IgnoreForHandlerType( + reason = "Synthetic constructs aren't available in elements, so some information like " + + "JvmStatic can't be deduced.", + handlerType = ELEMENTS, + ) + @Test + fun synthetics_reflective() { + val typeSpec = Synthetics::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Synthetics( + @get:kotlin.jvm.JvmSynthetic + public val `param`: kotlin.String, + ) { + @field:kotlin.jvm.JvmSynthetic + public val fieldProperty: kotlin.String? = null + + @field:kotlin.jvm.JvmSynthetic + public val `property`: kotlin.String? = null + + @get:kotlin.jvm.JvmSynthetic + public val propertyGet: kotlin.String? = null + + @get:kotlin.jvm.JvmSynthetic + @set:kotlin.jvm.JvmSynthetic + public var propertyGetAndSet: kotlin.String? = null + + @set:kotlin.jvm.JvmSynthetic + public var propertySet: kotlin.String? = null + + /** + * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing. + */ + @kotlin.jvm.JvmSynthetic + public fun function(): kotlin.Unit { + } + + public interface InterfaceWithJvmName { + /** + * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing. + */ + @kotlin.jvm.JvmSynthetic + public fun interfaceFunction(): kotlin.Unit + + public companion object { + @get:kotlin.jvm.JvmSynthetic + @kotlin.jvm.JvmStatic + public val FOO_BOOL: kotlin.Boolean = false + + /** + * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing. + */ + @kotlin.jvm.JvmStatic + @kotlin.jvm.JvmSynthetic + public fun staticFunction(): kotlin.Unit { + } + } + } + } + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + reason = "Synthetic constructs aren't available in elements, so some information like " + + "JvmStatic can't be deduced.", + handlerType = REFLECTIVE, + ) + @Test + fun synthetics_elements() { + val typeSpec = Synthetics::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Synthetics( + @get:kotlin.jvm.JvmSynthetic + public val `param`: kotlin.String, + ) { + @field:kotlin.jvm.JvmSynthetic + public val fieldProperty: kotlin.String? = null + + @field:kotlin.jvm.JvmSynthetic + public val `property`: kotlin.String? = null + + @get:kotlin.jvm.JvmSynthetic + public val propertyGet: kotlin.String? = null + + @get:kotlin.jvm.JvmSynthetic + @set:kotlin.jvm.JvmSynthetic + public var propertyGetAndSet: kotlin.String? = null + + @set:kotlin.jvm.JvmSynthetic + public var propertySet: kotlin.String? = null + + /** + * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing. + */ + @kotlin.jvm.JvmSynthetic + public fun function(): kotlin.Unit { + } + + public interface InterfaceWithJvmName { + /** + * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing. + */ + @kotlin.jvm.JvmSynthetic + public fun interfaceFunction(): kotlin.Unit + + public companion object { + @get:kotlin.jvm.JvmSynthetic + @kotlin.jvm.JvmStatic + public val FOO_BOOL: kotlin.Boolean = throw NotImplementedError("Stub!") + + /** + * Note: Since this is a synthetic function, some JVM information (annotations, modifiers) may be missing. + */ + @kotlin.jvm.JvmSynthetic + public fun staticFunction(): kotlin.Unit { + } + } + } + } + """.trimIndent(), + ) + } + + class Synthetics( + @get:JvmSynthetic val param: String, + ) { + + @JvmSynthetic + val property: String? = null + + @field:JvmSynthetic + val fieldProperty: String? = null + + @get:JvmSynthetic + val propertyGet: String? = null + + @set:JvmSynthetic + var propertySet: String? = null + + @set:JvmSynthetic + @get:JvmSynthetic + var propertyGetAndSet: String? = null + + @JvmSynthetic + fun function() { + } + + // Interfaces can have JvmSynthetic, so covering a potential edge case of having a companion + // object with JvmSynthetic elements. Also covers an edge case where constants have getters + interface InterfaceWithJvmName { + @JvmSynthetic + fun interfaceFunction() + + companion object { + @JvmStatic + @get:JvmSynthetic + val FOO_BOOL = false + + @JvmSynthetic + @JvmStatic + fun staticFunction() { + } + } + } + } + + @Test + fun throws() { + val typeSpec = Throwing::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Throwing @kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class]) constructor() { + @get:kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class]) + @set:kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class]) + public var getterAndSetterThrows: kotlin.String? = null + + @get:kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class]) + public val getterThrows: kotlin.String? = null + + @set:kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class]) + public var setterThrows: kotlin.String? = null + + @kotlin.jvm.Throws(exceptionClasses = [java.lang.IllegalStateException::class]) + public fun testFunction(): kotlin.Unit { + } + } + """.trimIndent(), + ) + } + + class Throwing + @Throws(IllegalStateException::class) + constructor() { + + @get:Throws(IllegalStateException::class) + val getterThrows: String? = null + + @set:Throws(IllegalStateException::class) + var setterThrows: String? = null + + @get:Throws(IllegalStateException::class) + @set:Throws(IllegalStateException::class) + var getterAndSetterThrows: String? = null + + @Throws(IllegalStateException::class) + fun testFunction() { + } + } + + // The meta-ist of metadata meta-tests. + @IgnoreForHandlerType( + reason = "Reflection can't parse non-runtime retained annotations", + handlerType = REFLECTIVE, + ) + @Test + fun metaTest_elements() { + val typeSpec = Metadata::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + @kotlin.SinceKotlin(version = "1.3") + @kotlin.`annotation`.Retention(value = kotlin.`annotation`.AnnotationRetention.RUNTIME) + @kotlin.`annotation`.Target(allowedTargets = arrayOf(kotlin.`annotation`.AnnotationTarget.CLASS)) + public annotation class Metadata( + @get:kotlin.jvm.JvmName(name = "k") + public val kind: kotlin.Int = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "mv") + public val metadataVersion: kotlin.IntArray = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "bv") + public val bytecodeVersion: kotlin.IntArray = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "d1") + public val data1: kotlin.Array<kotlin.String> = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "d2") + public val data2: kotlin.Array<kotlin.String> = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "xs") + public val extraString: kotlin.String = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "pn") + public val packageName: kotlin.String = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "xi") + public val extraInt: kotlin.Int = throw NotImplementedError("Stub!"), + ) + """.trimIndent(), + ) + } + + // The meta-ist of metadata meta-tests. + @IgnoreForHandlerType( + reason = "Reflection can't parse non-runtime retained annotations", + handlerType = ELEMENTS, + ) + @Test + fun metaTest_reflection() { + val typeSpec = Metadata::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + @kotlin.`annotation`.Retention(value = kotlin.`annotation`.AnnotationRetention.RUNTIME) + @kotlin.`annotation`.Target(allowedTargets = arrayOf(kotlin.`annotation`.AnnotationTarget.CLASS)) + public annotation class Metadata( + @get:kotlin.jvm.JvmName(name = "k") + public val kind: kotlin.Int = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "mv") + public val metadataVersion: kotlin.IntArray = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "bv") + public val bytecodeVersion: kotlin.IntArray = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "d1") + public val data1: kotlin.Array<kotlin.String> = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "d2") + public val data2: kotlin.Array<kotlin.String> = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "xs") + public val extraString: kotlin.String = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "pn") + public val packageName: kotlin.String = throw NotImplementedError("Stub!"), + @get:kotlin.jvm.JvmName(name = "xi") + public val extraInt: kotlin.Int = throw NotImplementedError("Stub!"), + ) + """.trimIndent(), + ) + } + + @Test + fun classNamesAndNesting() { + // Make sure we parse class names correctly at all levels + val typeSpec = ClassNesting::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class ClassNesting() { + public class NestedClass() { + public class SuperNestedClass() { + public inner class SuperDuperInnerClass() + } + } + } + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + reason = "compile-testing can't handle class names with dashes, will throw " + + "\"class file for com.squareup.kotlinpoet.metadata.specs.Fuzzy\$ClassNesting\$-Nested not found\"", + handlerType = ELEMENTS, + ) + @Test + fun classNamesAndNesting_pathological() { + // Make sure we parse class names correctly at all levels + val typeSpec = `Fuzzy$ClassNesting`::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class `Fuzzy${'$'}ClassNesting`() { + public class `-Nested`() { + public class SuperNestedClass() { + public inner class `-${'$'}Fuzzy${'$'}Super${'$'}Weird-Nested${'$'}Name`() + } + } + } + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + reason = "Property site-target annotations are always stored on the synthetic annotations " + + "method, which is not accessible in the elements API", + handlerType = ELEMENTS, + ) + @Test + fun parameterAnnotations_reflective() { + val typeSpec = ParameterAnnotations::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class ParameterAnnotations( + @property:com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "${'$'}{'${'$'}'}a") + @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "b") + public val param1: kotlin.String, + @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "2") + param2: kotlin.String, + ) { + public fun function(@com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "woo") param1: kotlin.String): kotlin.Unit { + } + } + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + reason = "Property site-target annotations are always stored on the synthetic annotations " + + "method, which is not accessible in the elements API", + handlerType = REFLECTIVE, + ) + @Test + fun parameterAnnotations_elements() { + val typeSpec = ParameterAnnotations::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class ParameterAnnotations( + @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "b") + public val param1: kotlin.String, + @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "2") + param2: kotlin.String, + ) { + public fun function(@com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.CustomAnnotation(name = "woo") param1: kotlin.String): kotlin.Unit { + } + } + """.trimIndent(), + ) + } + + annotation class CustomAnnotation(val name: String) + + class ParameterAnnotations( + @property:CustomAnnotation("\$a") + @param:CustomAnnotation("b") + val param1: String, + @CustomAnnotation("2") param2: String, + ) { + fun function(@CustomAnnotation("woo") param1: String) { + } + } + + @IgnoreForHandlerType( + reason = "Non-runtime annotations are not present for reflection.", + handlerType = ELEMENTS, + ) + @Test + fun classAnnotations_reflective() { + val typeSpec = ClassAnnotations::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.RuntimeCustomClassAnnotation(name = "Runtime") + public class ClassAnnotations() + """.trimIndent(), + ) + } + + @IgnoreForHandlerType( + reason = "Non-runtime annotations are not present for reflection.", + handlerType = REFLECTIVE, + ) + @Test + fun classAnnotations_elements() { + val typeSpec = ClassAnnotations::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.BinaryCustomClassAnnotation(name = "Binary") + @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.RuntimeCustomClassAnnotation(name = "Runtime") + public class ClassAnnotations() + """.trimIndent(), + ) + } + + @Retention(AnnotationRetention.SOURCE) + annotation class SourceCustomClassAnnotation(val name: String) + + @Retention(AnnotationRetention.BINARY) + annotation class BinaryCustomClassAnnotation(val name: String) + + @Retention(AnnotationRetention.RUNTIME) + annotation class RuntimeCustomClassAnnotation(val name: String) + + @SourceCustomClassAnnotation("Source") + @BinaryCustomClassAnnotation("Binary") + @RuntimeCustomClassAnnotation("Runtime") + class ClassAnnotations + + @Test + fun typeAnnotations() { + val typeSpec = TypeAnnotations::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class TypeAnnotations() { + public val foo: kotlin.collections.List<@com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation kotlin.String> = throw NotImplementedError("Stub!") + + public fun <T> bar(input: @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation kotlin.String, input2: @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation (@com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation kotlin.Int) -> @com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.TypeAnnotation kotlin.String): kotlin.Unit { + } + } + """.trimIndent(), + ) + } + + @Target(TYPE, TYPE_PARAMETER) + annotation class TypeAnnotation + + class TypeAnnotations { + val foo: List<@TypeAnnotation String> = emptyList() + + fun <@TypeAnnotation T> bar( + input: @TypeAnnotation String, + input2: @TypeAnnotation (@TypeAnnotation Int) -> @TypeAnnotation String, + ) { + } + } + + // Regression test for https://github.com/square/kotlinpoet/issues/812 + @Test + fun backwardTypeVarReferences() { + val typeSpec = Asset::class.toTypeSpecWithTestHandler() + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public class Asset<A : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset<A>>() { + public fun <D : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset<D>, C : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset<A>> function(): kotlin.Unit { + } + + public class AssetIn<in C : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset.AssetIn<C>>() + + public class AssetOut<out B : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Asset.AssetOut<B>>() + } + """.trimIndent(), + ) + } + + class Asset<A : Asset<A>> { + fun <D : Asset<D>, C : Asset<A>> function() { + } + + class AssetOut<out B : AssetOut<B>> + class AssetIn<in C : AssetIn<C>> + } + + // Regression test for https://github.com/square/kotlinpoet/issues/821 + @Test + fun abstractClass() { + val typeSpec = AbstractClass::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public abstract class AbstractClass() { + public val baz: kotlin.String? = null + + public abstract val foo: kotlin.String + + public abstract fun bar(): kotlin.Unit + + public abstract fun barWithReturn(): kotlin.String + + public fun fuz(): kotlin.Unit { + } + + public fun fuzWithReturn(): kotlin.String = throw NotImplementedError("Stub!") + } + """.trimIndent(), + ) + } + + abstract class AbstractClass { + abstract val foo: String + abstract fun bar() + abstract fun barWithReturn(): String + + val baz: String? = null + fun fuz() {} + fun fuzWithReturn(): String { + return "" + } + } + + // Regression test for https://github.com/square/kotlinpoet/issues/820 + @Test + fun internalAbstractProperty() { + val typeSpec = InternalAbstractPropertyHolder::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public abstract class InternalAbstractPropertyHolder() { + internal abstract val valProp: kotlin.String + + internal abstract var varProp: kotlin.String + } + """.trimIndent(), + ) + } + + abstract class InternalAbstractPropertyHolder { + internal abstract val valProp: String + internal abstract var varProp: String + } + + @Test + fun modalities() { + val abstractModalities = AbstractModalities::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(abstractModalities.trimmedToString()).isEqualTo( + """ + public abstract class AbstractModalities() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.ModalitiesInterface { + public val implicitFinalProp: kotlin.String? = null + + public override val interfaceProp: kotlin.String? = null + + public open val openProp: kotlin.String? = null + + public fun implicitFinalFun(): kotlin.Unit { + } + + public override fun interfaceFun(): kotlin.Unit { + } + + public open fun openFun(): kotlin.Unit { + } + } + """.trimIndent(), + ) + + val finalAbstractModalities = FinalAbstractModalities::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(finalAbstractModalities.trimmedToString()).isEqualTo( + """ + public abstract class FinalAbstractModalities() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.ModalitiesInterface { + public final override val interfaceProp: kotlin.String? = null + + public final override fun interfaceFun(): kotlin.Unit { + } + } + """.trimIndent(), + ) + + val modalities = Modalities::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(modalities.trimmedToString()).isEqualTo( + """ + public class Modalities() : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.AbstractModalities() { + public override val interfaceProp: kotlin.String? = null + + public override val openProp: kotlin.String? = null + + public override fun interfaceFun(): kotlin.Unit { + } + + public override fun openFun(): kotlin.Unit { + } + } + """.trimIndent(), + ) + } + + interface ModalitiesInterface { + val interfaceProp: String? + fun interfaceFun() + } + abstract class AbstractModalities : ModalitiesInterface { + override val interfaceProp: String? = null + override fun interfaceFun() { + } + val implicitFinalProp: String? = null + fun implicitFinalFun() { + } + open val openProp: String? = null + open fun openFun() { + } + } + abstract class FinalAbstractModalities : ModalitiesInterface { + final override val interfaceProp: String? = null + final override fun interfaceFun() { + } + } + class Modalities : AbstractModalities() { + override val interfaceProp: String? = null + override fun interfaceFun() { + } + + override val openProp: String? = null + override fun openFun() { + } + } + + @Test + fun funClass() { + val funInterface = FunInterface::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(funInterface.trimmedToString()).isEqualTo( + """ + public fun interface FunInterface { + public fun example(): kotlin.Unit + } + """.trimIndent(), + ) + } + + fun interface FunInterface { + fun example() + } + + @Test + fun selfReferencingTypeParams() { + val typeSpec = Node::class.toTypeSpecWithTestHandler() + + //language=kotlin + assertThat(typeSpec.trimmedToString()).isEqualTo( + """ + public open class Node<T : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Node<T, R>, R : com.squareup.kotlinpoet.metadata.specs.KotlinPoetMetadataSpecsTest.Node<R, T>>() { + public var r: R? = null + + public var t: T? = null + } + """.trimIndent(), + ) + } + + open class Node<T : Node<T, R>, R : Node<R, T>> { + var t: T? = null + var r: R? = null + } +} + +class ClassNesting { + class NestedClass { + class SuperNestedClass { + inner class SuperDuperInnerClass + } + } +} + +class `Fuzzy$ClassNesting` { + class `-Nested` { + class SuperNestedClass { + inner class `-$Fuzzy$Super$Weird-Nested$Name` + } + } +} + +private fun TypeSpec.trimmedToString(): String { + return toString().trim() +} + +inline class InlineClass(val value: String) + +@JvmInline +value class ValueClass(val value: String) + +typealias TypeAliasName = String +typealias GenericTypeAlias = List<String> +typealias NestedTypeAlias = List<GenericTypeAlias> diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/MultiClassInspectorTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/MultiClassInspectorTest.kt new file mode 100644 index 00000000..cb7c21d6 --- /dev/null +++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/MultiClassInspectorTest.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2019 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.kotlinpoet.metadata.specs + +import com.google.testing.compile.CompilationRule +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview +import com.squareup.kotlinpoet.metadata.classinspectors.ElementsClassInspector +import com.squareup.kotlinpoet.metadata.classinspectors.ReflectiveClassInspector +import com.squareup.kotlinpoet.metadata.specs.MultiClassInspectorTest.ClassInspectorType +import com.squareup.kotlinpoet.metadata.toKotlinClassMetadata +import java.lang.annotation.Inherited +import kotlin.annotation.AnnotationRetention.RUNTIME +import kotlin.reflect.KClass +import kotlinx.metadata.jvm.KotlinClassMetadata.FileFacade +import org.junit.Assume +import org.junit.Rule +import org.junit.rules.TestRule +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.junit.runners.model.Statement + +/** Base test class that runs all tests with multiple [ClassInspectorTypes][ClassInspectorType]. */ +@RunWith(Parameterized::class) +@KotlinPoetMetadataPreview +abstract class MultiClassInspectorTest { + companion object { + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun data(): Collection<Array<ClassInspectorType>> { + return listOf( + arrayOf(ClassInspectorType.REFLECTIVE), + arrayOf(ClassInspectorType.ELEMENTS), + ) + } + } + + enum class ClassInspectorType { + NONE { + override fun create(testInstance: MultiClassInspectorTest): ClassInspector { + throw IllegalStateException("Should not be called, just here to default the jvmfield to something.") + } + }, + REFLECTIVE { + override fun create(testInstance: MultiClassInspectorTest): ClassInspector { + return ReflectiveClassInspector.create() + } + }, + ELEMENTS { + override fun create(testInstance: MultiClassInspectorTest): ClassInspector { + return ElementsClassInspector.create(testInstance.compilation.elements, testInstance.compilation.types) + } + }, + ; + + abstract fun create(testInstance: MultiClassInspectorTest): ClassInspector + } + + @Retention(RUNTIME) + @Target(AnnotationTarget.FUNCTION) + @Inherited + annotation class IgnoreForHandlerType( + val reason: String, + val handlerType: ClassInspectorType, + ) + + @JvmField + @Parameter + var classInspectorType: ClassInspectorType = ClassInspectorType.NONE + + @Rule + @JvmField + val compilation = CompilationRule() + + @Rule + @JvmField + val ignoreForElementsRule = TestRule { base, description -> + object : Statement() { + override fun evaluate() { + val annotation = description.getAnnotation( + IgnoreForHandlerType::class.java, + ) + val shouldIgnore = annotation?.handlerType == classInspectorType + Assume.assumeTrue( + "Ignoring ${description.methodName}: ${annotation?.reason}", + !shouldIgnore, + ) + base.evaluate() + } + } + } + + protected fun KClass<*>.toTypeSpecWithTestHandler(): TypeSpec { + return toTypeSpec(classInspectorType.create(this@MultiClassInspectorTest)) + } + + protected fun KClass<*>.toFileSpecWithTestHandler(): FileSpec { + val classInspector = classInspectorType.create(this@MultiClassInspectorTest) + return java.annotations.filterIsInstance<Metadata>().first().toKotlinClassMetadata<FileFacade>() + .toKmPackage() + .toFileSpec(classInspector, asClassName()) + } +} diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/NoJvmNameFacadeFile.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/NoJvmNameFacadeFile.kt new file mode 100644 index 00000000..6347108d --- /dev/null +++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/NoJvmNameFacadeFile.kt @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2021 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.kotlinpoet.metadata.specs + +val prop: String = "" diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/ReflectiveClassInspectorTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/ReflectiveClassInspectorTest.kt new file mode 100644 index 00000000..a4ecc3de --- /dev/null +++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/ReflectiveClassInspectorTest.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.kotlinpoet.metadata.specs + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview +import com.squareup.kotlinpoet.metadata.classinspectors.ReflectiveClassInspector +import com.tschuchort.compiletesting.KotlinCompilation +import com.tschuchort.compiletesting.SourceFile +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import org.junit.Test + +/** + * Class to test the new functionality of Issue#1036. + * @see <a href="https://github.com/square/kotlinpoet/issues/1036">issue</a> + * @author oberstrike + */ +@KotlinPoetMetadataPreview +class ReflectiveClassInspectorTest { + + data class Person(val name: String) + + /** + * Tests if the [ReflectiveClassInspector] can be created without a + * custom ClassLoader and still works. + */ + @Test + fun standardClassLoaderTest() { + val classInspector = ReflectiveClassInspector.create() + val className = Person::class.asClassName() + val declarationContainer = classInspector.declarationContainerFor(className) + assertNotNull(declarationContainer) + } + + /** + * Tests if the [ReflectiveClassInspector] can be created with a + * custom ClassLoader. + */ + @Test + fun useACustomClassLoaderTest() { + val testClass = "Person" + val testPropertyName = "name" + val testPropertyType = "String" + val testPackageName = "com.test" + val testClassName = ClassName(testPackageName, testClass) + val testKtFileName = "KClass.kt" + + val kotlinSource = SourceFile.kotlin( + testKtFileName, + """ + package $testPackageName + data class $testClass(val $testPropertyName: $testPropertyType) + """.trimIndent(), + ) + + val result = KotlinCompilation().apply { + sources = listOf(kotlinSource) + }.compile() + + assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) + val classLoader = result.classLoader + val classInspector = ReflectiveClassInspector.create(classLoader) + + val declarationContainer = classInspector.declarationContainerFor(testClassName) + + val properties = declarationContainer.properties + assertEquals(1, properties.size) + + val testProperty = properties.findLast { it.name == testPropertyName } + assertNotNull(testProperty) + + val returnType = testProperty.returnType + assertNotNull(returnType) + } +} diff --git a/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/classinspectors/ClassInspectorUtilTest.kt b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/classinspectors/ClassInspectorUtilTest.kt new file mode 100644 index 00000000..4e1c268b --- /dev/null +++ b/interop/kotlinx-metadata/src/test/kotlin/com/squareup/kotlinpoet/metadata/specs/classinspectors/ClassInspectorUtilTest.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.kotlinpoet.metadata.specs.classinspectors + +import com.google.common.truth.Truth.assertThat +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview +import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil +import kotlin.test.Test + +@KotlinPoetMetadataPreview +class ClassInspectorUtilTest { + + @Test fun createClassName_simple() { + assertThat(ClassInspectorUtil.createClassName("some/path/Foo")) + .isEqualTo(ClassName("some.path", "Foo")) + } + + @Test fun createClassName_nested() { + assertThat(ClassInspectorUtil.createClassName("some/path/Foo.Nested")) + .isEqualTo(ClassName("some.path", "Foo", "Nested")) + } + + @Test fun createClassName_simple_dollarNameStart() { + assertThat(ClassInspectorUtil.createClassName("some/path/Foo$")) + .isEqualTo(ClassName("some.path", "Foo$")) + } + + @Test fun createClassName_simple_dollarNameMiddle() { + assertThat(ClassInspectorUtil.createClassName("some/path/Fo${'$'}o")) + .isEqualTo(ClassName("some.path", "Fo${'$'}o")) + } + + @Test fun createClassName_simple_dollarNameEnd() { + assertThat(ClassInspectorUtil.createClassName("some/path/Nested.${'$'}Foo")) + .isEqualTo(ClassName("some.path", "Nested", "${'$'}Foo")) + } + + @Test fun createClassName_nested_dollarNameStart() { + assertThat(ClassInspectorUtil.createClassName("some/path/Nested.Foo$")) + .isEqualTo(ClassName("some.path", "Nested", "Foo$")) + } + + @Test fun createClassName_nested_dollarNameMiddle() { + assertThat(ClassInspectorUtil.createClassName("some/path/Nested.Fo${'$'}o")) + .isEqualTo(ClassName("some.path", "Nested", "Fo${'$'}o")) + } + + @Test fun createClassName_nested_dollarNameEnd() { + assertThat(ClassInspectorUtil.createClassName("some/path/Nested.${'$'}Foo")) + .isEqualTo(ClassName("some.path", "Nested", "${'$'}Foo")) + } + + @Test fun createClassName_noPackageName() { + assertThat(ClassInspectorUtil.createClassName("ClassWithNoPackage")) + .isEqualTo(ClassName("", "ClassWithNoPackage")) + } + + // Regression test for avoiding https://github.com/square/kotlinpoet/issues/795 + @Test fun createClassName_noEmptyNames() { + val noPackage = ClassInspectorUtil.createClassName("ClassWithNoPackage") + assertThat(noPackage.simpleNames.any { it.isEmpty() }).isFalse() + + val withPackage = ClassInspectorUtil.createClassName("path/to/ClassWithNoPackage") + assertThat(withPackage.simpleNames.any { it.isEmpty() }).isFalse() + } + + @Test fun createClassName_packageWithCaps() { + assertThat(ClassInspectorUtil.createClassName("some/Path/Foo.Nested")) + .isEqualTo(ClassName("some.Path", "Foo", "Nested")) + } + + @Test fun throwsSpec_normal() { + assertThat(ClassInspectorUtil.createThrowsSpec(listOf(Exception::class.asClassName()))) + .isEqualTo( + AnnotationSpec.builder(Throws::class.asClassName()) + .addMember("exceptionClasses = [%T::class]", Exception::class.asClassName()) + .build(), + ) + } +} |