From 2a787b9ba4287dd1f1efd19b1139794ee8b530b6 Mon Sep 17 00:00:00 2001 From: Sam Judd Date: Thu, 21 Jul 2022 16:58:28 -0700 Subject: [PATCH] Add support for @Excludes in Glide's KSP processor. --- .../glide/annotation/ksp/AppGlideModules.kt | 92 +++++- .../annotation/ksp/GlideSymbolProcessor.kt | 26 +- .../glide/annotation/ksp/ModuleParser.kt | 32 +++ .../annotation/ksp/AppGlideModuleOnlyTests.kt | 62 ++-- .../annotation/ksp/LibraryGlideModuleTests.kt | 271 ++++++++++++++++++ 5 files changed, 421 insertions(+), 62 deletions(-) create mode 100644 annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/ModuleParser.kt diff --git a/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModules.kt b/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModules.kt index b417e55901..4f19ee36ab 100644 --- a/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModules.kt +++ b/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModules.kt @@ -9,6 +9,8 @@ import com.google.devtools.ksp.symbol.KSAnnotation import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSNode +import com.google.devtools.ksp.symbol.KSType import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec @@ -26,9 +28,17 @@ object AppGlideModuleConstants { const val INVALID_MODULE_MESSAGE = "Your AppGlideModule must have at least one constructor that has either no parameters or " + "accepts only a Context." + // This variable is visible only for testing + // TODO(b/174783094): Add @VisibleForTesting when internal is supported. + const val INVALID_EXCLUDES_ANNOTATION_MESSAGE = """ + @Excludes on %s is invalid. The value argument of your @Excludes annotation must be set to + either a single LibraryGlideModule class or a non-empty list of LibraryGlideModule classes. + Remove the annotation if you do not wish to exclude any LibraryGlideModules. Include each + LibraryGlideModule you do wish to exclude exactly once. Do not put types other than + LibraryGlideModules in the argument list""" private const val CONTEXT_NAME = "Context" - internal const val CONTEXT_PACKAGE = "android.content" + private const val CONTEXT_PACKAGE = "android.content" internal const val GLIDE_PACKAGE_NAME = "com.bumptech.glide" internal const val CONTEXT_QUALIFIED_NAME = "$CONTEXT_PACKAGE.$CONTEXT_NAME" internal const val GENERATED_ROOT_MODULE_PACKAGE_NAME = GLIDE_PACKAGE_NAME @@ -74,14 +84,84 @@ internal class AppGlideModuleParser( } private fun getExcludedGlideModuleClassNames(): Set { - val excludesAnnotation = appGlideModuleClass.atMostOneExcludesAnnotation() - // TODO(judds): Implement support for the excludes annotation. + val excludesAnnotation = appGlideModuleClass.atMostOneExcludesAnnotation() ?: return emptySet() environment.logger.logging( - "Found excludes annotation arguments: ${excludesAnnotation?.arguments}" + "Found excludes annotation arguments: ${excludesAnnotation.arguments}" ) - return emptySet() + return parseExcludesAnnotationArgumentsOrNull(excludesAnnotation) + ?: throw InvalidGlideSourceException( + AppGlideModuleConstants.INVALID_EXCLUDES_ANNOTATION_MESSAGE.format( + appGlideModuleClass.qualifiedName?.asString())) + } + + /** + * Given a list of arguments from an [com.bumptech.glide.annotation.Excludes] annotation, parses + * and returns a list of qualified names of the excluded + * [com.bumptech.glide.module.LibraryGlideModule] implementations, or returns null if the + * arguments are invalid. + * + * Ideally we'd throw more specific exceptions based on the type of failure. However, there are + * a bunch of individual failure types and they differ depending on whether the source was written + * in Java or Kotlin. Rather than trying to describe every failure in detail, we'll just return + * null and allow callers to describe the correct behavior. + */ + private fun parseExcludesAnnotationArgumentsOrNull( + excludesAnnotation: KSAnnotation + ): Set? { + val valueArguments: List? = excludesAnnotation.valueArgumentList() + // From the 'value' argument list, keep only those types that: + return valueArguments + // 1. extend LibraryGlideModules + ?.filter { it.extendsLibraryGlideModule() } + // 2. have valid qualified names + ?.mapNotNull { it.declaration.qualifiedName?.asString() } + // 3. are unique + ?.toSet() + // And then return the validated set only if the excludes annotation was non-empty every + // excluded module referenced by the annotation was valid. + ?.takeIf { it.isNotEmpty() && it.size == valueArguments.size } + } + + private fun KSType.extendsLibraryGlideModule(): Boolean = + ModuleParser.extractGlideModules(listOf(declaration)).libraryModules.size == 1 + + /** + * Parses the `value` argument as a list of the given type, or returns `null` if the annotation + * has multiple arguments or `value` has any entries that are not of the expected type `T`. + * + * `value` is the name of the default annotation parameter allowed by syntax like + * `@Excludes(argument)` or `@Excludes(argument1, argument2)` or + * `@Excludes({argument1, argument2})`, depending on the source type (Kotlin or Java). This method + * requires that the annotation has exactly one `value` argument of a given type and standardizes + * the differences KSP produces between Kotlin and Java source. + * + * To make this function more general purpose, we should assert that the values are of type T + * rather just returning null. For our current single use case, returning null matches the use + * case for the caller better than throwing. + */ + private inline fun KSAnnotation.valueArgumentList(): List? { + // Require that the annotation has a single value argument that points either to a single thing + // or a list of things (A or [A, B, C]). First validate that there's exactly one parameter and + // that it has the expected name. + // e.g. @Excludes(value = (A or [A, B, C])) -> (A or [A, B, C]) + val valueParameterValue: Any? = + arguments.singleOrNull() + .takeIf{ it?.name?.asString() == "value" } + ?.value + + // Next unify the types by verifying that it either has a single value of T, or a List of + // T and converting both to List + // (A or [A, B, C]) -> ([A] or [A, B, C]) with the correct types + return when(valueParameterValue) { + is T -> listOf(valueParameterValue) + is List<*> -> valueParameterValue.asListGivenTypeOfOrNull() + else -> null + } } + private inline fun List<*>.asListGivenTypeOfOrNull(): List? = + filterIsInstance(T::class.java).takeIf { it.size == size } + private fun parseAppGlideModuleConstructorOrThrow(): AppGlideModuleData.Constructor { val hasEmptyConstructors = appGlideModuleClass.getConstructors().any { it.parameters.isEmpty() } val hasContextParamOnlyConstructor = @@ -152,7 +232,7 @@ internal class AppGlideModuleParser( .toList() if (matchingAnnotations.size > 1) { throw InvalidGlideSourceException( - """Expected 0 or 1 $annotation annotations on ${this.qualifiedName}, but found: + """Expected 0 or 1 $annotation annotations on $qualifiedName, but found: ${matchingAnnotations.size}""" ) } diff --git a/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/GlideSymbolProcessor.kt b/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/GlideSymbolProcessor.kt index 454dfdaad4..46a421958b 100644 --- a/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/GlideSymbolProcessor.kt +++ b/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/GlideSymbolProcessor.kt @@ -46,7 +46,7 @@ class GlideSymbolProcessor(private val environment: SymbolProcessorEnvironment) ): List { environment.logger.logging("Found symbols, valid: $validSymbols, invalid: $invalidSymbols") - val (appGlideModules, libraryGlideModules) = extractGlideModules(validSymbols) + val (appGlideModules, libraryGlideModules) = ModuleParser.extractGlideModules(validSymbols) if (libraryGlideModules.size + appGlideModules.size != validSymbols.count()) { val invalidModules = @@ -136,28 +136,6 @@ class GlideSymbolProcessor(private val environment: SymbolProcessorEnvironment) environment.logger.logging("Wrote file: $file") } - - internal data class GlideModules( - val appModules: List, - val libraryModules: List, - ) - - private fun extractGlideModules(annotatedModules: List): GlideModules { - val appAndLibraryModuleNames = listOf(APP_MODULE_QUALIFIED_NAME, LIBRARY_MODULE_QUALIFIED_NAME) - val modulesBySuperType: Map> = - annotatedModules.filterIsInstance().groupBy { classDeclaration -> - appAndLibraryModuleNames.singleOrNull { classDeclaration.hasSuperType(it) } - } - - val (appModules, libraryModules) = - appAndLibraryModuleNames.map { modulesBySuperType[it] ?: emptyList() } - return GlideModules(appModules, libraryModules) - } - - private fun KSClassDeclaration.hasSuperType(superTypeQualifiedName: String) = - superTypes - .map { superType -> superType.resolve().declaration.qualifiedName!!.asString() } - .contains(superTypeQualifiedName) } // This class is visible only for testing @@ -175,5 +153,3 @@ object GlideSymbolProcessorConstants { internal class InvalidGlideSourceException(val userMessage: String) : Exception(userMessage) -private const val APP_MODULE_QUALIFIED_NAME = "com.bumptech.glide.module.AppGlideModule" -private const val LIBRARY_MODULE_QUALIFIED_NAME = "com.bumptech.glide.module.LibraryGlideModule" diff --git a/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/ModuleParser.kt b/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/ModuleParser.kt new file mode 100644 index 0000000000..deee71cc8f --- /dev/null +++ b/annotation/ksp/src/main/kotlin/com/bumptech/glide/annotation/ksp/ModuleParser.kt @@ -0,0 +1,32 @@ +package com.bumptech.glide.annotation.ksp + +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSNode + +object ModuleParser { + + internal data class GlideModules( + val appModules: List, + val libraryModules: List, + ) + + internal fun extractGlideModules(annotatedModules: List): GlideModules { + val appAndLibraryModuleNames = listOf(APP_MODULE_QUALIFIED_NAME, LIBRARY_MODULE_QUALIFIED_NAME) + val modulesBySuperType: Map> = + annotatedModules.filterIsInstance().groupBy { classDeclaration -> + appAndLibraryModuleNames.singleOrNull { classDeclaration.hasSuperType(it) } + } + + val (appModules, libraryModules) = + appAndLibraryModuleNames.map { modulesBySuperType[it] ?: emptyList() } + return GlideModules(appModules, libraryModules) + } + + private fun KSClassDeclaration.hasSuperType(superTypeQualifiedName: String) = + superTypes + .map { superType -> superType.resolve().declaration.qualifiedName!!.asString() } + .contains(superTypeQualifiedName) + + private const val APP_MODULE_QUALIFIED_NAME = "com.bumptech.glide.module.AppGlideModule" + private const val LIBRARY_MODULE_QUALIFIED_NAME = "com.bumptech.glide.module.LibraryGlideModule" +} \ No newline at end of file diff --git a/annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModuleOnlyTests.kt b/annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModuleOnlyTests.kt index 6d5d49995d..8f8519be22 100644 --- a/annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModuleOnlyTests.kt +++ b/annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/AppGlideModuleOnlyTests.kt @@ -52,22 +52,22 @@ class OnlyAppGlideModuleTests(override val sourceType: SourceType) : PerSourceTy fun compile_withGlideModuleOnValidAppGlideModule_generatedGeneratedAppGlideModule() { val kotlinModule = KotlinSourceFile( - "Module.kt", + "AppModule.kt", """ import com.bumptech.glide.annotation.GlideModule import com.bumptech.glide.module.AppGlideModule - @GlideModule class Module : AppGlideModule() + @GlideModule class AppModule : AppGlideModule() """ ) val javaModule = JavaSourceFile( - "Module.java", + "AppModule.java", """ import com.bumptech.glide.annotation.GlideModule; import com.bumptech.glide.module.AppGlideModule; - @GlideModule public class Module extends AppGlideModule {} + @GlideModule public class AppModule extends AppGlideModule {} """.trimIndent() ) @@ -81,26 +81,26 @@ class OnlyAppGlideModuleTests(override val sourceType: SourceType) : PerSourceTy fun compile_withAppGlideModuleConstructorAcceptingOnlyContext_generatesGeneratedAppGlideModule() { val kotlinModule = KotlinSourceFile( - "Module.kt", + "AppModule.kt", """ import android.content.Context import com.bumptech.glide.annotation.GlideModule import com.bumptech.glide.module.AppGlideModule - @GlideModule class Module(context: Context) : AppGlideModule() + @GlideModule class AppModule(context: Context) : AppGlideModule() """ ) val javaModule = JavaSourceFile( - "Module.java", + "AppModule.java", """ import android.content.Context; import com.bumptech.glide.annotation.GlideModule; import com.bumptech.glide.module.AppGlideModule; - @GlideModule public class Module extends AppGlideModule { - public Module(Context context) {} + @GlideModule public class AppModule extends AppGlideModule { + public AppModule(Context context) {} } """ ) @@ -115,23 +115,23 @@ class OnlyAppGlideModuleTests(override val sourceType: SourceType) : PerSourceTy fun compile_withAppGlideModuleConstructorRequiringOtherThanContext_fails() { val kotlinModule = KotlinSourceFile( - "Module.kt", + "AppModule.kt", """ import com.bumptech.glide.annotation.GlideModule import com.bumptech.glide.module.AppGlideModule - @GlideModule class Module(value: Int) : AppGlideModule() + @GlideModule class AppModule(value: Int) : AppGlideModule() """ ) val javaModule = JavaSourceFile( - "Module.java", + "AppModule.java", """ import com.bumptech.glide.annotation.GlideModule; import com.bumptech.glide.module.AppGlideModule; - @GlideModule public class Module extends AppGlideModule { - public Module(Integer value) {} + @GlideModule public class AppModule extends AppGlideModule { + public AppModule(Integer value) {} } """ ) @@ -146,25 +146,25 @@ class OnlyAppGlideModuleTests(override val sourceType: SourceType) : PerSourceTy fun compile_withAppGlideModuleConstructorRequiringMultipleArguments_fails() { val kotlinModule = KotlinSourceFile( - "Module.kt", + "AppModule.kt", """ import android.content.Context import com.bumptech.glide.annotation.GlideModule import com.bumptech.glide.module.AppGlideModule - @GlideModule class Module(value: Context, otherValue: Int) : AppGlideModule() + @GlideModule class AppModule(value: Context, otherValue: Int) : AppGlideModule() """ ) val javaModule = JavaSourceFile( - "Module.java", + "AppModule.java", """ import android.content.Context; import com.bumptech.glide.annotation.GlideModule; import com.bumptech.glide.module.AppGlideModule; - @GlideModule public class Module extends AppGlideModule { - public Module(Context value, int otherValue) {} + @GlideModule public class AppModule extends AppGlideModule { + public AppModule(Context value, int otherValue) {} } """ ) @@ -180,13 +180,13 @@ class OnlyAppGlideModuleTests(override val sourceType: SourceType) : PerSourceTy fun compile_withAppGlideModuleWithOneEmptyrConstructor_andOneContextOnlyConstructor_usesTheContextOnlyConstructor() { val kotlinModule = KotlinSourceFile( - "Module.kt", + "AppModule.kt", """ import android.content.Context import com.bumptech.glide.annotation.GlideModule import com.bumptech.glide.module.AppGlideModule - @GlideModule class Module(context: Context?) : AppGlideModule() { + @GlideModule class AppModule(context: Context?) : AppGlideModule() { constructor() : this(null) } @@ -194,16 +194,16 @@ class OnlyAppGlideModuleTests(override val sourceType: SourceType) : PerSourceTy ) val javaModule = JavaSourceFile( - "Module.java", + "AppModule.java", """ import android.content.Context; import com.bumptech.glide.annotation.GlideModule; import com.bumptech.glide.module.AppGlideModule; import javax.annotation.Nullable; - @GlideModule public class Module extends AppGlideModule { - public Module() {} - public Module(@Nullable Context context) {} + @GlideModule public class AppModule extends AppGlideModule { + public AppModule() {} + public AppModule(@Nullable Context context) {} } """ ) @@ -279,7 +279,7 @@ const val simpleAppGlideModule = """ package com.bumptech.glide -import Module +import AppModule import android.content.Context import kotlin.Boolean import kotlin.Suppress @@ -289,9 +289,9 @@ internal class GeneratedAppGlideModuleImpl( @Suppress("UNUSED_VARIABLE") context: Context, ) : GeneratedAppGlideModule() { - private val appGlideModule: Module + private val appGlideModule: AppModule init { - appGlideModule = Module() + appGlideModule = AppModule() } public override fun registerComponents( @@ -315,7 +315,7 @@ const val appGlideModuleWithContext = """ package com.bumptech.glide -import Module +import AppModule import android.content.Context import kotlin.Boolean import kotlin.Unit @@ -323,9 +323,9 @@ import kotlin.Unit internal class GeneratedAppGlideModuleImpl( context: Context, ) : GeneratedAppGlideModule() { - private val appGlideModule: Module + private val appGlideModule: AppModule init { - appGlideModule = Module(context) + appGlideModule = AppModule(context) } public override fun registerComponents( diff --git a/annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModuleTests.kt b/annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModuleTests.kt index 8cb5952733..328c60a960 100644 --- a/annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModuleTests.kt +++ b/annotation/ksp/test/src/test/kotlin/com/bumptech/glide/annotation/ksp/LibraryGlideModuleTests.kt @@ -1,7 +1,9 @@ package com.bumptech.glide.annotation.ksp.test +import com.bumptech.glide.annotation.ksp.AppGlideModuleConstants import com.bumptech.glide.annotation.ksp.GlideSymbolProcessorConstants import com.google.common.truth.Truth.assertThat +import com.tschuchort.compiletesting.KotlinCompilation import com.tschuchort.compiletesting.KotlinCompilation.ExitCode import java.io.FileNotFoundException import kotlin.test.assertFailsWith @@ -327,6 +329,275 @@ class LibraryGlideModuleTests(override val sourceType: SourceType) : PerSourceTy assertThat(it.exitCode).isEqualTo(ExitCode.OK) } } + + @Test + fun compile_withLibraryModuleInExcludes_producesGeneratedAppGlideModuleThatDoesNotCallExcludedLibraryModule() { + val kotlinLibraryModule1 = + KotlinSourceFile( + "LibraryModule.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.LibraryGlideModule + + @GlideModule class LibraryModule : LibraryGlideModule() + """ + ) + val kotlinLibraryModule2 = + KotlinSourceFile( + "ExcludedLibraryModule.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.LibraryGlideModule + + @GlideModule class ExcludedLibraryModule : LibraryGlideModule() + """ + ) + val kotlinAppModule = + KotlinSourceFile( + "AppModule.kt", + """ + import com.bumptech.glide.annotation.Excludes + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.AppGlideModule + + @GlideModule + @Excludes(ExcludedLibraryModule::class) + class AppModule : AppGlideModule() + """ + ) + + val javaLibraryModule1 = + JavaSourceFile( + "LibraryModule.java", + """ + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.LibraryGlideModule; + + @GlideModule + public class LibraryModule extends LibraryGlideModule {} + """ + ) + val javaLibraryModule2 = + JavaSourceFile( + "ExcludedLibraryModule.java", + """ + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.LibraryGlideModule; + + @GlideModule + public class ExcludedLibraryModule extends LibraryGlideModule {} + """ + ) + val javaAppModule = + JavaSourceFile( + "AppModule.java", + """ + import com.bumptech.glide.annotation.Excludes; + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.AppGlideModule; + + @GlideModule + @Excludes(ExcludedLibraryModule.class) + public class AppModule extends AppGlideModule { + public AppModule() {} + } + """ + ) + compileCurrentSourceType( + kotlinAppModule, + kotlinLibraryModule1, + kotlinLibraryModule2, + javaAppModule, + javaLibraryModule1, + javaLibraryModule2, + ) { + assertThat(it.generatedAppGlideModuleContents()) + .hasSourceEqualTo(appGlideModuleWithLibraryModule) + assertThat(it.exitCode).isEqualTo(ExitCode.OK) + } + } + + @Test + fun compile_withMultipleLibraryModulesInExcludes_producesGeneratedAppGlideModuleThatDoesNotCallExcludedLibraryModules() { + val kotlinLibraryModule1 = + KotlinSourceFile( + "LibraryModule.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.LibraryGlideModule + + @GlideModule class LibraryModule : LibraryGlideModule() + """ + ) + val kotlinLibraryModule2 = + KotlinSourceFile( + "ExcludedLibraryModule.kt", + """ + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.LibraryGlideModule + + @GlideModule class ExcludedLibraryModule : LibraryGlideModule() + """ + ) + val kotlinAppModule = + KotlinSourceFile( + "AppModule.kt", + """ + import com.bumptech.glide.annotation.Excludes + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.AppGlideModule + + @GlideModule + @Excludes(LibraryModule::class, ExcludedLibraryModule::class) + class AppModule : AppGlideModule() + """ + ) + + val javaLibraryModule1 = + JavaSourceFile( + "LibraryModule.java", + """ + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.LibraryGlideModule; + + @GlideModule + public class LibraryModule extends LibraryGlideModule {} + """ + ) + val javaLibraryModule2 = + JavaSourceFile( + "ExcludedLibraryModule.java", + """ + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.LibraryGlideModule; + + @GlideModule + public class ExcludedLibraryModule extends LibraryGlideModule {} + """ + ) + val javaAppModule = + JavaSourceFile( + "AppModule.java", + """ + import com.bumptech.glide.annotation.Excludes; + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.AppGlideModule; + + @GlideModule + @Excludes({LibraryModule.class, ExcludedLibraryModule.class}) + public class AppModule extends AppGlideModule { + public AppModule() {} + } + """ + ) + compileCurrentSourceType( + kotlinAppModule, + kotlinLibraryModule1, + kotlinLibraryModule2, + javaAppModule, + javaLibraryModule1, + javaLibraryModule2, + ) { + assertThat(it.generatedAppGlideModuleContents()) + .hasSourceEqualTo(simpleAppGlideModule) + assertThat(it.exitCode).isEqualTo(ExitCode.OK) + } + } + + @Test + fun compile_withAppModuleWithEmptyExcludes_fails() { + val kotlinAppModule = + KotlinSourceFile( + "AppModule.kt", + """ + import com.bumptech.glide.annotation.Excludes + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.AppGlideModule + + @GlideModule + @Excludes + class AppModule : AppGlideModule() + """ + ) + val javaAppModule = + JavaSourceFile( + "AppModule.java", + """ + import com.bumptech.glide.annotation.Excludes; + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.AppGlideModule; + + @GlideModule + @Excludes + public class AppModule extends AppGlideModule { + public AppModule() {} + } + """ + ) + compileCurrentSourceType( + kotlinAppModule, + javaAppModule, + ) { + assertThat(it.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR) + assertThat(it.messages) + .contains(AppGlideModuleConstants.INVALID_EXCLUDES_ANNOTATION_MESSAGE.format("AppModule")) + } + } + + @Test + fun compile_withAppModuleWithExcludes_pointingToAppModules_fails() { + val kotlinAppModule = + KotlinSourceFile( + "AppModule.kt", + """ + import com.bumptech.glide.annotation.Excludes + import com.bumptech.glide.annotation.GlideModule + import com.bumptech.glide.module.AppGlideModule + + class SomeOtherAppModule: AppGlideModule() + + @GlideModule + @Excludes(SomeOtherAppModule::class) + class AppModule : AppGlideModule() + """ + ) + val otherJavaAppModule = + JavaSourceFile( + "SomeOtherAppModule.java", + """ + import com.bumptech.glide.module.AppGlideModule; + + public class SomeOtherAppModule extends AppGlideModule { + public SomeOtherAppModule() {} + } + """ + + ) + val javaAppModule = + JavaSourceFile( + "AppModule.java", + """ + import com.bumptech.glide.annotation.Excludes; + import com.bumptech.glide.annotation.GlideModule; + import com.bumptech.glide.module.AppGlideModule; + + @GlideModule + @Excludes(SomeOtherAppModule.class) + public class AppModule extends AppGlideModule { + public AppModule() {} + } + """ + ) + compileCurrentSourceType( + kotlinAppModule, + otherJavaAppModule, + javaAppModule, + ) { + assertThat(it.exitCode).isEqualTo(ExitCode.COMPILATION_ERROR) + assertThat(it.messages) + .contains(AppGlideModuleConstants.INVALID_EXCLUDES_ANNOTATION_MESSAGE.format("AppModule")) + } + } } @Language("kotlin")