From 2f9061f1c9c30e79937f37e0970700ee5b6e136e Mon Sep 17 00:00:00 2001 From: Rick Busarow Date: Wed, 1 May 2024 12:26:49 -0500 Subject: [PATCH] consider `ignoreQualifier` arguments when checking for duplicate bindings fixes #986 --- .../anvil/compiler/ContributedBinding.kt | 3 +- .../codegen/AnnotationReferenceExtensions.kt | 11 +- .../anvil/compiler/codegen/Contribution.kt | 11 +- .../codegen/ksp/KSAnnotationExtensions.kt | 9 +- .../com/squareup/anvil/compiler/TestUtils.kt | 37 +++ .../codegen/BindingModuleQualifierTest.kt | 292 +++++++++--------- .../ContributesMultibindingGeneratorTest.kt | 37 +++ .../compiler/testing/AnvilAnnotationsTest.kt | 56 ++++ 8 files changed, 300 insertions(+), 156 deletions(-) create mode 100644 compiler/src/test/java/com/squareup/anvil/compiler/testing/AnvilAnnotationsTest.kt diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/ContributedBinding.kt b/compiler/src/main/java/com/squareup/anvil/compiler/ContributedBinding.kt index e23fa60b1..739770580 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/ContributedBinding.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/ContributedBinding.kt @@ -14,6 +14,7 @@ import com.squareup.anvil.compiler.internal.reference.ClassReference import com.squareup.anvil.compiler.internal.reference.ClassReference.Descriptor import com.squareup.anvil.compiler.internal.reference.ClassReference.Psi import com.squareup.anvil.compiler.internal.requireFqName +import com.squareup.kotlinpoet.ksp.toTypeName import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.types.KotlinType @@ -223,7 +224,7 @@ internal fun KSClassDeclaration.checkNotGeneric( } internal fun KSAnnotation.qualifierKey(): String { - return shortName.asString() + + return annotationType.resolve().toTypeName().toString() + arguments.joinToString(separator = "") { argument -> val valueString = when (val value = argument.value) { is KSType -> value.resolveKSClassDeclaration()!!.qualifiedName!!.asString() diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/AnnotationReferenceExtensions.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/AnnotationReferenceExtensions.kt index 0fe283773..6ca64d2ed 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/AnnotationReferenceExtensions.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/AnnotationReferenceExtensions.kt @@ -15,6 +15,7 @@ import com.squareup.anvil.compiler.internal.reference.argumentAt import com.squareup.anvil.compiler.mergeComponentFqName import com.squareup.anvil.compiler.mergeModulesFqName import com.squareup.anvil.compiler.mergeSubcomponentFqName +import com.squareup.anvil.compiler.qualifierKey import org.jetbrains.kotlin.name.FqName internal fun AnnotationReference.parentScope(): ClassReference { @@ -135,7 +136,14 @@ internal fun List.checkNoDuplicateScopeAndBoundType if (size < 2) return if (size == 2 && this[0].scope() != this[1].scope()) return - val duplicateScopes = groupBy { it.scope() } + val clazz = this[0].declaringClass() + val qualifierKey = clazz.qualifierAnnotation()?.qualifierKey() + + val duplicateScopes = groupBy { annotation -> + // If there's a qualifier and this annotation isn't using `ignoreQualifier`, + // we need to include that qualifier in the duplicate check. + annotation.scope() to qualifierKey?.takeIf { !annotation.ignoreQualifier() } + } .filterValues { it.size > 1 } .ifEmpty { return } @@ -145,7 +153,6 @@ internal fun List.checkNoDuplicateScopeAndBoundType .ifEmpty { return } .keys - val clazz = this[0].declaringClass() throw AnvilCompilationExceptionClassReference( classReference = clazz, message = "${clazz.fqName} contributes multiple times to the same scope using the same " + diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/Contribution.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/Contribution.kt index 98d75977a..3493c3bab 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/Contribution.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/Contribution.kt @@ -41,6 +41,7 @@ internal sealed class Contribution { originType = origin, boundType = boundType, scopeType = scope, + qualifierKeyOrNull = qualifier?.key, suffix = bindingModuleNameSuffix, ) } @@ -204,15 +205,23 @@ internal sealed class Contribution { originType: ClassName, boundType: ClassName, scopeType: ClassName, + qualifierKeyOrNull: String?, suffix: String, ): ClassName { val types = listOf(originType, boundType, scopeType) + return ClassName( packageName = originType.packageName, simpleNames = types.map { it.simpleName } + suffix, ) .joinSimpleNamesAndTruncate( - hashParams = types + suffix, + hashParams = listOfNotNull( + originType, + boundType, + scopeType, + qualifierKeyOrNull, + suffix, + ), separator = "_", ) } diff --git a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt index 84b15a58b..ee4ed6d0c 100644 --- a/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt +++ b/compiler/src/main/java/com/squareup/anvil/compiler/codegen/ksp/KSAnnotationExtensions.kt @@ -12,6 +12,7 @@ import com.google.devtools.ksp.symbol.KSValueArgument import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.compiler.internal.mapKeyFqName import com.squareup.anvil.compiler.qualifierFqName +import com.squareup.anvil.compiler.qualifierKey import com.squareup.kotlinpoet.ksp.toClassName import org.jetbrains.kotlin.name.FqName @@ -53,7 +54,13 @@ internal fun List.checkNoDuplicateScopeAndBoundType( if (size < 2) return if (size == 2 && this[0].scope() != this[1].scope()) return - val duplicateScopes = groupBy { it.scope() } + val qualifierKey = annotatedType.qualifierAnnotation()?.qualifierKey() + + val duplicateScopes = groupBy { annotation -> + // If there's a qualifier and this annotation isn't using `ignoreQualifier`, + // we need to include that qualifier in the duplicate check. + annotation.scope() to qualifierKey?.takeIf { !annotation.ignoreQualifier() } + } .filterValues { it.size > 1 } .ifEmpty { return } diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/TestUtils.kt b/compiler/src/test/java/com/squareup/anvil/compiler/TestUtils.kt index b7c443b66..16eda5b05 100644 --- a/compiler/src/test/java/com/squareup/anvil/compiler/TestUtils.kt +++ b/compiler/src/test/java/com/squareup/anvil/compiler/TestUtils.kt @@ -34,6 +34,9 @@ import org.intellij.lang.annotations.Language import org.junit.Assume.assumeTrue import java.io.File import kotlin.reflect.KClass +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.full.hasAnnotation +import kotlin.reflect.full.memberProperties import kotlin.test.fail internal fun compile( @@ -194,6 +197,7 @@ private fun Class<*>.generatedBindingModules( originType = kotlin.asClassName(), boundType = boundType, scopeType = scope, + qualifierKeyOrNull = qualifierKey(bindingAnnotation), suffix = suffix, ) @@ -201,6 +205,39 @@ private fun Class<*>.generatedBindingModules( } } +private fun Class<*>.qualifierKey(bindingAnnotation: Annotation): String? { + + val ignoreQualifier = bindingAnnotation::class.memberProperties + .firstOrNull { it.name == "ignoreQualifier" } + ?.call(bindingAnnotation) + + if (ignoreQualifier == true) return null + + // For each annotation on the receiver class, check its class declaration + // to see if it has the `@Qualifier` annotation. + val qualifierAnnotation = annotations + .firstOrNull { it.annotationClass.hasAnnotation() } + // If there is no qualifier annotation, there's no key + ?: return null + + val qualifierFqName = qualifierAnnotation.annotationClass.qualifiedName!! + + val joinedArgs = qualifierAnnotation.annotationClass + .declaredMemberProperties + .joinToString("") { property -> + + val valueString = when (val argument = property.call(qualifierAnnotation)) { + is Enum<*> -> "${argument::class.qualifiedName}.${argument.name}" + is Class<*> -> argument.kotlin.qualifiedName + is KClass<*> -> argument.qualifiedName + else -> argument.toString() + } + property.name + valueString + } + + return qualifierFqName + joinedArgs +} + internal val Class<*>.bindingOriginKClass: KClass<*>? get() { return resolveOriginClass(ContributesBinding::class) diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/BindingModuleQualifierTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/BindingModuleQualifierTest.kt index bd278e437..26b6afab1 100644 --- a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/BindingModuleQualifierTest.kt +++ b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/BindingModuleQualifierTest.kt @@ -3,51 +3,30 @@ package com.squareup.anvil.compiler.codegen import com.google.common.truth.Truth.assertThat import com.squareup.anvil.annotations.MergeComponent import com.squareup.anvil.annotations.MergeSubcomponent -import com.squareup.anvil.annotations.compat.MergeModules +import com.squareup.anvil.annotations.compat.MergeInterfaces import com.squareup.anvil.compiler.anyQualifier -import com.squareup.anvil.compiler.compile import com.squareup.anvil.compiler.componentInterface import com.squareup.anvil.compiler.contributingInterface import com.squareup.anvil.compiler.generatedBindingModule import com.squareup.anvil.compiler.generatedMultiBindingModule import com.squareup.anvil.compiler.internal.testing.isAbstract -import com.squareup.anvil.compiler.isFullTestRun import com.squareup.anvil.compiler.mergedModules import com.squareup.anvil.compiler.parentInterface import com.squareup.anvil.compiler.subcomponentInterface +import com.squareup.anvil.compiler.testing.AnvilAnnotationsTest import dagger.Binds import dagger.Provides import dagger.multibindings.IntoSet -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.junit.runners.Parameterized.Parameters +import org.junit.jupiter.api.TestFactory import javax.inject.Named -import kotlin.reflect.KClass -@RunWith(Parameterized::class) -class BindingModuleQualifierTest( - private val annotationClass: KClass, +class BindingModuleQualifierTest : AnvilAnnotationsTest( + MergeComponent::class, + MergeSubcomponent::class, + MergeInterfaces::class, ) { - private val annotation = "@${annotationClass.simpleName}" - private val import = "import ${annotationClass.java.canonicalName}" - - companion object { - @Parameters(name = "{0}") - @JvmStatic - fun annotationClasses(): Collection { - return buildList { - add(MergeComponent::class) - if (isFullTestRun()) { - add(MergeSubcomponent::class) - add(MergeModules::class) - } - } - } - } - - @Test fun `the Dagger binding method has a qualifier without parameter`() { + @TestFactory fun `the Dagger binding method has a qualifier without parameter`() = testFactory { compile( """ package com.squareup.test @@ -82,42 +61,44 @@ class BindingModuleQualifierTest( } } - @Test fun `the Dagger multibinding method has a qualifier without parameter`() { - compile( - """ - package com.squareup.test - - import com.squareup.anvil.annotations.ContributesMultibinding - import javax.inject.Qualifier - $import - - @Qualifier - annotation class AnyQualifier + @TestFactory fun `the Dagger multibinding method has a qualifier without parameter`() = + testFactory { + compile( + """ + package com.squareup.test + + import com.squareup.anvil.annotations.ContributesMultibinding + import javax.inject.Qualifier + $import + + @Qualifier + annotation class AnyQualifier + + interface ParentInterface + + @ContributesMultibinding(Any::class) + @AnyQualifier + interface ContributingInterface : ParentInterface + + $annotation(Any::class) + interface ComponentInterface + """, + ) { + val bindingMethod = + contributingInterface.generatedMultiBindingModule.declaredMethods.single() - interface ParentInterface - - @ContributesMultibinding(Any::class) - @AnyQualifier - interface ContributingInterface : ParentInterface - - $annotation(Any::class) - interface ComponentInterface - """, - ) { - val bindingMethod = contributingInterface.generatedMultiBindingModule.declaredMethods.single() + with(bindingMethod) { + assertThat(returnType).isEqualTo(parentInterface) + assertThat(parameterTypes.toList()).containsExactly(contributingInterface) + assertThat(isAbstract).isTrue() - with(bindingMethod) { - assertThat(returnType).isEqualTo(parentInterface) - assertThat(parameterTypes.toList()).containsExactly(contributingInterface) - assertThat(isAbstract).isTrue() - - assertThat(annotations.map { it.annotationClass }) - .containsExactly(Binds::class, IntoSet::class, anyQualifier.kotlin) + assertThat(annotations.map { it.annotationClass }) + .containsExactly(Binds::class, IntoSet::class, anyQualifier.kotlin) + } } } - } - @Test fun `the Dagger provider method for an object has a qualifier`() { + @TestFactory fun `the Dagger provider method for an object has a qualifier`() = testFactory { compile( """ package com.squareup.test @@ -152,9 +133,10 @@ class BindingModuleQualifierTest( } } - @Test fun `the Dagger multibind provider method for an object has a qualifier`() { - compile( - """ + @TestFactory fun `the Dagger multibind provider method for an object has a qualifier`() = + testFactory { + compile( + """ package com.squareup.test import com.squareup.anvil.annotations.ContributesMultibinding @@ -173,21 +155,22 @@ class BindingModuleQualifierTest( $annotation(Any::class) interface ComponentInterface """, - ) { - val bindingMethod = contributingInterface.generatedMultiBindingModule.declaredMethods.single() + ) { + val bindingMethod = + contributingInterface.generatedMultiBindingModule.declaredMethods.single() - with(bindingMethod) { - assertThat(returnType).isEqualTo(parentInterface) - assertThat(parameterTypes.toList()).isEmpty() - assertThat(isAbstract).isFalse() + with(bindingMethod) { + assertThat(returnType).isEqualTo(parentInterface) + assertThat(parameterTypes.toList()).isEmpty() + assertThat(isAbstract).isFalse() - assertThat(annotations.map { it.annotationClass }) - .containsExactly(Provides::class, IntoSet::class, anyQualifier.kotlin) + assertThat(annotations.map { it.annotationClass }) + .containsExactly(Provides::class, IntoSet::class, anyQualifier.kotlin) + } } } - } - @Test fun `the Dagger binding method has a qualifier with string value`() { + @TestFactory fun `the Dagger binding method has a qualifier with string value`() = testFactory { compile( """ package com.squareup.test @@ -221,7 +204,7 @@ class BindingModuleQualifierTest( } } - @Test fun `the Dagger binding method has a qualifier with a class value`() { + @TestFactory fun `the Dagger binding method has a qualifier with a class value`() = testFactory { compile( """ package com.squareup.test @@ -263,7 +246,7 @@ class BindingModuleQualifierTest( } } - @Test fun `the Dagger binding method has a qualifier with a value`() { + @TestFactory fun `the Dagger binding method has a qualifier with a value`() = testFactory { compile( """ package com.squareup.test @@ -310,9 +293,10 @@ class BindingModuleQualifierTest( } } - @Test fun `the Dagger binding method has a qualifier with multiple arguments`() { - compile( - """ + @TestFactory fun `the Dagger binding method has a qualifier with multiple arguments`() = + testFactory { + compile( + """ package com.squareup.test import com.squareup.anvil.annotations.ContributesBinding @@ -335,27 +319,28 @@ class BindingModuleQualifierTest( $annotation(Any::class) interface ComponentInterface """, - ) { - val bindingMethod = contributingInterface.generatedBindingModule.declaredMethods.single() + ) { + val bindingMethod = contributingInterface.generatedBindingModule.declaredMethods.single() - with(bindingMethod) { - assertThat(returnType).isEqualTo(parentInterface) - assertThat(parameterTypes.toList()).containsExactly(contributingInterface) - assertThat(isAbstract).isTrue() + with(bindingMethod) { + assertThat(returnType).isEqualTo(parentInterface) + assertThat(parameterTypes.toList()).containsExactly(contributingInterface) + assertThat(isAbstract).isTrue() - assertThat(annotations.map { it.annotationClass }) - .containsExactly(Binds::class, anyQualifier.kotlin) + assertThat(annotations.map { it.annotationClass }) + .containsExactly(Binds::class, anyQualifier.kotlin) - val qualifierAnnotation = annotations.single { it.annotationClass == anyQualifier.kotlin } - assertThat(qualifierAnnotation.toString()) - .isEqualTo("@com.squareup.test.AnyQualifier(abc=java.lang.String.class, def=1)") + val qualifierAnnotation = annotations.single { it.annotationClass == anyQualifier.kotlin } + assertThat(qualifierAnnotation.toString()) + .isEqualTo("@com.squareup.test.AnyQualifier(abc=java.lang.String.class, def=1)") + } } } - } - @Test fun `the Dagger binding method has no other annotation that is not a qualifier`() { - compile( - """ + @TestFactory fun `the Dagger binding method has no other annotation that is not a qualifier`() = + testFactory { + compile( + """ package com.squareup.test import com.squareup.anvil.annotations.ContributesBinding @@ -372,20 +357,20 @@ class BindingModuleQualifierTest( $annotation(Any::class) interface ComponentInterface """, - ) { - val bindingMethod = contributingInterface.generatedBindingModule.declaredMethods.single() + ) { + val bindingMethod = contributingInterface.generatedBindingModule.declaredMethods.single() - with(bindingMethod) { - assertThat(returnType).isEqualTo(parentInterface) - assertThat(parameterTypes.toList()).containsExactly(contributingInterface) - assertThat(isAbstract).isTrue() + with(bindingMethod) { + assertThat(returnType).isEqualTo(parentInterface) + assertThat(parameterTypes.toList()).containsExactly(contributingInterface) + assertThat(isAbstract).isTrue() - assertThat(annotations.map { it.annotationClass }).containsExactly(Binds::class) + assertThat(annotations.map { it.annotationClass }).containsExactly(Binds::class) + } } } - } - @Test fun `the Dagger binding method has no qualifier when disabled`() { + @TestFactory fun `the Dagger binding method has no qualifier when disabled`() = testFactory { compile( """ package com.squareup.test @@ -415,7 +400,7 @@ class BindingModuleQualifierTest( } } - @Test fun `the Dagger multibinding method has no qualifier when disabled`() { + @TestFactory fun `the Dagger multibinding method has no qualifier when disabled`() = testFactory { compile( """ package com.squareup.test @@ -445,53 +430,58 @@ class BindingModuleQualifierTest( } } - @Test fun `the Dagger binding method has a qualifier for multiple contributions`() { - compile( - """ - package com.squareup.test - - import com.squareup.anvil.annotations.ContributesBinding - import javax.inject.Qualifier - $import - - @Qualifier - annotation class AnyQualifier - - interface ParentInterface - - @ContributesBinding(Any::class) - @ContributesBinding(Unit::class) - @AnyQualifier - interface ContributingInterface : ParentInterface - - $annotation(Any::class) - interface ComponentInterface - - $annotation(Unit::class) - interface SubcomponentInterface - """, - ) { - with( - componentInterface.mergedModules(annotationClass).single().java.declaredMethods.single(), - ) { - assertThat(returnType).isEqualTo(parentInterface) - assertThat(parameterTypes.toList()).containsExactly(contributingInterface) - assertThat(isAbstract).isTrue() - - assertThat(annotations.map { it.annotationClass }) - .containsExactly(Binds::class, anyQualifier.kotlin) - } - - with( - subcomponentInterface.mergedModules(annotationClass).single().java.declaredMethods.single(), - ) { - assertThat(returnType).isEqualTo(parentInterface) - assertThat(parameterTypes.toList()).containsExactly(contributingInterface) - assertThat(isAbstract).isTrue() - - assertThat(annotations.map { it.annotationClass }) - .containsExactly(Binds::class, anyQualifier.kotlin) + @TestFactory fun `the Dagger binding method has a qualifier for multiple contributions`() = + params + // MergeInterfaces doesn't make a component or subcomponent, so it's not applicable here. + .filterNot { it.a1 == MergeInterfaces::class } + .asTests { + compile( + """ + package com.squareup.test + import com.squareup.anvil.annotations.ContributesBinding + import javax.inject.Qualifier + $import + + @Qualifier + annotation class AnyQualifier + + interface ParentInterface + + @ContributesBinding(Any::class) + @ContributesBinding(Unit::class) + @AnyQualifier + interface ContributingInterface : ParentInterface + + $annotation(Any::class) + interface ComponentInterface + + $annotation(Unit::class) + interface SubcomponentInterface + """, + ) { + with( + componentInterface.mergedModules(annotationClass) + .single().java.declaredMethods.single(), + ) { + assertThat(returnType).isEqualTo(parentInterface) + assertThat(parameterTypes.toList()).containsExactly(contributingInterface) + assertThat(isAbstract).isTrue() + + assertThat(annotations.map { it.annotationClass }) + .containsExactly(Binds::class, anyQualifier.kotlin) + } + + with( + subcomponentInterface.mergedModules(annotationClass) + .single().java.declaredMethods.single(), + ) { + assertThat(returnType).isEqualTo(parentInterface) + assertThat(parameterTypes.toList()).containsExactly(contributingInterface) + assertThat(isAbstract).isTrue() + + assertThat(annotations.map { it.annotationClass }) + .containsExactly(Binds::class, anyQualifier.kotlin) + } + } } - } - } } diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesMultibindingGeneratorTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesMultibindingGeneratorTest.kt index 7a8877287..d0a70a3c3 100644 --- a/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesMultibindingGeneratorTest.kt +++ b/compiler/src/test/java/com/squareup/anvil/compiler/codegen/ContributesMultibindingGeneratorTest.kt @@ -442,6 +442,43 @@ class ContributesMultibindingGeneratorTest : AnvilCompilationModeTest( } } + @TestFactory fun `duplicate binding checks consider ignoreQualifier`() = testFactory { + compile( + """ + package com.squareup.test + + import com.squareup.anvil.annotations.ContributesMultibinding + import com.squareup.anvil.annotations.MergeComponent + import javax.inject.Inject + import javax.inject.Named + + interface ParentInterface + + @Named("test") + @ContributesMultibinding(Int::class, ParentInterface::class) + @ContributesMultibinding(Int::class, ParentInterface::class, ignoreQualifier = true) + class ContributingInterface @Inject constructor() : ParentInterface + + @MergeComponent(Int::class) + interface ComponentInterface { + fun injectClass(): InjectClass + } + + class InjectClass @Inject constructor( + @Named("test") val qualified : Set<@JvmSuppressWildcards ParentInterface>, + val unqualified : Set<@JvmSuppressWildcards ParentInterface> + ) + """, + mode = mode, + ) { + assertThat(contributingInterface.multibindingOriginClass?.java) + .isEqualTo(contributingInterface) + + assertThat(contributingInterface.multibindingModuleScopes) + .containsExactly(Int::class, Int::class) + } + } + @TestFactory fun `the bound type is not implied when explicitly defined`() = testFactory { compile( """ diff --git a/compiler/src/test/java/com/squareup/anvil/compiler/testing/AnvilAnnotationsTest.kt b/compiler/src/test/java/com/squareup/anvil/compiler/testing/AnvilAnnotationsTest.kt new file mode 100644 index 000000000..59398d61b --- /dev/null +++ b/compiler/src/test/java/com/squareup/anvil/compiler/testing/AnvilAnnotationsTest.kt @@ -0,0 +1,56 @@ +package com.squareup.anvil.compiler.testing + +import com.rickbusarow.kase.DefaultTestEnvironment +import com.rickbusarow.kase.Kase1 +import com.rickbusarow.kase.KaseTestFactory +import com.rickbusarow.kase.ParamTestEnvironmentFactory +import com.rickbusarow.kase.files.HasWorkingDir +import com.rickbusarow.kase.files.TestLocation +import com.rickbusarow.kase.kases +import com.squareup.anvil.compiler.isFullTestRun +import kotlin.reflect.KClass + +abstract class AnvilAnnotationsTest( + firstAnnotation: KClass, + vararg fullTestRunAnnotations: KClass, +) : KaseTestFactory< + Kase1>, + AnnotationTestEnvironment, + ParamTestEnvironmentFactory>, AnnotationTestEnvironment>, + > { + + override val params: List>> = kases( + buildList { + add(firstAnnotation) + if (isFullTestRun()) { + addAll(fullTestRunAnnotations) + } + }, + displayNameFactory = { "annotationClass: ${a1.simpleName!!}" }, + ) + + override val testEnvironmentFactory: + ParamTestEnvironmentFactory>, AnnotationTestEnvironment> = + AnnotationTestEnvironment +} + +class AnnotationTestEnvironment( + val annotationClass: KClass, + hasWorkingDir: HasWorkingDir, +) : DefaultTestEnvironment(hasWorkingDir), + CompilationEnvironment { + + val annotation = "@${annotationClass.simpleName}" + val import = "import ${annotationClass.java.canonicalName}" + + companion object : ParamTestEnvironmentFactory>, AnnotationTestEnvironment> { + override fun create( + params: Kase1>, + names: List, + location: TestLocation, + ): AnnotationTestEnvironment = AnnotationTestEnvironment( + params.a1, + HasWorkingDir(names, location), + ) + } +}