| /* |
| * Copyright 2020 The Android Open Source Project |
| * |
| * 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 |
| * |
| * http://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 androidx.room.compiler.processing |
| |
| import androidx.room.compiler.processing.util.Source |
| import androidx.room.compiler.processing.util.XTestInvocation |
| import androidx.room.compiler.processing.util.compileFiles |
| import androidx.room.compiler.processing.util.getAllFieldNames |
| import androidx.room.compiler.processing.util.getField |
| import androidx.room.compiler.processing.util.getMethodByJvmName |
| import androidx.room.compiler.processing.util.runProcessorTest |
| import com.google.common.truth.Truth.assertThat |
| import com.google.common.truth.Truth.assertWithMessage |
| import com.squareup.javapoet.ClassName |
| import com.squareup.javapoet.ParameterizedTypeName |
| import com.squareup.javapoet.TypeName |
| import com.squareup.javapoet.TypeVariableName |
| import org.junit.Test |
| import org.junit.runner.RunWith |
| import org.junit.runners.JUnit4 |
| |
| @RunWith(JUnit4::class) |
| class XTypeElementTest { |
| @Test |
| fun qualifiedNames() { |
| val src1 = Source.kotlin( |
| "Foo.kt", |
| """ |
| class TopLevel |
| """.trimIndent() |
| ) |
| val src2 = Source.kotlin( |
| "Bar.kt", |
| """ |
| package foo.bar |
| class InFooBar |
| """.trimIndent() |
| ) |
| val src3 = Source.java( |
| "foo.bar.Outer", |
| """ |
| package foo.bar; |
| public class Outer { |
| public static class Nested { |
| } |
| } |
| """ |
| ) |
| runProcessorTest( |
| sources = listOf(src1, src2, src3) |
| ) { invocation -> |
| invocation.processingEnv.requireTypeElement("TopLevel").let { |
| assertThat(it.packageName).isEqualTo("") |
| assertThat(it.name).isEqualTo("TopLevel") |
| assertThat(it.qualifiedName).isEqualTo("TopLevel") |
| assertThat(it.className).isEqualTo(ClassName.get("", "TopLevel")) |
| } |
| invocation.processingEnv.requireTypeElement("foo.bar.InFooBar").let { |
| assertThat(it.packageName).isEqualTo("foo.bar") |
| assertThat(it.name).isEqualTo("InFooBar") |
| assertThat(it.qualifiedName).isEqualTo("foo.bar.InFooBar") |
| assertThat(it.className).isEqualTo(ClassName.get("foo.bar", "InFooBar")) |
| } |
| invocation.processingEnv.requireTypeElement("foo.bar.Outer").let { |
| assertThat(it.packageName).isEqualTo("foo.bar") |
| assertThat(it.name).isEqualTo("Outer") |
| assertThat(it.qualifiedName).isEqualTo("foo.bar.Outer") |
| assertThat(it.className).isEqualTo( |
| ClassName.get("foo.bar", "Outer") |
| ) |
| } |
| invocation.processingEnv.requireTypeElement("foo.bar.Outer.Nested").let { |
| assertThat(it.packageName).isEqualTo("foo.bar") |
| assertThat(it.name).isEqualTo("Nested") |
| assertThat(it.qualifiedName).isEqualTo("foo.bar.Outer.Nested") |
| assertThat(it.className).isEqualTo( |
| ClassName.get("foo.bar", "Outer", "Nested") |
| ) |
| } |
| if (invocation.isKsp) { |
| // these are KSP specific tests, typenames are tested elsewhere |
| invocation.processingEnv.requireTypeElement("java.lang.Integer").let { |
| // always return kotlin types, this is what compiler does |
| assertThat(it.packageName).isEqualTo("kotlin") |
| assertThat(it.name).isEqualTo("Int") |
| assertThat(it.qualifiedName).isEqualTo("kotlin.Int") |
| } |
| invocation.processingEnv.requireTypeElement("kotlin.Int").let { |
| assertThat(it.packageName).isEqualTo("kotlin") |
| assertThat(it.name).isEqualTo("Int") |
| assertThat(it.qualifiedName).isEqualTo("kotlin.Int") |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun typeAndSuperType() { |
| val src = Source.kotlin( |
| "foo.kt", |
| """ |
| package foo.bar; |
| class Baz : MyInterface, AbstractClass() { |
| } |
| abstract class AbstractClass {} |
| interface MyInterface {} |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| invocation.processingEnv.requireTypeElement("foo.bar.Baz").let { |
| assertThat(it.superType).isEqualTo( |
| invocation.processingEnv.requireType("foo.bar.AbstractClass") |
| ) |
| assertThat(it.type).isEqualTo( |
| invocation.processingEnv.requireType("foo.bar.Baz") |
| ) |
| assertThat(it.isInterface()).isFalse() |
| assertThat(it.isKotlinObject()).isFalse() |
| assertThat(it.isAbstract()).isFalse() |
| } |
| invocation.processingEnv.requireTypeElement("foo.bar.AbstractClass").let { |
| assertThat(it.superType).let { |
| // KSP does not return Object / Any as super class |
| if (invocation.isKsp) { |
| it.isNull() |
| } else { |
| it.isEqualTo( |
| invocation.processingEnv.requireType(TypeName.OBJECT) |
| ) |
| } |
| } |
| assertThat(it.isAbstract()).isTrue() |
| assertThat(it.isInterface()).isFalse() |
| assertThat(it.type).isEqualTo( |
| invocation.processingEnv.requireType("foo.bar.AbstractClass") |
| ) |
| } |
| invocation.processingEnv.requireTypeElement("foo.bar.MyInterface").let { |
| assertThat(it.superType).isNull() |
| assertThat(it.isInterface()).isTrue() |
| assertThat(it.type).isEqualTo( |
| invocation.processingEnv.requireType("foo.bar.MyInterface") |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun superInterfaces() { |
| val src = Source.kotlin( |
| "foo.kt", |
| """ |
| package foo.bar; |
| class Baz : MyInterface<String>, AbstractClass() { |
| } |
| abstract class AbstractClass {} |
| interface MyInterface<E> {} |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| invocation.processingEnv.requireTypeElement("foo.bar.Baz").let { |
| assertThat(it.superInterfaces).hasSize(1) |
| val superInterface = it.superInterfaces.first { type -> |
| type.rawType.toString() == "foo.bar.MyInterface" |
| } |
| assertThat(superInterface.typeArguments).hasSize(1) |
| assertThat(superInterface.typeArguments[0].typeName) |
| .isEqualTo(ClassName.get("java.lang", "String")) |
| } |
| } |
| } |
| |
| @Test |
| fun nestedClassName() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| package foo.bar; |
| class Outer { |
| class Inner |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| invocation.processingEnv.requireTypeElement("foo.bar.Outer").let { |
| assertThat(it.className).isEqualTo(ClassName.get("foo.bar", "Outer")) |
| assertThat(it.isNested()).isFalse() |
| assertThat(it.enclosingTypeElement).isNull() |
| } |
| invocation.processingEnv.requireTypeElement("foo.bar.Outer.Inner").let { |
| assertThat(it.className).isEqualTo(ClassName.get("foo.bar", "Outer", "Inner")) |
| assertThat(it.packageName).isEqualTo("foo.bar") |
| assertThat(it.name).isEqualTo("Inner") |
| assertThat(it.isNested()).isTrue() |
| assertThat(it.enclosingTypeElement).isEqualTo( |
| invocation.processingEnv.requireTypeElement("foo.bar.Outer") |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun modifiers() { |
| val kotlinSrc = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class OpenClass |
| abstract class AbstractClass |
| object MyObject |
| interface MyInterface |
| class Final |
| private class PrivateClass |
| class OuterKotlinClass { |
| inner class InnerKotlinClass |
| class NestedKotlinClass |
| } |
| annotation class KotlinAnnotation |
| data class DataClass(val foo: Int) |
| inline class InlineClass(val foo: Int) |
| fun interface FunInterface { |
| fun foo() |
| } |
| """.trimIndent() |
| ) |
| val javaSrc = Source.java( |
| "OuterJavaClass", |
| """ |
| public class OuterJavaClass { |
| public class InnerJavaClass {} |
| public static class NestedJavaClass {} |
| } |
| """.trimIndent() |
| ) |
| val javaAnnotationSrc = Source.java( |
| "JavaAnnotation", |
| """ |
| public @interface JavaAnnotation { |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(kotlinSrc, javaSrc, javaAnnotationSrc) |
| ) { invocation -> |
| fun getModifiers(element: XTypeElement): Set<String> { |
| val result = mutableSetOf<String>() |
| if (element.isAbstract()) result.add("abstract") |
| if (element.isFinal()) result.add("final") |
| if (element.isPrivate()) result.add("private") |
| if (element.isProtected()) result.add("protected") |
| if (element.isPublic()) result.add("public") |
| if (element.isKotlinObject()) result.add("object") |
| if (element.isCompanionObject()) result.add("companion") |
| if (element.isFunctionalInterface()) result.add("fun") |
| if (element.isClass()) result.add("class") |
| if (element.isDataClass()) result.add("data") |
| if (element.isValueClass()) result.add("value") |
| if (element.isExpect()) result.add("expect") |
| if (element.isInterface()) result.add("interface") |
| if (element.isStatic()) result.add("static") |
| if (element.isAnnotationClass()) result.add("annotation") |
| return result |
| } |
| |
| fun getModifiers(qName: String): Set<String> = getModifiers( |
| invocation.processingEnv |
| .requireTypeElement(qName) |
| ) |
| |
| assertThat(getModifiers("OpenClass")) |
| .containsExactly("public", "class") |
| assertThat(getModifiers("AbstractClass")) |
| .containsExactly("abstract", "public", "class") |
| assertThat(getModifiers("MyObject")) |
| .containsExactly("final", "public", "object") |
| assertThat(getModifiers("MyInterface")) |
| .containsExactly("abstract", "interface", "public") |
| assertThat(getModifiers("Final")) |
| .containsExactly("final", "public", "class") |
| assertThat(getModifiers("PrivateClass")) |
| .containsExactlyElementsIn( |
| if (invocation.isKsp) { |
| listOf("private", "final", "class") |
| } else { |
| // java does not support top level private classes. |
| listOf("final", "class") |
| } |
| ) |
| assertThat(getModifiers("OuterKotlinClass.InnerKotlinClass")) |
| .containsExactly("final", "public", "class") |
| assertThat(getModifiers("OuterKotlinClass.NestedKotlinClass")) |
| .containsExactly("final", "public", "static", "class") |
| assertThat(getModifiers("OuterJavaClass.InnerJavaClass")) |
| .containsExactly("public", "class") |
| assertThat(getModifiers("OuterJavaClass.NestedJavaClass")) |
| .containsExactly("public", "static", "class") |
| assertThat(getModifiers("JavaAnnotation")) |
| .containsExactly("abstract", "public", "annotation") |
| assertThat(getModifiers("KotlinAnnotation")).apply { |
| // KSP vs KAPT metadata have a difference in final vs abstract modifiers |
| // for annotation types. |
| if (invocation.isKsp) { |
| containsExactly("final", "public", "annotation") |
| } else { |
| containsExactly("abstract", "public", "annotation") |
| } |
| } |
| assertThat(getModifiers("DataClass")) |
| .containsExactly("public", "final", "class", "data") |
| assertThat(getModifiers("InlineClass")) |
| .containsExactly("public", "final", "class", "value") |
| assertThat(getModifiers("FunInterface")) |
| .containsExactly("public", "abstract", "interface", "fun") |
| } |
| } |
| |
| @Test |
| fun kindName() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| class MyClass |
| interface MyInterface |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| invocation.processingEnv.requireTypeElement("MyClass").let { |
| assertThat(it.kindName()).isEqualTo("class") |
| } |
| invocation.processingEnv.requireTypeElement("MyInterface").let { |
| assertThat(it.kindName()).isEqualTo("interface") |
| } |
| } |
| } |
| |
| @Test |
| fun fieldBasic() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class BaseClass<T>(val genericProp : T) { |
| fun baseMethod(input: T) {} |
| } |
| class SubClass(x : Int) : BaseClass<Int>(x) { |
| val subClassProp : String = "abc" |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val baseClass = invocation.processingEnv.requireTypeElement("BaseClass") |
| assertThat(baseClass.getAllFieldNames()).containsExactly("genericProp") |
| assertThat(baseClass.getDeclaredFields().map { it.name }) |
| .containsExactly("genericProp") |
| val subClass = invocation.processingEnv.requireTypeElement("SubClass") |
| assertThat(subClass.getAllFieldNames()).containsExactly("genericProp", "subClassProp") |
| assertThat(subClass.getDeclaredFields().map { it.name }) |
| .containsExactly("subClassProp") |
| |
| val baseMethod = baseClass.getMethodByJvmName("baseMethod") |
| baseMethod.asMemberOf(subClass.type).let { methodType -> |
| val genericArg = methodType.parameterTypes.first() |
| assertThat(genericArg.typeName).isEqualTo(TypeName.INT.box()) |
| } |
| |
| baseClass.getField("genericProp").let { field -> |
| assertThat(field.type.typeName).isEqualTo(TypeVariableName.get("T")) |
| } |
| |
| subClass.getField("genericProp").let { field -> |
| // this is tricky because even though it is non-null it, it should still be boxed |
| assertThat(field.type.typeName).isEqualTo(TypeName.INT.box()) |
| } |
| } |
| } |
| |
| @Test |
| fun fieldsOverride() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class BaseClass( |
| open val value : List<Int> |
| ) |
| class SubClass( |
| override val value : MutableList<Int> |
| ) : BaseClass(value) |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val baseClass = invocation.processingEnv.requireTypeElement("BaseClass") |
| assertThat(baseClass.getAllFieldNames()).containsExactly("value") |
| val subClass = invocation.processingEnv.requireTypeElement("SubClass") |
| assertThat(subClass.getAllFieldNames()).containsExactly("value") |
| assertThat( |
| baseClass.getField("value").type.typeName |
| ).isEqualTo( |
| ParameterizedTypeName.get(List::class.java, Integer::class.java) |
| ) |
| assertThat( |
| subClass.getField("value").type.typeName |
| ).isEqualTo( |
| ParameterizedTypeName.get(List::class.java, Integer::class.java) |
| ) |
| } |
| } |
| |
| @Test |
| fun fieldsMethodsWithoutBacking() { |
| fun buildSrc(pkg: String) = Source.kotlin( |
| "Foo.kt", |
| """ |
| package $pkg; |
| class Subject { |
| val realField: String = "" |
| get() = field |
| val noBackingVal: String |
| get() = "" |
| var noBackingVar: String |
| get() = "" |
| set(value) {} |
| |
| companion object { |
| @JvmStatic |
| val staticRealField: String = "" |
| get() = field |
| @JvmStatic |
| val staticNoBackingVal: String |
| get() = "" |
| @JvmStatic |
| var staticNoBackingVar: String |
| get() = "" |
| set(value) {} |
| } |
| } |
| """.trimIndent() |
| ) |
| val lib = compileFiles(listOf(buildSrc("lib"))) |
| runProcessorTest( |
| sources = listOf(buildSrc("main")), |
| classpath = lib |
| ) { invocation -> |
| listOf("lib", "main").forEach { pkg -> |
| val subject = invocation.processingEnv.requireTypeElement("$pkg.Subject") |
| val declaredFields = subject.getDeclaredFields().map { it.name } - |
| listOf("Companion") // skip Companion, KAPT generates it |
| val expectedFields = listOf("realField", "staticRealField") |
| assertWithMessage(subject.qualifiedName) |
| .that(declaredFields) |
| .containsExactlyElementsIn(expectedFields) |
| val allFields = subject.getAllFieldsIncludingPrivateSupers().map { it.name } - |
| listOf("Companion") // skip Companion, KAPT generates it |
| assertWithMessage(subject.qualifiedName) |
| .that(allFields.toList()) |
| .containsExactlyElementsIn(expectedFields) |
| val methodNames = subject.getDeclaredMethods().map { it.jvmName } |
| assertWithMessage(subject.qualifiedName) |
| .that(methodNames) |
| .containsAtLeast("getNoBackingVal", "getNoBackingVar", "setNoBackingVar") |
| assertWithMessage(subject.qualifiedName) |
| .that(methodNames) |
| .doesNotContain("setNoBackingVal") |
| } |
| } |
| } |
| |
| @Test |
| fun abstractFields() { |
| fun buildSource(pkg: String) = Source.kotlin( |
| "Foo.kt", |
| """ |
| package $pkg; |
| abstract class Subject { |
| val value: String = "" |
| abstract val abstractValue: String |
| companion object { |
| var realCompanion: String = "" |
| @JvmStatic |
| var jvmStatic: String = "" |
| } |
| } |
| """.trimIndent() |
| ) |
| |
| val lib = compileFiles(listOf(buildSource("lib"))) |
| runProcessorTest( |
| sources = listOf(buildSource("main")), |
| classpath = lib |
| ) { invocation -> |
| listOf("lib", "main").forEach { pkg -> |
| val subject = invocation.processingEnv.requireTypeElement("$pkg.Subject") |
| val declaredFields = subject.getDeclaredFields().map { it.name } - |
| listOf("Companion") |
| val expectedFields = listOf("value", "realCompanion", "jvmStatic") |
| assertWithMessage(subject.qualifiedName) |
| .that(declaredFields) |
| .containsExactlyElementsIn(expectedFields) |
| } |
| } |
| } |
| |
| @Test |
| fun lateinitFields() { |
| fun buildSource(pkg: String) = Source.kotlin( |
| "Foo.kt", |
| """ |
| package $pkg |
| class Subject { |
| lateinit var x:String |
| var y:String = "abc" |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(buildSource("app")), |
| classpath = compileFiles(listOf(buildSource("lib"))) |
| ) { invocation -> |
| listOf("app", "lib").forEach { pkg -> |
| val subject = invocation.processingEnv.requireTypeElement("$pkg.Subject") |
| assertWithMessage(subject.fallbackLocationText) |
| .that(subject.getDeclaredFields().map { it.name }) |
| .containsExactly( |
| "x", "y" |
| ) |
| assertWithMessage(subject.fallbackLocationText) |
| .that(subject.getDeclaredMethods().map { it.jvmName }) |
| .containsExactly( |
| "getX", "setX", "getY", "setY" |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun fieldsInInterfaces() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| interface MyInterface { |
| var x:Int |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val element = invocation.processingEnv.requireTypeElement("MyInterface") |
| assertThat(element.getAllFieldsIncludingPrivateSupers().toList()).isEmpty() |
| element.getMethodByJvmName("getX").let { |
| assertThat(it.isAbstract()).isTrue() |
| } |
| element.getMethodByJvmName("setX").let { |
| assertThat(it.isAbstract()).isTrue() |
| } |
| } |
| } |
| |
| @Test |
| fun fieldsInAbstractClass() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| abstract class MyAbstractClass { |
| @JvmField |
| var jvmVar: Int = 0 |
| abstract var abstractVar: Int |
| var nonAbstractVar: Int = 0 |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val element = invocation.processingEnv.requireTypeElement("MyAbstractClass") |
| assertThat( |
| element.getAllFieldNames() |
| ).containsExactly( |
| "nonAbstractVar", "jvmVar" |
| ) |
| assertThat( |
| element.getDeclaredMethods().map { it.jvmName } |
| ).containsExactly( |
| "getAbstractVar", "setAbstractVar", |
| "getNonAbstractVar", "setNonAbstractVar" |
| ) |
| element.getMethodByJvmName("getAbstractVar").let { |
| assertThat(it.isAbstract()).isTrue() |
| } |
| element.getMethodByJvmName("setAbstractVar").let { |
| assertThat(it.isAbstract()).isTrue() |
| } |
| |
| element.getMethodByJvmName("getNonAbstractVar").let { |
| assertThat(it.isAbstract()).isFalse() |
| } |
| element.getMethodByJvmName("setNonAbstractVar").let { |
| assertThat(it.isAbstract()).isFalse() |
| } |
| } |
| } |
| |
| @Test |
| fun propertyGettersSetters() { |
| val dependencyJavaSource = Source.java( |
| "DependencyJavaSubject.java", |
| """ |
| class DependencyJavaSubject { |
| int myField; |
| private int mutable; |
| int immutable; |
| int getMutable() {return 3;} |
| void setMutable(int x) {} |
| int getImmutable() {return 3;} |
| } |
| """.trimIndent() |
| ) |
| val dependencyKotlinSource = Source.kotlin( |
| "DependencyKotlinSubject.kt", |
| """ |
| class DependencyKotlinSubject { |
| private val myField = 0 |
| var mutable: Int = 0 |
| val immutable:Int = 0 |
| } |
| """.trimIndent() |
| ) |
| val dependency = compileFiles(listOf(dependencyJavaSource, dependencyKotlinSource)) |
| val javaSource = Source.java( |
| "JavaSubject.java", |
| """ |
| class JavaSubject { |
| int myField; |
| private int mutable; |
| int immutable; |
| int getMutable() {return 3;} |
| void setMutable(int x) {} |
| int getImmutable() {return 3;} |
| } |
| """.trimIndent() |
| ) |
| val kotlinSource = Source.kotlin( |
| "KotlinSubject.kt", |
| """ |
| class KotlinSubject { |
| private val myField = 0 |
| var mutable: Int = 0 |
| val immutable:Int = 0 |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| listOf(javaSource, kotlinSource), |
| classpath = dependency |
| ) { invocation -> |
| listOf( |
| "JavaSubject", "DependencyJavaSubject", |
| "KotlinSubject", "DependencyKotlinSubject" |
| ).map { |
| invocation.processingEnv.requireTypeElement(it) |
| }.forEach { subject -> |
| assertWithMessage(subject.qualifiedName) |
| .that( |
| subject.getDeclaredMethods().map { |
| it.jvmName |
| } |
| ).containsExactly( |
| "getMutable", "setMutable", "getImmutable" |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun declaredAndInstanceMethods() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class Base(x:Int) { |
| open fun baseFun(): Int = TODO() |
| suspend fun suspendFun(): Int = TODO() |
| private fun privateBaseFun(): Int = TODO() |
| companion object { |
| @JvmStatic |
| fun staticBaseFun(): Int = TODO() |
| fun companionMethod(): Int = TODO() |
| } |
| } |
| open class SubClass : Base { |
| constructor(y:Int): super(y) { |
| } |
| constructor(x:Int, y:Int): super(y) { |
| } |
| override fun baseFun(): Int = TODO() |
| fun subFun(): Int = TODO() |
| private fun privateSubFun(): Int = TODO() |
| companion object { |
| @JvmStatic |
| fun staticFun(): Int = TODO() |
| } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val base = invocation.processingEnv.requireTypeElement("Base") |
| val objectMethodNames = invocation.objectMethodNames() |
| assertThat(base.getDeclaredMethods().jvmNames()).containsExactly( |
| "baseFun", "suspendFun", "privateBaseFun", "staticBaseFun" |
| ) |
| |
| val sub = invocation.processingEnv.requireTypeElement("SubClass") |
| assertThat(sub.getDeclaredMethods().jvmNames()).containsExactly( |
| "baseFun", "subFun", "privateSubFun", "staticFun" |
| ) |
| assertThat( |
| sub.getAllNonPrivateInstanceMethods().jvmNames() - objectMethodNames |
| ).containsExactly( |
| "baseFun", "suspendFun", "subFun" |
| ) |
| } |
| } |
| |
| @Test |
| fun diamondOverride() { |
| fun buildSrc(pkg: String) = Source.kotlin( |
| "Foo.kt", |
| """ |
| package $pkg; |
| interface Parent<T> { |
| fun parent(t: T) |
| } |
| |
| interface Child1<T> : Parent<T> { |
| fun child1(t: T) |
| } |
| |
| interface Child2<T> : Parent<T> { |
| fun child2(t: T) |
| } |
| |
| abstract class Subject1 : Child1<String>, Child2<String>, Parent<String> |
| abstract class Subject2 : Child1<String>, Parent<String> |
| abstract class Subject3 : Child1<String>, Parent<String> { |
| abstract override fun parent(t: String) |
| } |
| """.trimIndent() |
| ) |
| |
| runProcessorTest( |
| sources = listOf(buildSrc("app")), |
| classpath = compileFiles(listOf(buildSrc("lib"))) |
| ) { invocation -> |
| listOf("lib", "app").forEach { pkg -> |
| val objectMethodNames = invocation.processingEnv.requireTypeElement(Any::class) |
| .getAllMethods().jvmNames() |
| |
| fun XMethodElement.signature( |
| owner: XType |
| ): String { |
| val methodType = this.asMemberOf(owner) |
| val params = methodType.parameterTypes.joinToString(",") { |
| it.typeName.toString() |
| } |
| return "$jvmName($params):${returnType.typeName}" |
| } |
| |
| fun XTypeElement.allMethodSignatures(): List<String> = getAllMethods().filterNot { |
| it.jvmName in objectMethodNames |
| }.map { it.signature(this.type) }.toList() |
| invocation.processingEnv.requireTypeElement("$pkg.Subject1").let { subject -> |
| assertWithMessage(subject.qualifiedName).that( |
| subject.allMethodSignatures() |
| ).containsExactly( |
| "child1(java.lang.String):void", |
| "child2(java.lang.String):void", |
| "parent(java.lang.String):void", |
| ) |
| } |
| invocation.processingEnv.requireTypeElement("$pkg.Subject2").let { subject -> |
| assertWithMessage(subject.qualifiedName).that( |
| subject.allMethodSignatures() |
| ).containsExactly( |
| "child1(java.lang.String):void", |
| "parent(java.lang.String):void", |
| ) |
| } |
| invocation.processingEnv.requireTypeElement("$pkg.Subject3").let { subject -> |
| assertWithMessage(subject.qualifiedName).that( |
| subject.allMethodSignatures() |
| ).containsExactly( |
| "child1(java.lang.String):void", |
| "parent(java.lang.String):void", |
| ) |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun overrideMethodWithCovariantReturnType() { |
| val src = Source.kotlin( |
| "ParentWithExplicitOverride.kt", |
| """ |
| interface ParentWithExplicitOverride: ChildInterface, Child { |
| override fun child(): Child |
| } |
| |
| interface ParentWithoutExplicitOverride: ChildInterface, Child |
| |
| interface Child: ChildInterface { |
| override fun child(): Child |
| } |
| |
| interface ChildInterface { |
| fun child(): ChildInterface |
| } |
| """.trimIndent() |
| ) |
| |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val objectMethodNames = invocation.processingEnv.requireTypeElement(Any::class) |
| .getAllMethods().jvmNames() |
| fun XMethodElement.signature( |
| owner: XType |
| ): String { |
| val methodType = this.asMemberOf(owner) |
| val params = methodType.parameterTypes.joinToString(",") { |
| it.typeName.toString() |
| } |
| return "$jvmName($params):${returnType.typeName}" |
| } |
| |
| fun XTypeElement.allMethodSignatures(): List<String> = getAllMethods().filterNot { |
| it.jvmName in objectMethodNames |
| }.map { it.signature(this.type) }.toList() |
| |
| invocation.processingEnv.requireTypeElement( |
| "ParentWithExplicitOverride" |
| ).let { parent -> |
| assertWithMessage(parent.qualifiedName).that( |
| parent.allMethodSignatures() |
| ).containsExactly( |
| "child():Child" |
| ) |
| } |
| |
| invocation.processingEnv.requireTypeElement( |
| "ParentWithoutExplicitOverride" |
| ).let { parent -> |
| assertWithMessage(parent.qualifiedName).that( |
| parent.allMethodSignatures() |
| ).containsExactly( |
| "child():Child" |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun allMethods() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class Base(x:Int) { |
| constructor(x:Int, y:Int): this(x) { |
| } |
| fun baseMethod(): Int = TODO() |
| open fun overriddenMethod(): Int = TODO() |
| private fun privateBaseMethod(): Int = TODO() |
| companion object { |
| @JvmStatic |
| private fun privateBaseCompanionMethod(): Int = TODO() |
| @JvmStatic |
| fun baseCompanionMethod(): Int = TODO() |
| } |
| } |
| interface MyInterface { |
| fun interfaceMethod(): Int = TODO() |
| } |
| class SubClass : Base, MyInterface { |
| constructor(x:Int): super(x) { |
| } |
| constructor(x:Int, y:Int): super(y) { |
| } |
| fun subMethod(): Int = TODO() |
| fun privateSubMethod(): Int = TODO() |
| override fun overriddenMethod(): Int = TODO() |
| override fun interfaceMethod(): Int = TODO() |
| companion object { |
| fun dontSeeThisOne(): Int = TODO() |
| @JvmStatic |
| fun subCompanionMethod(): Int = TODO() |
| } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val objectMethodNames = invocation.objectMethodNames() |
| val klass = invocation.processingEnv.requireTypeElement("SubClass") |
| assertThat( |
| klass.getAllMethods().jvmNames() - objectMethodNames |
| ).containsExactly( |
| "baseMethod", "overriddenMethod", "baseCompanionMethod", |
| "interfaceMethod", "subMethod", "privateSubMethod", "subCompanionMethod" |
| ) |
| } |
| } |
| |
| /** |
| * When JvmNames is used along with a suppression over the error, the behavior becomes |
| * complicated. Normally, JvmName annotation is not allowed in overrides and open methods, yet |
| * developers can still use it by putting a suppression over it. The compiler will generate a |
| * delegating method in these cases in the .class file, yet in KSP, we don't really see that |
| * method (also shouldn't ideally). |
| * |
| * This test is here to acknowledge that the behavior is inconsistent yet working as intended |
| * from XProcessing's perspective. |
| * |
| * Also see: https://youtrack.jetbrains.com/issue/KT-50782 as a sign why this suppression is |
| * not worth supporting :). |
| */ |
| @Test |
| fun allMethods_withJvmNames() { |
| fun buildSource(pkg: String) = listOf( |
| Source.kotlin( |
| "Foo.kt", |
| """ |
| package $pkg |
| interface Interface { |
| fun f1() |
| @JvmName("notF2") |
| @Suppress("INAPPLICABLE_JVM_NAME") |
| fun f2() |
| } |
| abstract class Subject : Interface { |
| @JvmName("notF1") |
| @Suppress("INAPPLICABLE_JVM_NAME") |
| override fun f1() { |
| } |
| } |
| """.trimIndent() |
| ) |
| ) |
| |
| runProcessorTest( |
| sources = buildSource("app"), |
| classpath = compileFiles(buildSource("lib")) |
| ) { invocation -> |
| listOf("app", "lib").forEach { |
| val appSubject = invocation.processingEnv.requireTypeElement("$it.Subject") |
| val methodNames = appSubject.getAllMethods().map { it.name }.toList() |
| val methodJvmNames = appSubject.getAllMethods().map { it.jvmName }.toList() |
| val objectMethodNames = invocation.objectMethodNames() |
| if (invocation.isKsp) { |
| assertThat(methodNames - objectMethodNames).containsExactly( |
| "f1", "f2" |
| ) |
| assertThat(methodJvmNames - objectMethodNames).containsExactly( |
| "notF1", "notF2" |
| ) |
| } else { |
| assertThat(methodNames - objectMethodNames).containsExactly( |
| "f1", "f1", "f2" |
| ) |
| assertThat(methodJvmNames - objectMethodNames).containsExactly( |
| "f1", "notF1", "notF2" |
| ) |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun gettersSetters() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class JustGetter(val x:Int) { |
| private val invisible:Int = TODO() |
| private var invisibleMutable:Int = TODO() |
| } |
| class GetterSetter(var y:Int) : JustGetter(y) { |
| private val subInvisible:Int = TODO() |
| private var subInvisibleMutable:Int = TODO() |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val objectMethodNames = invocation.objectMethodNames() |
| invocation.processingEnv.requireTypeElement("JustGetter").let { base -> |
| assertThat(base.getDeclaredMethods().jvmNames()).containsExactly( |
| "getX" |
| ) |
| assertThat(base.getAllMethods().jvmNames() - objectMethodNames).containsExactly( |
| "getX" |
| ) |
| assertThat( |
| base.getAllNonPrivateInstanceMethods().jvmNames() - objectMethodNames |
| ).containsExactly( |
| "getX" |
| ) |
| } |
| invocation.processingEnv.requireTypeElement("GetterSetter").let { sub -> |
| assertThat(sub.getDeclaredMethods().jvmNames()).containsExactly( |
| "getY", "setY" |
| ) |
| assertThat(sub.getAllMethods().jvmNames() - objectMethodNames).containsExactly( |
| "getX", "getY", "setY" |
| ) |
| assertThat( |
| sub.getAllNonPrivateInstanceMethods().jvmNames() - objectMethodNames |
| ).containsExactly( |
| "getX", "getY", "setY" |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun companion() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class CompanionSubject { |
| companion object { |
| @JvmStatic |
| var mutableStatic: String = "a" |
| @JvmStatic |
| val immutableStatic: String = "bar" |
| val companionProp: Int = 3 |
| @get:JvmStatic |
| var companionProp_getterJvmStatic:Int =3 |
| @set:JvmStatic |
| var companionProp_setterJvmStatic:Int =3 |
| |
| fun companionMethod() { |
| } |
| |
| @JvmStatic |
| fun companionMethodWithJvmStatic() {} |
| } |
| } |
| class SubClass : CompanionSubject() |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val objectMethodNames = invocation.processingEnv.requireTypeElement( |
| Any::class |
| ).getAllMethods().jvmNames() |
| val subject = invocation.processingEnv.requireTypeElement("CompanionSubject") |
| assertThat(subject.getAllFieldNames() - "Companion").containsExactly( |
| "mutableStatic", "immutableStatic", "companionProp", |
| "companionProp_getterJvmStatic", "companionProp_setterJvmStatic" |
| ) |
| val expectedMethodNames = listOf( |
| "getMutableStatic", "setMutableStatic", "getImmutableStatic", |
| "getCompanionProp_getterJvmStatic", "setCompanionProp_setterJvmStatic", |
| "companionMethodWithJvmStatic" |
| ) |
| assertThat( |
| subject.getDeclaredMethods().jvmNames() |
| ).containsExactlyElementsIn( |
| expectedMethodNames |
| ) |
| assertThat( |
| subject.getAllMethods().jvmNames() - objectMethodNames |
| ).containsExactlyElementsIn( |
| expectedMethodNames |
| ) |
| assertThat( |
| subject.getAllNonPrivateInstanceMethods().jvmNames() - objectMethodNames |
| ).isEmpty() |
| val subClass = invocation.processingEnv.requireTypeElement("SubClass") |
| assertThat(subClass.getDeclaredMethods()).isEmpty() |
| assertThat( |
| subClass.getAllMethods().jvmNames() - objectMethodNames |
| ).containsExactlyElementsIn( |
| expectedMethodNames |
| ) |
| |
| // make sure everything coming from companion is marked as static |
| subject.getDeclaredFields().forEach { |
| assertWithMessage(it.name).that(it.isStatic()).isTrue() |
| } |
| subject.getDeclaredMethods().forEach { |
| assertWithMessage(it.jvmName).that(it.isStatic()).isTrue() |
| } |
| |
| // make sure asMemberOf works fine for statics |
| val subClassType = subClass.type |
| subject.getDeclaredFields().forEach { |
| try { |
| it.asMemberOf(subClassType) |
| } catch (th: Throwable) { |
| throw AssertionError("Couldn't run asMemberOf for ${it.name}") |
| } |
| } |
| subject.getDeclaredMethods().forEach { |
| try { |
| it.asMemberOf(subClassType) |
| } catch (th: Throwable) { |
| throw AssertionError("Couldn't run asMemberOf for ${it.jvmName}") |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun gettersSetters_interface() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| interface JustGetter { |
| val x:Int |
| } |
| interface GetterSetter : JustGetter { |
| var y:Int |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| invocation.processingEnv.requireTypeElement("JustGetter").let { base -> |
| assertThat(base.getDeclaredMethods().jvmNames()).containsExactly( |
| "getX" |
| ) |
| assertThat(base.getAllMethods().jvmNames()).containsExactly( |
| "getX" |
| ) |
| assertThat(base.getAllNonPrivateInstanceMethods().jvmNames()).containsExactly( |
| "getX" |
| ) |
| } |
| invocation.processingEnv.requireTypeElement("GetterSetter").let { sub -> |
| assertThat(sub.getDeclaredMethods().jvmNames()).containsExactly( |
| "getY", "setY" |
| ) |
| assertThat(sub.getAllMethods().jvmNames()).containsExactly( |
| "getX", "getY", "setY" |
| ) |
| assertThat(sub.getAllNonPrivateInstanceMethods().jvmNames()).containsExactly( |
| "getX", "getY", "setY" |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun constructors() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| interface MyInterface |
| class NoExplicitConstructor |
| open class Base(x:Int) |
| open class ExplicitConstructor { |
| constructor(x:Int) |
| } |
| open class BaseWithSecondary(x:Int) { |
| constructor(y:String):this(3) |
| } |
| class Sub(x:Int) : Base(x) |
| class SubWith3Constructors() : BaseWithSecondary("abc") { |
| constructor(list:List<String>): this() |
| constructor(list:List<String>, x:Int): this() |
| } |
| abstract class AbstractNoExplicit |
| abstract class AbstractExplicit(x:Int) |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val subjects = listOf( |
| "MyInterface", "NoExplicitConstructor", "Base", "ExplicitConstructor", |
| "BaseWithSecondary", "Sub", "SubWith3Constructors", |
| "AbstractNoExplicit", "AbstractExplicit" |
| ) |
| val constructorCounts = subjects.map { |
| it to invocation.processingEnv.requireTypeElement(it).getConstructors().size |
| } |
| assertThat(constructorCounts) |
| .containsExactly( |
| "MyInterface" to 0, |
| "NoExplicitConstructor" to 1, |
| "Base" to 1, |
| "ExplicitConstructor" to 1, |
| "BaseWithSecondary" to 2, |
| "Sub" to 1, |
| "SubWith3Constructors" to 3, |
| "AbstractNoExplicit" to 1, |
| "AbstractExplicit" to 1 |
| ) |
| |
| val primaryConstructorParameterNames = subjects.map { |
| it to invocation.processingEnv.requireTypeElement(it) |
| .findPrimaryConstructor() |
| ?.parameters?.map { |
| it.name |
| } |
| } |
| assertThat(primaryConstructorParameterNames) |
| .containsExactly( |
| "MyInterface" to null, |
| "NoExplicitConstructor" to emptyList<String>(), |
| "Base" to listOf("x"), |
| "ExplicitConstructor" to null, |
| "BaseWithSecondary" to listOf("x"), |
| "Sub" to listOf("x"), |
| "SubWith3Constructors" to emptyList<String>(), |
| "AbstractNoExplicit" to emptyList<String>(), |
| "AbstractExplicit" to listOf("x") |
| ) |
| } |
| } |
| |
| @Test |
| fun jvmDefault() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| interface MyInterface { |
| fun notJvmDefault() |
| @JvmDefault |
| fun jvmDefault() {} |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val subject = invocation.processingEnv.requireTypeElement("MyInterface") |
| assertThat(subject.getMethodByJvmName("notJvmDefault").isJavaDefault()).isFalse() |
| assertThat(subject.getMethodByJvmName("jvmDefault").isJavaDefault()).isTrue() |
| } |
| } |
| |
| @Test |
| fun constructors_java() { |
| val src = Source.java( |
| "Source", |
| """ |
| import java.util.List; |
| interface MyInterface {} |
| class NoExplicitConstructor{} |
| class Base { |
| Base(int x){} |
| } |
| class ExplicitConstructor { |
| ExplicitConstructor(int x){} |
| } |
| class BaseWithSecondary { |
| BaseWithSecondary(int x){} |
| BaseWithSecondary(String y){} |
| } |
| class Sub extends Base { |
| Sub(int x) { |
| super(x); |
| } |
| } |
| class SubWith3Constructors extends BaseWithSecondary { |
| SubWith3Constructors() { |
| super(3); |
| } |
| SubWith3Constructors(List<String> list) { |
| super(3); |
| } |
| SubWith3Constructors(List<String> list, int x) { |
| super(3); |
| } |
| } |
| abstract class AbstractNoExplicit {} |
| abstract class AbstractExplicit { |
| AbstractExplicit(int x) {} |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val subjects = listOf( |
| "MyInterface", "NoExplicitConstructor", "Base", "ExplicitConstructor", |
| "BaseWithSecondary", "Sub", "SubWith3Constructors", |
| "AbstractNoExplicit", "AbstractExplicit" |
| ) |
| val constructorCounts = subjects.map { |
| it to invocation.processingEnv.requireTypeElement(it).getConstructors().size |
| } |
| assertThat(constructorCounts) |
| .containsExactly( |
| "MyInterface" to 0, |
| "NoExplicitConstructor" to 1, |
| "Base" to 1, |
| "ExplicitConstructor" to 1, |
| "BaseWithSecondary" to 2, |
| "Sub" to 1, |
| "SubWith3Constructors" to 3, |
| "AbstractNoExplicit" to 1, |
| "AbstractExplicit" to 1 |
| ) |
| |
| subjects.forEach { |
| assertWithMessage(it) |
| .that(invocation.processingEnv.requireTypeElement(it).findPrimaryConstructor()) |
| .isNull() |
| } |
| } |
| } |
| |
| @Test |
| fun enumTypeElement() { |
| fun createSources(packageName: String) = listOf( |
| Source.kotlin( |
| "$packageName/KotlinEnum.kt", |
| """ |
| package $packageName |
| enum class KotlinEnum(private val x:Int) { |
| VAL1(1), |
| VAL2(2); |
| |
| fun enumMethod():Unit {} |
| } |
| """.trimIndent() |
| ), |
| Source.java( |
| "$packageName.JavaEnum", |
| """ |
| package $packageName; |
| public enum JavaEnum { |
| VAL1(1), |
| VAL2(2); |
| |
| private int x; |
| |
| JavaEnum(int x) { |
| this.x = x; |
| } |
| void enumMethod() {} |
| } |
| """.trimIndent() |
| ) |
| ) |
| |
| val classpath = compileFiles( |
| createSources("lib") |
| ) |
| runProcessorTest( |
| sources = createSources("app"), |
| classpath = classpath |
| ) { invocation -> |
| listOf( |
| "lib.KotlinEnum", "lib.JavaEnum", |
| "app.KotlinEnum", "app.JavaEnum" |
| ).forEach { qName -> |
| val typeElement = invocation.processingEnv.requireTypeElement(qName) |
| assertWithMessage("$qName is enum") |
| .that(typeElement.isEnum()) |
| .isTrue() |
| assertWithMessage("$qName does not report enum constants in methods") |
| .that(typeElement.getDeclaredMethods().map { it.jvmName }) |
| .run { |
| contains("enumMethod") |
| containsNoneOf("VAL1", "VAL2") |
| } |
| assertWithMessage("$qName can return enum constants") |
| .that((typeElement as XEnumTypeElement).entries.map { it.name }) |
| .containsExactly("VAL1", "VAL2") |
| assertWithMessage("$qName does not report enum constants in fields") |
| .that(typeElement.getAllFieldNames()) |
| .run { |
| contains("x") |
| containsNoneOf("VAL1", "VAL2") |
| } |
| assertWithMessage("$qName does not report enum constants in declared fields") |
| .that(typeElement.getDeclaredFields().map { it.name }) |
| .containsExactly("x") |
| } |
| } |
| } |
| |
| @Test |
| fun enclosedTypes() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| class TopLevelClass { |
| class NestedClass |
| object NestedObject |
| interface NestedInterface |
| enum class NestedEnum { |
| A, B |
| } |
| companion object { |
| val foo = 1 |
| } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val topLevelClass = invocation.processingEnv.requireTypeElement("TopLevelClass") |
| val enclosedTypeElements = topLevelClass.getEnclosedTypeElements() |
| |
| assertThat(enclosedTypeElements) |
| .containsExactly( |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedClass"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedObject"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedInterface"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedEnum"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.Companion"), |
| ) |
| } |
| } |
| |
| @Test |
| fun enclosedTypes_java() { |
| val src = Source.java( |
| "Source", |
| """ |
| class TopLevelClass { |
| class InnerClass { } |
| static class NestedClass { } |
| interface NestedInterface { } |
| enum NestedEnum { |
| A, B |
| } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val topLevelClass = invocation.processingEnv.requireTypeElement("TopLevelClass") |
| val enclosedTypeElements = topLevelClass.getEnclosedTypeElements() |
| |
| assertThat(enclosedTypeElements) |
| .containsExactly( |
| invocation.processingEnv.requireTypeElement("TopLevelClass.InnerClass"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedClass"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedInterface"), |
| invocation.processingEnv.requireTypeElement("TopLevelClass.NestedEnum"), |
| ) |
| } |
| } |
| |
| @Test |
| fun kotlinObjects() { |
| val kotlinSrc = Source.kotlin( |
| "Test.kt", |
| """ |
| package foo.bar |
| class KotlinClass { |
| companion object |
| object NestedObject |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(listOf(kotlinSrc)) { invocation -> |
| val kotlinClass = invocation.processingEnv.requireTypeElement( |
| "foo.bar.KotlinClass") |
| val companionObjects = kotlinClass.getEnclosedTypeElements().filter { |
| it.isCompanionObject() |
| } |
| assertThat(companionObjects.size).isEqualTo(1) |
| val companionObj = companionObjects.first() |
| assertThat(companionObj.isKotlinObject()).isTrue() |
| } |
| } |
| |
| /** |
| * it is good to exclude methods coming from Object when testing as they differ between KSP |
| * and KAPT but irrelevant for Room. |
| */ |
| private fun XTestInvocation.objectMethodNames() = processingEnv |
| .requireTypeElement("java.lang.Object") |
| .getAllMethods() |
| .jvmNames() |
| |
| private fun Sequence<XMethodElement>.jvmNames() = map { |
| it.jvmName |
| }.toList() |
| |
| private fun List<XMethodElement>.jvmNames() = map { |
| it.jvmName |
| }.toList() |
| } |