├── testing ├── integration │ ├── src │ │ ├── main │ │ │ └── kotlin │ │ │ │ └── TestClass.kt │ │ └── test │ │ │ └── kotlin │ │ │ └── me │ │ │ └── shika │ │ │ └── di │ │ │ └── factory │ │ │ ├── Dependency.kt │ │ │ ├── InstantiableConcreteModule.kt │ │ │ ├── UninstantiableConcreteModule.kt │ │ │ ├── AbstractModule.kt │ │ │ ├── ConcreteModuleThatCouldBeAbstract.kt │ │ │ ├── FactoryDependenciesTest.kt │ │ │ ├── FactoryRequiredModulesTest.kt │ │ │ ├── FactoryMixedParametersTest.kt │ │ │ ├── FactoryBindsInstanceTest.kt │ │ │ ├── SubcomponentFactoryTest.kt │ │ │ └── FactoryImplicitModulesTest.kt │ └── build.gradle └── dagger-google │ └── build.gradle ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── embedded.gradle ├── .gitmodules ├── common ├── build-info │ ├── build.gradle │ └── src │ │ └── main │ │ └── kotlin │ │ └── me │ │ └── shika │ │ └── BuildInfo.kt ├── resolver │ ├── src │ │ └── main │ │ │ └── kotlin │ │ │ └── me │ │ │ └── shika │ │ │ └── di │ │ │ └── dagger │ │ │ └── resolver │ │ │ ├── bindings │ │ │ ├── BindingsResolver.kt │ │ │ ├── CreatorInstanceBindingResolver.kt │ │ │ ├── InjectConstructorBindingResolver.kt │ │ │ ├── ProviderOrLazyBindingResolver.kt │ │ │ ├── DependencyBindingResolver.kt │ │ │ └── ModuleBindingResolver.kt │ │ │ ├── endpoints │ │ │ ├── EndpointResolver.kt │ │ │ ├── ProvisionEndpointResolver.kt │ │ │ └── InjectionEndpointResolver.kt │ │ │ ├── creator │ │ │ ├── DefaultBuilderDescriptor.kt │ │ │ ├── CreatorDescriptor.kt │ │ │ ├── FactoryDescriptor.kt │ │ │ └── BuilderDescriptor.kt │ │ │ ├── ResolverContext.kt │ │ │ ├── ComponentAnnotationDescriptor.kt │ │ │ ├── utils.kt │ │ │ └── ComponentDescriptor.kt │ └── build.gradle ├── gradle-base │ ├── build.gradle │ └── src │ │ └── main │ │ └── kotlin │ │ └── me │ │ └── shika │ │ └── di │ │ ├── DiCompilerPlugin.kt │ │ └── AbstractDiCompilerSubplugin.kt └── kotlin-base │ ├── build.gradle │ └── src │ └── main │ └── kotlin │ └── me │ └── shika │ └── di │ └── model │ └── resolution.kt ├── codegen ├── gradle-plugin │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ └── org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin │ │ │ └── kotlin │ │ │ └── me │ │ │ └── shika │ │ │ └── di │ │ │ └── DiCompilerSubplugin.kt │ └── build.gradle └── kotlin-plugin │ ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ ├── org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor │ │ │ └── org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar │ │ └── kotlin │ │ └── me │ │ └── shika │ │ └── di │ │ ├── dagger │ │ ├── resolver │ │ │ ├── bindings │ │ │ │ ├── BindingsResolver.kt │ │ │ │ ├── CreatorInstanceBindingResolver.kt │ │ │ │ ├── InjectConstructorBindingResolver.kt │ │ │ │ ├── ProviderOrLazyBindingResolver.kt │ │ │ │ ├── DependencyBindingResolver.kt │ │ │ │ └── ModuleBindingResolver.kt │ │ │ ├── endpoints │ │ │ │ ├── EndpointResolver.kt │ │ │ │ ├── ProvisionEndpointResolver.kt │ │ │ │ └── InjectionEndpointResolver.kt │ │ │ ├── creator │ │ │ │ ├── DefaultBuilderDescriptor.kt │ │ │ │ ├── CreatorDescriptor.kt │ │ │ │ ├── FactoryDescriptor.kt │ │ │ │ └── BuilderDescriptor.kt │ │ │ ├── ResolverContext.kt │ │ │ ├── ComponentAnnotationDescriptor.kt │ │ │ ├── utils.kt │ │ │ └── ComponentDescriptor.kt │ │ └── renderer │ │ │ ├── provider │ │ │ ├── EqualityBindingRenderer.kt │ │ │ ├── BindingRenderer.kt │ │ │ ├── BoundInstanceBindingRenderer.kt │ │ │ ├── ComponentBindingRenderer.kt │ │ │ ├── RecursiveBindingRenderer.kt │ │ │ ├── ConstructorBindingRenderer.kt │ │ │ ├── ProviderBindingRenderer.kt │ │ │ ├── StaticFunctionRenderer.kt │ │ │ ├── InstancePropertyRenderer.kt │ │ │ ├── InstanceFunctionRenderer.kt │ │ │ ├── LazyBindingRenderer.kt │ │ │ └── utils.kt │ │ │ ├── dsl │ │ │ ├── PropSpecDsl.kt │ │ │ ├── TypeSpecDsl.kt │ │ │ └── FunSpecDsl.kt │ │ │ ├── creator │ │ │ ├── DefaultBuilderRenderer.kt │ │ │ ├── FactoryRenderer.kt │ │ │ └── BuilderRenderer.kt │ │ │ ├── utils.kt │ │ │ ├── MembersInjectorRenderer.kt │ │ │ ├── GraphRenderer.kt │ │ │ └── ComponentRenderer.kt │ │ ├── DiCompilerComponentRegistrar.kt │ │ ├── DiCommandLineProcessor.kt │ │ ├── model │ │ └── resolution.kt │ │ └── DiCompilerAnalysisExtension.kt │ └── build.gradle ├── codegen-reflect ├── kotlin-plugin │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── META-INF │ │ │ │ └── services │ │ │ │ ├── org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor │ │ │ │ └── org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar │ │ │ └── java │ │ │ └── me │ │ │ └── shika │ │ │ └── di │ │ │ ├── dagger │ │ │ └── renderer │ │ │ │ ├── dsl │ │ │ │ ├── PropSpecDsl.kt │ │ │ │ ├── TypeSpecDsl.kt │ │ │ │ └── FunSpecDsl.kt │ │ │ │ ├── creator │ │ │ │ ├── DefaultBuilderRenderer.kt │ │ │ │ ├── FactoryRenderer.kt │ │ │ │ └── BuilderRenderer.kt │ │ │ │ ├── utils.kt │ │ │ │ └── ComponentRenderer.kt │ │ │ ├── DiCompilerComponentRegistrar.kt │ │ │ └── DiCommandLineProcessor.kt │ └── build.gradle └── gradle-plugin │ ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ └── org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin │ │ └── kotlin │ │ └── me │ │ └── shika │ │ └── di │ │ └── DiReflectCompilerSubplugin.kt │ └── build.gradle ├── gradle.properties ├── settings.gradle ├── .gitignore ├── README.md ├── gradlew.bat └── gradlew /testing/integration/src/main/kotlin/TestClass.kt: -------------------------------------------------------------------------------- 1 | class TestClass 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShikaSD/kotlin-compiler-dagger-plugin/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "testing/dagger-google/dagger"] 2 | path = testing/dagger-google/dagger 3 | url = https://github.com/google/dagger.git 4 | -------------------------------------------------------------------------------- /common/build-info/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "org.jetbrains.kotlin.jvm" 2 | 3 | dependencies { 4 | implementation deps.kotlin.stdlib 5 | } 6 | -------------------------------------------------------------------------------- /codegen/gradle-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin: -------------------------------------------------------------------------------- 1 | me.shika.di.DiCompilerSubplugin 2 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor: -------------------------------------------------------------------------------- 1 | me.shika.di.DiCommandLineProcessor 2 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor: -------------------------------------------------------------------------------- 1 | me.shika.di.DiCommandLineProcessor 2 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar: -------------------------------------------------------------------------------- 1 | me.shika.di.DiCompilerComponentRegistrar 2 | -------------------------------------------------------------------------------- /codegen-reflect/gradle-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin: -------------------------------------------------------------------------------- 1 | me.shika.di.DiReflectCompilerSubplugin 2 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar: -------------------------------------------------------------------------------- 1 | me.shika.di.DiCompilerComponentRegistrar 2 | -------------------------------------------------------------------------------- /common/build-info/src/main/kotlin/me/shika/BuildInfo.kt: -------------------------------------------------------------------------------- 1 | package me.shika 2 | 3 | object BuildInfo { 4 | const val VERSION = "0.0.4-preview" // TODO fine for now, but should be generated 5 | } 6 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/bindings/BindingsResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.bindings 2 | 3 | import me.shika.di.model.Binding 4 | 5 | typealias BindingResolver = () -> List 6 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/bindings/BindingsResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.bindings 2 | 3 | import me.shika.di.model.Binding 4 | 5 | typealias BindingResolver = () -> List 6 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/endpoints/EndpointResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.endpoints 2 | 3 | import me.shika.di.model.Endpoint 4 | 5 | typealias EndpointResolver = () -> List 6 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/endpoints/EndpointResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.endpoints 2 | 3 | import me.shika.di.model.Endpoint 4 | 5 | typealias EndpointResolver = () -> List 6 | -------------------------------------------------------------------------------- /common/gradle-base/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "org.jetbrains.kotlin.jvm" 2 | 3 | group "me.shika.di" 4 | 5 | dependencies { 6 | implementation gradleApi() 7 | implementation deps.kotlin.stdlib 8 | implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api" 9 | } 10 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.incremental=true 2 | 3 | org.gradle.daemon=false 4 | org.gradle.parallel=false 5 | org.gradle.configureondemand=false 6 | kotlin.compiler.execution.strategy=in-process 7 | kotlin.daemon.debug.log=true 8 | org.gradle.jvmargs=-Xmx6g -XX:MaxPermSize=1024m 9 | 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Aug 03 01:40:21 BST 2019 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/creator/DefaultBuilderDescriptor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.creator 2 | 3 | import me.shika.di.model.Binding 4 | 5 | class DefaultBuilderDescriptor() : CreatorDescriptor { 6 | override val instances: List = emptyList() 7 | } 8 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/creator/DefaultBuilderDescriptor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.creator 2 | 3 | import me.shika.di.model.Binding 4 | 5 | class DefaultBuilderDescriptor() : CreatorDescriptor { 6 | override val instances: List = emptyList() 7 | } 8 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/ResolverContext.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver 2 | 3 | import org.jetbrains.kotlin.descriptors.ModuleDescriptor 4 | import org.jetbrains.kotlin.resolve.BindingTrace 5 | 6 | class ResolverContext( 7 | val module: ModuleDescriptor, 8 | val trace: BindingTrace 9 | ) 10 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/ResolverContext.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver 2 | 3 | import org.jetbrains.kotlin.descriptors.ModuleDescriptor 4 | import org.jetbrains.kotlin.resolve.BindingTrace 5 | 6 | class ResolverContext( 7 | val module: ModuleDescriptor, 8 | // val resolveSession: ResolveSession, 9 | val trace: BindingTrace 10 | ) 11 | -------------------------------------------------------------------------------- /common/kotlin-base/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "org.jetbrains.kotlin.jvm" 2 | 3 | group "me.shika.di" 4 | 5 | dependencies { 6 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 7 | implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api" 8 | compileOnly "org.jetbrains.kotlin:kotlin-compiler-embeddable" 9 | } 10 | 11 | compileKotlin { 12 | kotlinOptions { 13 | jvmTarget = '1.8' 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'dagger-kotlin-compiler-plugin' 2 | 3 | include ':common:build-info' 4 | include ':common:gradle-base' 5 | include ':common:kotlin-base' 6 | include ':common:resolver' 7 | 8 | include ':codegen:gradle-plugin' 9 | include ':codegen:kotlin-plugin' 10 | include ':codegen-reflect:gradle-plugin' 11 | include ':codegen-reflect:kotlin-plugin' 12 | 13 | include ':testing:dagger-google' 14 | include ':testing:integration' 15 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/bindings/CreatorInstanceBindingResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.bindings 2 | 3 | import me.shika.di.dagger.resolver.creator.CreatorDescriptor 4 | import me.shika.di.model.Binding 5 | 6 | class CreatorInstanceBindingResolver( 7 | private val creatorDescriptor: CreatorDescriptor 8 | ): BindingResolver { 9 | override fun invoke(): List = 10 | creatorDescriptor.instances 11 | } 12 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/bindings/CreatorInstanceBindingResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.bindings 2 | 3 | import me.shika.di.dagger.resolver.creator.CreatorDescriptor 4 | import me.shika.di.model.Binding 5 | 6 | class CreatorInstanceBindingResolver( 7 | private val creatorDescriptor: CreatorDescriptor 8 | ): BindingResolver { 9 | override fun invoke(): List = 10 | creatorDescriptor.instances 11 | } 12 | -------------------------------------------------------------------------------- /common/resolver/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "org.jetbrains.kotlin.jvm" 2 | 3 | group "me.shika.di" 4 | 5 | dependencies { 6 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 7 | implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api" 8 | compileOnly "org.jetbrains.kotlin:kotlin-compiler-embeddable" 9 | 10 | implementation project(':common:kotlin-base') 11 | } 12 | 13 | compileKotlin { 14 | kotlinOptions { 15 | jvmTarget = '1.8' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /common/gradle-base/src/main/kotlin/me/shika/di/DiCompilerPlugin.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di 2 | 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | 6 | class DiCompilerPlugin : Plugin { 7 | override fun apply(project: Project) { 8 | project.extensions.create( 9 | "daggerCompiler", 10 | DiCompilerExtension::class.java 11 | ) 12 | } 13 | } 14 | 15 | open class DiCompilerExtension { 16 | var enabled: Boolean = true 17 | } 18 | -------------------------------------------------------------------------------- /codegen/gradle-plugin/src/main/kotlin/me/shika/di/DiCompilerSubplugin.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di 2 | 3 | import me.shika.BuildInfo 4 | import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact 5 | 6 | class DiCompilerSubplugin : AbstractDiCompilerSubplugin() { 7 | override fun getPluginArtifact(): SubpluginArtifact = 8 | SubpluginArtifact( 9 | groupId = "me.shika.di", 10 | artifactId = "dagger-compiler-plugin", 11 | version = BuildInfo.VERSION 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/provider/EqualityBindingRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.provider 2 | 3 | import com.squareup.kotlinpoet.TypeSpec 4 | import me.shika.di.model.Binding 5 | import me.shika.di.model.Binding.Variation.Equality 6 | 7 | class EqualityBindingRenderer( 8 | private val deps: List 9 | ) : BindingRenderer { 10 | override fun TypeSpec.Builder.render(binding: Binding, variation: Equality): ProviderSpec = 11 | deps.first() 12 | } 13 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "org.jetbrains.kotlin.jvm" 2 | 3 | group "me.shika.di" 4 | 5 | configurations { 6 | published 7 | implementation.extendsFrom(published) 8 | } 9 | 10 | dependencies { 11 | implementation "org.jetbrains.kotlin:kotlin-stdlib" 12 | implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api" 13 | published "org.jetbrains.kotlin:kotlin-gradle-plugin-model" 14 | published 'com.squareup:kotlinpoet:1.3.0' 15 | 16 | compileOnly "org.jetbrains.kotlin:kotlin-compiler-embeddable" 17 | } 18 | -------------------------------------------------------------------------------- /codegen-reflect/gradle-plugin/src/main/kotlin/me/shika/di/DiReflectCompilerSubplugin.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di 2 | 3 | import me.shika.BuildInfo 4 | import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact 5 | 6 | // @AutoService(KotlinGradleSubplugin::class) 7 | class DiReflectCompilerSubplugin: AbstractDiCompilerSubplugin() { 8 | override fun getPluginArtifact(): SubpluginArtifact = 9 | SubpluginArtifact( 10 | groupId = "me.shika.di", 11 | artifactId = "dagger-compiler-plugin", 12 | version = BuildInfo.VERSION + "-r3" 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/dsl/PropSpecDsl.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.dsl 2 | 3 | import com.squareup.kotlinpoet.KModifier 4 | import com.squareup.kotlinpoet.PropertySpec 5 | import com.squareup.kotlinpoet.TypeName 6 | import com.squareup.kotlinpoet.TypeSpec 7 | 8 | fun TypeSpec.Builder.property(name: String, type: TypeName, block: PropertySpec.Builder.() -> Unit) = 9 | PropertySpec.builder(name, type).apply(block).build().also { 10 | addProperty(it) 11 | } 12 | 13 | fun PropertySpec.Builder.markPrivate() = addModifiers(KModifier.PRIVATE) 14 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/creator/CreatorDescriptor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.creator 2 | 3 | import me.shika.di.dagger.resolver.qualifiers 4 | import me.shika.di.model.Binding 5 | import me.shika.di.model.Key 6 | import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor 7 | 8 | interface CreatorDescriptor { 9 | val instances: List 10 | } 11 | 12 | fun ValueParameterDescriptor.toInstanceBinding() = 13 | Binding( 14 | Key(type, qualifiers()), 15 | emptyList(), 16 | Binding.Variation.BoundInstance(this) 17 | ) 18 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/src/main/java/me/shika/di/dagger/renderer/dsl/PropSpecDsl.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.dsl 2 | 3 | import com.squareup.kotlinpoet.KModifier 4 | import com.squareup.kotlinpoet.PropertySpec 5 | import com.squareup.kotlinpoet.TypeName 6 | import com.squareup.kotlinpoet.TypeSpec 7 | 8 | fun TypeSpec.Builder.property(name: String, type: TypeName, block: PropertySpec.Builder.() -> Unit) = 9 | PropertySpec.builder(name, type).apply(block).build().also { 10 | addProperty(it) 11 | } 12 | 13 | fun PropertySpec.Builder.markPrivate() = addModifiers(KModifier.PRIVATE) 14 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/creator/CreatorDescriptor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.creator 2 | 3 | import me.shika.di.dagger.resolver.qualifiers 4 | import me.shika.di.model.Binding 5 | import me.shika.di.model.Key 6 | import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor 7 | 8 | interface CreatorDescriptor { 9 | val instances: List 10 | } 11 | 12 | fun ValueParameterDescriptor.toInstanceBinding() = 13 | Binding( 14 | Key(type, qualifiers()), 15 | emptyList(), 16 | Binding.Variation.BoundInstance(this) 17 | ) 18 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/dsl/TypeSpecDsl.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.dsl 2 | 3 | import com.squareup.kotlinpoet.KModifier 4 | import com.squareup.kotlinpoet.TypeSpec 5 | 6 | fun TypeSpec.Builder.nestedClass(name: String, block: TypeSpec.Builder.() -> Unit) = addType( 7 | TypeSpec.classBuilder(name).apply(block).build() 8 | ) 9 | 10 | fun TypeSpec.Builder.companionObject(name: String? = null, block: TypeSpec.Builder.() -> Unit) = addType( 11 | TypeSpec.companionObjectBuilder(name).apply(block).build() 12 | ) 13 | 14 | fun TypeSpec.Builder.markPrivate() = addModifiers(KModifier.PRIVATE) 15 | 16 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/provider/BindingRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.provider 2 | 3 | import com.squareup.kotlinpoet.PropertySpec 4 | import com.squareup.kotlinpoet.TypeSpec 5 | import me.shika.di.model.Binding 6 | 7 | interface BindingRenderer { 8 | fun TypeSpec.Builder.render(binding: Binding, variation: Variation): ProviderSpec 9 | } 10 | 11 | data class ProviderSpec( 12 | val property: PropertySpec, 13 | val type: ProviderType 14 | ) { 15 | sealed class ProviderType { 16 | object Provider : ProviderType() 17 | object Value : ProviderType() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "org.jetbrains.kotlin.jvm" 2 | apply from: rootProject.file('gradle/embedded.gradle') 3 | 4 | group "me.shika.di" 5 | 6 | dependencies { 7 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 8 | implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api" 9 | compileOnly "org.jetbrains.kotlin:kotlin-compiler-embeddable" 10 | 11 | published "org.jetbrains.kotlin:kotlin-gradle-plugin-model" 12 | published deps.kotlinpoet 13 | embedded project(':common:kotlin-base') 14 | embedded project(':common:resolver') 15 | } 16 | 17 | compileKotlin { 18 | kotlinOptions { 19 | jvmTarget = '1.8' 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /testing/integration/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "kotlin" 3 | } 4 | 5 | apply plugin: "org.jetbrains.kotlin.jvm" 6 | 7 | configurations { 8 | testCompilerImplementation 9 | } 10 | 11 | dependencies { 12 | implementation deps.dagger.runtime 13 | implementation deps.kotlin.stdlib 14 | implementation deps.junit 15 | implementation deps.truth 16 | 17 | kotlinCompilerPluginClasspath project(':codegen-reflect:kotlin-plugin') 18 | } 19 | 20 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { 21 | def sourcesDir = new File(buildDir, "kotlin/${name}/generated") 22 | 23 | kotlinOptions { 24 | freeCompilerArgs = [ 25 | "-P", 26 | "plugin:dagger-compiler-plugin:sources=${sourcesDir.getAbsolutePath()}" 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/src/main/java/me/shika/di/dagger/renderer/dsl/TypeSpecDsl.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.dsl 2 | 3 | import com.squareup.kotlinpoet.KModifier 4 | import com.squareup.kotlinpoet.TypeSpec 5 | 6 | fun TypeSpec.Builder.nestedClass(name: String, block: TypeSpec.Builder.() -> Unit) = addType( 7 | TypeSpec.classBuilder(name).apply(block).build() 8 | ) 9 | 10 | fun TypeSpec.Builder.nestedInterface(name: String, block: TypeSpec.Builder.() -> Unit) = addType( 11 | TypeSpec.interfaceBuilder(name).apply(block).build() 12 | ) 13 | 14 | fun TypeSpec.Builder.companionObject(name: String? = null, block: TypeSpec.Builder.() -> Unit) = addType( 15 | TypeSpec.companionObjectBuilder(name).apply(block).build() 16 | ) 17 | 18 | fun TypeSpec.Builder.markPrivate() = addModifiers(KModifier.PRIVATE) 19 | 20 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/provider/BoundInstanceBindingRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.provider 2 | 3 | import com.squareup.kotlinpoet.TypeSpec 4 | import me.shika.di.dagger.renderer.provider.ProviderSpec.ProviderType.Value 5 | import me.shika.di.dagger.renderer.typeName 6 | import me.shika.di.model.Binding 7 | import me.shika.di.model.Binding.Variation.BoundInstance 8 | 9 | class BoundInstanceBindingRenderer() : BindingRenderer { 10 | override fun TypeSpec.Builder.render(binding: Binding, variation: BoundInstance): ProviderSpec { 11 | val instanceType = binding.key.type 12 | val returnType = instanceType.typeName()!! 13 | 14 | val property = propertySpecs.first { it.type == returnType } 15 | return ProviderSpec(property, Value) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /testing/integration/src/test/kotlin/me/shika/di/factory/Dependency.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Dagger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package me.shika.di.factory 17 | 18 | internal class Dependency { 19 | fun obj(): Any { 20 | return "bar" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /testing/integration/src/test/kotlin/me/shika/di/factory/InstantiableConcreteModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Dagger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package me.shika.di.factory 17 | 18 | import dagger.Module 19 | import dagger.Provides 20 | 21 | // intentionally non-static 22 | @Module 23 | internal class InstantiableConcreteModule { 24 | @Provides 25 | fun provideInt(): Int { 26 | return 42 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /testing/integration/src/test/kotlin/me/shika/di/factory/UninstantiableConcreteModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Dagger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package me.shika.di.factory 17 | 18 | import dagger.Module 19 | import dagger.Provides 20 | 21 | @Module 22 | internal class UninstantiableConcreteModule(private val l: Long) { 23 | @Provides 24 | fun provideLong(): Long { 25 | return l 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /testing/integration/src/test/kotlin/me/shika/di/factory/AbstractModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Dagger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package me.shika.di.factory 17 | 18 | import dagger.Module 19 | import dagger.Provides 20 | 21 | @Module 22 | abstract class AbstractModule { 23 | companion object { 24 | @Provides 25 | fun provideString(): String { 26 | return "foo" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/provider/ComponentBindingRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.provider 2 | 3 | import com.squareup.kotlinpoet.TypeName 4 | import com.squareup.kotlinpoet.TypeSpec 5 | import me.shika.di.dagger.renderer.asString 6 | import me.shika.di.dagger.renderer.dsl.markPrivate 7 | import me.shika.di.dagger.renderer.dsl.property 8 | import me.shika.di.dagger.renderer.provider.ProviderSpec.ProviderType.Value 9 | import me.shika.di.model.Binding 10 | import me.shika.di.model.Binding.Variation.Component 11 | 12 | class ComponentBindingRenderer( 13 | private val componentName: TypeName 14 | ): BindingRenderer { 15 | override fun TypeSpec.Builder.render(binding: Binding, variation: Component): ProviderSpec = 16 | ProviderSpec( 17 | property = property("component_${componentName.asString()}", componentName) { 18 | markPrivate() 19 | initializer("this") 20 | }, 21 | type = Value 22 | ) 23 | 24 | } 25 | -------------------------------------------------------------------------------- /testing/integration/src/test/kotlin/me/shika/di/factory/ConcreteModuleThatCouldBeAbstract.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Dagger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package me.shika.di.factory 17 | 18 | import dagger.Module 19 | import dagger.Provides 20 | 21 | @Module 22 | class ConcreteModuleThatCouldBeAbstract { 23 | companion object { 24 | @Provides 25 | fun provideDouble(): Double { 26 | return 42.0 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/src/main/java/me/shika/di/DiCompilerComponentRegistrar.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di 2 | 3 | import me.shika.di.DiCommandLineProcessor.Companion.KEY_ENABLED 4 | import me.shika.di.DiCommandLineProcessor.Companion.KEY_SOURCES 5 | import org.jetbrains.kotlin.com.intellij.mock.MockProject 6 | import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar 7 | import org.jetbrains.kotlin.config.CompilerConfiguration 8 | import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension 9 | 10 | //@AutoService(ComponentRegistrar::class) 11 | class DiCompilerComponentRegistrar: ComponentRegistrar { 12 | override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { 13 | if (configuration[KEY_ENABLED] == false) { 14 | return 15 | } 16 | val sourcesDir = configuration[KEY_SOURCES] ?: return 17 | 18 | AnalysisHandlerExtension.registerExtension( 19 | project, 20 | DiCompilerAnalysisExtension(sourcesDir = sourcesDir) 21 | ) 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/provider/RecursiveBindingRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.provider 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.TypeSpec 5 | import me.shika.di.dagger.renderer.asString 6 | import me.shika.di.dagger.renderer.dsl.property 7 | import me.shika.di.dagger.renderer.provider.ProviderSpec.ProviderType.Provider 8 | import me.shika.di.dagger.renderer.typeName 9 | import me.shika.di.model.Binding 10 | import me.shika.di.model.Binding.Variation.Recursive 11 | 12 | class RecursiveBindingRenderer : BindingRenderer { 13 | override fun TypeSpec.Builder.render(binding: Binding, variation: Recursive): ProviderSpec { 14 | val typeName = binding.key.type.typeName()!! 15 | val propertyName = "recursive_${typeName.asString()}_Provider" 16 | 17 | val property = property(propertyName, providerOf(typeName)) { 18 | initializer("%T()", DELEGATE_NAME) 19 | } 20 | 21 | return ProviderSpec(property, Provider) 22 | } 23 | 24 | companion object { 25 | private val DELEGATE_NAME = ClassName("dagger.internal", "DelegateFactory") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/DiCompilerComponentRegistrar.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di 2 | 3 | import me.shika.di.DiCommandLineProcessor.Companion.KEY_ENABLED 4 | import me.shika.di.DiCommandLineProcessor.Companion.KEY_SOURCES 5 | import org.jetbrains.kotlin.com.intellij.mock.MockProject 6 | import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar 7 | import org.jetbrains.kotlin.config.CompilerConfiguration 8 | import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension 9 | 10 | //@AutoService(ComponentRegistrar::class) 11 | class DiCompilerComponentRegistrar: ComponentRegistrar { 12 | override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { 13 | if (configuration[KEY_ENABLED] == false) { 14 | return 15 | } 16 | val sourcesDir = configuration[KEY_SOURCES] 17 | ?: throw IllegalStateException("No output folder for generated files specified") 18 | 19 | sourcesDir.deleteRecursively() 20 | sourcesDir.mkdirs() 21 | 22 | AnalysisHandlerExtension.registerExtension( 23 | project, 24 | DiCompilerAnalysisExtension(sourcesDir = sourcesDir) 25 | ) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /gradle/embedded.gradle: -------------------------------------------------------------------------------- 1 | configurations { 2 | embedded { 3 | transitive = false 4 | } 5 | implementation.extendsFrom(embedded) 6 | 7 | published 8 | implementation.extendsFrom(published) 9 | } 10 | 11 | def embeddedProjects() { 12 | return project.configurations.findByName('embedded') 13 | .resolvedConfiguration 14 | .resolvedArtifacts 15 | .findAll { it.id.componentIdentifier instanceof ProjectComponentIdentifier } 16 | .collect { project(it.id.componentIdentifier.projectPath) } 17 | } 18 | 19 | afterEvaluate { 20 | jar { 21 | from project.configurations.embedded.collect { 22 | zipTree(it) 23 | } 24 | } 25 | 26 | task sourcesJar(type: Jar) { 27 | archiveClassifier = 'sources' 28 | from embeddedProjects().collect { it.sourceSets.main.allSource } 29 | } 30 | 31 | dependencies { 32 | embeddedProjects().collect { it.configurations.findByName("published") } 33 | .forEach { 34 | if (it == null) return 35 | it.dependencies.forEach { 36 | runtimeOnly "$it.group:$it.name:$it.version" 37 | } 38 | } 39 | } 40 | 41 | artifacts { 42 | archives sourcesJar 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/dsl/FunSpecDsl.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.dsl 2 | 3 | import com.squareup.kotlinpoet.FunSpec 4 | import com.squareup.kotlinpoet.KModifier 5 | import com.squareup.kotlinpoet.ParameterSpec 6 | import com.squareup.kotlinpoet.TypeSpec 7 | import me.shika.di.dagger.renderer.typeName 8 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 9 | 10 | fun TypeSpec.Builder.function(name: String, block: FunSpec.Builder.() -> Unit) = addFunction( 11 | FunSpec.builder(name).apply(block).build() 12 | ) 13 | 14 | fun TypeSpec.Builder.primaryConstructor(block: FunSpec.Builder.() -> Unit) = primaryConstructor( 15 | FunSpec.constructorBuilder().apply(block).build() 16 | ) 17 | 18 | fun TypeSpec.Builder.overrideFunction(descriptor: FunctionDescriptor, block: FunSpec.Builder.() -> Unit) = 19 | function(descriptor.name.asString()) { 20 | addModifiers(KModifier.OVERRIDE) 21 | parametersFrom(descriptor) 22 | returns(descriptor.returnType?.typeName()!!) 23 | block() 24 | } 25 | 26 | fun FunSpec.Builder.parametersFrom(descriptor: FunctionDescriptor) = apply { 27 | descriptor.valueParameters.forEach { 28 | addParameter( 29 | ParameterSpec.builder(it.name.asString(), it.type.typeName()!!) 30 | .build() 31 | ) 32 | } 33 | } 34 | 35 | fun FunSpec.Builder.markOverride() = addModifiers(KModifier.OVERRIDE) 36 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/src/main/java/me/shika/di/dagger/renderer/dsl/FunSpecDsl.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.dsl 2 | 3 | import com.squareup.kotlinpoet.FunSpec 4 | import com.squareup.kotlinpoet.KModifier 5 | import com.squareup.kotlinpoet.ParameterSpec 6 | import com.squareup.kotlinpoet.TypeSpec 7 | import me.shika.di.dagger.renderer.typeName 8 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 9 | 10 | fun TypeSpec.Builder.function(name: String, block: FunSpec.Builder.() -> Unit) = addFunction( 11 | FunSpec.builder(name).apply(block).build() 12 | ) 13 | 14 | fun TypeSpec.Builder.primaryConstructor(block: FunSpec.Builder.() -> Unit) = primaryConstructor( 15 | FunSpec.constructorBuilder().apply(block).build() 16 | ) 17 | 18 | fun TypeSpec.Builder.overrideFunction(descriptor: FunctionDescriptor, block: FunSpec.Builder.() -> Unit) = 19 | function(descriptor.name.asString()) { 20 | addModifiers(KModifier.OVERRIDE) 21 | parametersFrom(descriptor) 22 | returns(descriptor.returnType?.typeName()!!) 23 | block() 24 | } 25 | 26 | fun FunSpec.Builder.parametersFrom(descriptor: FunctionDescriptor) = apply { 27 | descriptor.valueParameters.forEach { 28 | addParameter( 29 | ParameterSpec.builder(it.name.asString(), it.type.typeName()!!) 30 | .build() 31 | ) 32 | } 33 | } 34 | 35 | fun FunSpec.Builder.markOverride() = addModifiers(KModifier.OVERRIDE) 36 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/ComponentAnnotationDescriptor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver 2 | 3 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 4 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 5 | import org.jetbrains.kotlin.name.FqName 6 | 7 | class ComponentAnnotationDescriptor( 8 | val context: ResolverContext, 9 | val annotation: AnnotationDescriptor 10 | ) { 11 | val dependencies: List = annotation.classListValue(context, DAGGER_COMPONENT_DEPS_PARAM) 12 | val modules: List = annotation.classListValue(context, DAGGER_COMPONENT_MODULES_PARAM) 13 | .flatMap { it.resolveChildModules() + it } 14 | 15 | val moduleInstances by lazy { modules.filter { it.isInstance() } } 16 | val dependencyComponents by lazy { dependencies.filter { it.isComponent() } } 17 | 18 | private fun ClassDescriptor.resolveChildModules(): List = 19 | annotations.findAnnotation(DAGGER_MODULE_FQ_NAME) 20 | ?.classListValue(context, DAGGER_MODULE_INCLUDES_PARAM) 21 | .orEmpty() 22 | .flatMap { 23 | it.resolveChildModules() + it 24 | } 25 | } 26 | 27 | private const val DAGGER_COMPONENT_MODULES_PARAM = "modules" 28 | private const val DAGGER_COMPONENT_DEPS_PARAM = "dependencies" 29 | private const val DAGGER_MODULE_INCLUDES_PARAM = "includes" 30 | 31 | private val DAGGER_MODULE_FQ_NAME = FqName("dagger.Module") 32 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/ComponentAnnotationDescriptor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver 2 | 3 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 4 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 5 | import org.jetbrains.kotlin.name.FqName 6 | 7 | class ComponentAnnotationDescriptor( 8 | val context: ResolverContext, 9 | val annotation: AnnotationDescriptor 10 | ) { 11 | val dependencies: List = annotation.classListValue(context, DAGGER_COMPONENT_DEPS_PARAM) 12 | val modules: List = annotation.classListValue(context, DAGGER_COMPONENT_MODULES_PARAM) 13 | .flatMap { it.resolveChildModules() + it } 14 | 15 | val moduleInstances by lazy { modules.filter { it.isInstance() } } 16 | val dependencyComponents by lazy { dependencies.filter { it.isComponent() } } 17 | 18 | private fun ClassDescriptor.resolveChildModules(): List = 19 | annotations.findAnnotation(DAGGER_MODULE_FQ_NAME) 20 | ?.classListValue(context, DAGGER_MODULE_INCLUDES_PARAM) 21 | .orEmpty() 22 | .flatMap { 23 | it.resolveChildModules() + it 24 | } 25 | } 26 | 27 | private const val DAGGER_COMPONENT_MODULES_PARAM = "modules" 28 | private const val DAGGER_COMPONENT_DEPS_PARAM = "dependencies" 29 | private const val DAGGER_MODULE_INCLUDES_PARAM = "includes" 30 | 31 | private val DAGGER_MODULE_FQ_NAME = FqName("dagger.Module") 32 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/endpoints/ProvisionEndpointResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.endpoints 2 | 3 | import me.shika.di.dagger.resolver.ComponentAnnotationDescriptor 4 | import me.shika.di.dagger.resolver.ResolverContext 5 | import me.shika.di.dagger.resolver.allDescriptors 6 | import me.shika.di.dagger.resolver.isFromAny 7 | import me.shika.di.dagger.resolver.qualifiers 8 | import me.shika.di.model.Endpoint 9 | import org.jetbrains.kotlin.builtins.KotlinBuiltIns 10 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 11 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 12 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion.FUNCTIONS 13 | 14 | class ProvisionEndpointResolver( 15 | componentAnnotation: ComponentAnnotationDescriptor, 16 | private val definition: ClassDescriptor, 17 | private val context: ResolverContext 18 | ): EndpointResolver { 19 | // TODO: validate 20 | 21 | override fun invoke(): List { 22 | val componentFunctions = definition.allDescriptors(FUNCTIONS) 23 | .asSequence() 24 | .filterIsInstance() 25 | .filterNot { it.isFromAny() } 26 | 27 | return componentFunctions 28 | .filter { it.isProvisionEndpoint() } 29 | .map { Endpoint.Provided(it, it.qualifiers()) } 30 | .toList() 31 | } 32 | 33 | private fun FunctionDescriptor.isProvisionEndpoint() = 34 | valueParameters.isEmpty() && !KotlinBuiltIns.isUnit(returnType!!) 35 | } 36 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/endpoints/ProvisionEndpointResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.endpoints 2 | 3 | import me.shika.di.dagger.resolver.ComponentAnnotationDescriptor 4 | import me.shika.di.dagger.resolver.ResolverContext 5 | import me.shika.di.dagger.resolver.allDescriptors 6 | import me.shika.di.dagger.resolver.isFromAny 7 | import me.shika.di.dagger.resolver.qualifiers 8 | import me.shika.di.model.Endpoint 9 | import org.jetbrains.kotlin.builtins.KotlinBuiltIns 10 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 11 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 12 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion.FUNCTIONS 13 | 14 | class ProvisionEndpointResolver( 15 | componentAnnotation: ComponentAnnotationDescriptor, 16 | private val definition: ClassDescriptor, 17 | private val context: ResolverContext 18 | ): EndpointResolver { 19 | // TODO: validate 20 | 21 | override fun invoke(): List { 22 | val componentFunctions = definition.allDescriptors(FUNCTIONS) 23 | .asSequence() 24 | .filterIsInstance() 25 | .filterNot { it.isFromAny() } 26 | 27 | return componentFunctions 28 | .filter { it.isProvisionEndpoint() } 29 | .map { Endpoint.Provided(it, it.qualifiers()) } 30 | .toList() 31 | } 32 | 33 | private fun FunctionDescriptor.isProvisionEndpoint() = 34 | valueParameters.isEmpty() && !KotlinBuiltIns.isUnit(returnType!!) 35 | } 36 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/DiCommandLineProcessor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di 2 | 3 | import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption 4 | import org.jetbrains.kotlin.compiler.plugin.CliOption 5 | import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor 6 | import org.jetbrains.kotlin.config.CompilerConfiguration 7 | import org.jetbrains.kotlin.config.CompilerConfigurationKey 8 | import java.io.File 9 | 10 | //@AutoService(CommandLineProcessor::class) 11 | class DiCommandLineProcessor : CommandLineProcessor { 12 | override val pluginId: String = "dagger-compiler-plugin" 13 | override val pluginOptions: Collection = 14 | listOf( 15 | CliOption( 16 | "enabled", 17 | "", 18 | "Whether plugin is enabled", 19 | required = false 20 | ), 21 | CliOption( 22 | "sources", 23 | "", 24 | "generated files folder", 25 | required = true 26 | ) 27 | ) 28 | 29 | override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) { 30 | when (option.optionName) { 31 | "enabled" -> configuration.put(KEY_ENABLED, value.toBoolean()) 32 | "sources" -> configuration.put(KEY_SOURCES, File(value)) 33 | } 34 | } 35 | 36 | companion object { 37 | val KEY_ENABLED = CompilerConfigurationKey("di.plugin.enabled") 38 | val KEY_SOURCES = CompilerConfigurationKey("di.plugin.sources") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/bindings/InjectConstructorBindingResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.bindings 2 | 3 | import me.shika.di.MORE_THAN_ONE_INJECT_CONSTRUCTOR 4 | import me.shika.di.dagger.resolver.INJECT_FQ_NAME 5 | import me.shika.di.dagger.resolver.ResolverContext 6 | import me.shika.di.dagger.resolver.classDescriptor 7 | import me.shika.di.dagger.resolver.qualifiers 8 | import me.shika.di.dagger.resolver.report 9 | import me.shika.di.dagger.resolver.scopeAnnotations 10 | import me.shika.di.model.Binding 11 | import me.shika.di.model.Key 12 | import org.jetbrains.kotlin.types.KotlinType 13 | 14 | class InjectConstructorBindingResolver( 15 | private val type: KotlinType, 16 | private val context: ResolverContext 17 | ): BindingResolver { 18 | override fun invoke(): List { 19 | val classDescriptor = type.classDescriptor() ?: return emptyList() 20 | val injectableConstructors = classDescriptor.constructors.filter { it.annotations.hasAnnotation(INJECT_FQ_NAME) } 21 | if (injectableConstructors.size > 1) { 22 | classDescriptor.report(context.trace) { 23 | MORE_THAN_ONE_INJECT_CONSTRUCTOR.on(it, classDescriptor) 24 | } 25 | } 26 | return listOfNotNull( 27 | injectableConstructors.firstOrNull()?.let { 28 | Binding( 29 | Key(type, it.qualifiers().ifEmpty { classDescriptor.qualifiers() }), 30 | classDescriptor.scopeAnnotations(), 31 | Binding.Variation.Constructor(it) 32 | ) 33 | } 34 | ) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/src/main/java/me/shika/di/DiCommandLineProcessor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di 2 | 3 | import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption 4 | import org.jetbrains.kotlin.compiler.plugin.CliOption 5 | import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor 6 | import org.jetbrains.kotlin.config.CompilerConfiguration 7 | import org.jetbrains.kotlin.config.CompilerConfigurationKey 8 | import java.io.File 9 | 10 | //@AutoService(CommandLineProcessor::class) 11 | class DiCommandLineProcessor : CommandLineProcessor { 12 | override val pluginId: String = "dagger-compiler-plugin" 13 | override val pluginOptions: Collection = 14 | listOf( 15 | CliOption( 16 | "enabled", 17 | "", 18 | "Whether plugin is enabled", 19 | required = false 20 | ), 21 | CliOption( 22 | "sources", 23 | "generated files folder", 24 | "generated files folder", 25 | required = false 26 | ) 27 | ) 28 | 29 | override fun processOption(option: AbstractCliOption, value: String, configuration: CompilerConfiguration) { 30 | when (option.optionName) { 31 | "enabled" -> configuration.put(KEY_ENABLED, value.toBoolean()) 32 | "sources" -> configuration.put(KEY_SOURCES, File(value)) 33 | } 34 | } 35 | 36 | companion object { 37 | val KEY_ENABLED = CompilerConfigurationKey("di.plugin.enabled") 38 | val KEY_SOURCES = CompilerConfigurationKey("di.plugin.sources") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/provider/ConstructorBindingRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.provider 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.CodeBlock 5 | import com.squareup.kotlinpoet.TypeName 6 | import com.squareup.kotlinpoet.TypeSpec 7 | import me.shika.di.dagger.renderer.asString 8 | import me.shika.di.dagger.renderer.typeName 9 | import me.shika.di.model.Binding 10 | import me.shika.di.model.Binding.Variation.Constructor 11 | 12 | class ConstructorBindingRenderer( 13 | private val componentName: ClassName, 14 | private val deps: List 15 | ) : BindingRenderer { 16 | override fun TypeSpec.Builder.render(binding: Binding, variation: Constructor): ProviderSpec { 17 | val constructedClass = variation.source.constructedClass 18 | val returnType = constructedClass.defaultType.typeName()!! 19 | val renderedName = returnType.asString() 20 | val providerName = "${renderedName}_Provider" 21 | 22 | val providerType = providerImpl(providerName, returnType, deps, providerBody(returnType, deps)) 23 | 24 | return providerProperty( 25 | providerName.decapitalize(), 26 | deps, 27 | componentName.nestedClass(providerName), 28 | providerType, 29 | doubleCheck = binding.scopes.isNotEmpty() 30 | ) 31 | } 32 | 33 | private fun providerBody(className: TypeName, deps: List): CodeBlock { 34 | val params = deps.joinToString { it.getValue() } 35 | return CodeBlock.of("return %T($params)", className) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/bindings/InjectConstructorBindingResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.bindings 2 | 3 | import me.shika.di.MORE_THAN_ONE_INJECT_CONSTRUCTOR 4 | import me.shika.di.dagger.resolver.INJECT_FQ_NAME 5 | import me.shika.di.dagger.resolver.ResolverContext 6 | import me.shika.di.dagger.resolver.classDescriptor 7 | import me.shika.di.dagger.resolver.qualifiers 8 | import me.shika.di.dagger.resolver.report 9 | import me.shika.di.dagger.resolver.scopeAnnotations 10 | import me.shika.di.model.Binding 11 | import me.shika.di.model.Key 12 | import org.jetbrains.kotlin.types.KotlinType 13 | 14 | class InjectConstructorBindingResolver( 15 | private val type: KotlinType, 16 | private val context: ResolverContext 17 | ): BindingResolver { 18 | override fun invoke(): List { 19 | val classDescriptor = type.classDescriptor() ?: return emptyList() 20 | val injectableConstructors = classDescriptor.constructors.filter { it.annotations.hasAnnotation(INJECT_FQ_NAME) } 21 | if (injectableConstructors.size > 1) { 22 | classDescriptor.report(context.trace) { 23 | MORE_THAN_ONE_INJECT_CONSTRUCTOR.on(it, classDescriptor) 24 | } 25 | } 26 | return listOfNotNull( 27 | injectableConstructors.firstOrNull()?.let { 28 | Binding( 29 | Key(type, it.qualifiers().ifEmpty { classDescriptor.qualifiers() }), 30 | classDescriptor.scopeAnnotations(), 31 | Binding.Variation.Constructor(it) 32 | ) 33 | } 34 | ) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/bindings/ProviderOrLazyBindingResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.bindings 2 | 3 | import me.shika.di.dagger.resolver.ResolverContext 4 | import me.shika.di.dagger.resolver.classDescriptor 5 | import me.shika.di.model.Binding 6 | import me.shika.di.model.Key 7 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 8 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 9 | import org.jetbrains.kotlin.name.FqName 10 | import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe 11 | import org.jetbrains.kotlin.types.KotlinType 12 | 13 | class ProviderOrLazyBindingResolver( 14 | private val type: KotlinType, 15 | private val source: DeclarationDescriptor, 16 | private val qualifiers: List, 17 | private val context: ResolverContext 18 | ): BindingResolver { 19 | override fun invoke(): List { 20 | val classDescriptor = type.unwrap().classDescriptor() 21 | val innerType by lazy { type.arguments.first().type } 22 | 23 | val bindingType = when (classDescriptor?.fqNameSafe) { 24 | PROVIDER_FQ_NAME -> Binding.Variation.Provider(source, innerType) 25 | LAZY_FQ_NAME -> Binding.Variation.Lazy(source, innerType) 26 | else -> null 27 | } ?: return emptyList() 28 | 29 | return listOf( 30 | Binding( 31 | Key(type, qualifiers), 32 | emptyList(), 33 | bindingType 34 | ) 35 | ) 36 | } 37 | } 38 | 39 | private val PROVIDER_FQ_NAME = FqName("javax.inject.Provider") 40 | private val LAZY_FQ_NAME = FqName("dagger.Lazy") 41 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/bindings/ProviderOrLazyBindingResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.bindings 2 | 3 | import me.shika.di.dagger.resolver.ResolverContext 4 | import me.shika.di.dagger.resolver.classDescriptor 5 | import me.shika.di.model.Binding 6 | import me.shika.di.model.Key 7 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 8 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 9 | import org.jetbrains.kotlin.name.FqName 10 | import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe 11 | import org.jetbrains.kotlin.types.KotlinType 12 | 13 | class ProviderOrLazyBindingResolver( 14 | private val type: KotlinType, 15 | private val source: DeclarationDescriptor, 16 | private val qualifiers: List, 17 | private val context: ResolverContext 18 | ): BindingResolver { 19 | override fun invoke(): List { 20 | val classDescriptor = type.unwrap().classDescriptor() 21 | val innerType by lazy { type.arguments.first().type } 22 | 23 | val bindingType = when (classDescriptor?.fqNameSafe) { 24 | PROVIDER_FQ_NAME -> Binding.Variation.Provider(source, innerType) 25 | LAZY_FQ_NAME -> Binding.Variation.Lazy(source, innerType) 26 | else -> null 27 | } ?: return emptyList() 28 | 29 | return listOf( 30 | Binding( 31 | Key(type, qualifiers), 32 | emptyList(), 33 | bindingType 34 | ) 35 | ) 36 | } 37 | } 38 | 39 | private val PROVIDER_FQ_NAME = FqName("javax.inject.Provider") 40 | private val LAZY_FQ_NAME = FqName("dagger.Lazy") 41 | -------------------------------------------------------------------------------- /codegen-reflect/gradle-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | // TODO: Cleanup 2 | plugins { 3 | id 'com.gradle.plugin-publish' version '0.10.1' 4 | } 5 | 6 | apply plugin: "java-gradle-plugin" 7 | apply plugin: "org.jetbrains.kotlin.jvm" 8 | apply plugin: "maven" 9 | apply from: rootProject.file("gradle/embedded.gradle") 10 | 11 | group "me.shika.di" 12 | 13 | pluginBundle { 14 | website = 'https://github.com/ShikaSD/kotlin-compiler-me.shika.di' 15 | vcsUrl = 'https://github.com/ShikaSD/kotlin-compiler-me.shika.di.git' 16 | tags = ['kotlin', 'compiler-plugin', 'me.shika.di', 'dagger2'] 17 | 18 | plugins { 19 | diPlugin { 20 | displayName = "Dagger 2 Kotlin compiler plugin" 21 | description = "Experiment on implementation of dagger2 code generation through Kotlin compiler" 22 | } 23 | } 24 | 25 | mavenCoordinates { 26 | groupId = project.getGroup() 27 | artifactId = "dagger-compiler-plugin" 28 | version = project.getVersion() + "-r3" 29 | } 30 | } 31 | 32 | install { 33 | repositories { 34 | mavenInstaller { 35 | pom.artifactId = 'dagger-compiler-plugin' 36 | } 37 | } 38 | } 39 | 40 | gradlePlugin { 41 | plugins { 42 | diPlugin { 43 | id = "me.shika.dagger-compiler-plugin" 44 | implementationClass = "me.shika.di.DiCompilerPlugin" 45 | } 46 | } 47 | } 48 | 49 | dependencies { 50 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 51 | implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api" 52 | 53 | embedded project(':codegen-reflect:kotlin-plugin') 54 | embedded project(':common:build-info') 55 | embedded project(':common:gradle-base') 56 | } 57 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/provider/ProviderBindingRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.provider 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.CodeBlock 5 | import com.squareup.kotlinpoet.TypeSpec 6 | import me.shika.di.dagger.renderer.provider.ProviderSpec.ProviderType 7 | import me.shika.di.dagger.renderer.typeName 8 | import me.shika.di.model.Binding 9 | import me.shika.di.model.Binding.Variation.Provider 10 | 11 | class ProviderBindingRenderer( 12 | private val componentName: ClassName, 13 | private val deps: List 14 | ) : BindingRenderer { 15 | override fun TypeSpec.Builder.render(binding: Binding, variation: Provider): ProviderSpec { 16 | val parent = deps.first() 17 | return when (parent.type) { 18 | ProviderType.Provider -> parent.copy(type = ProviderType.Value) 19 | ProviderType.Value -> { 20 | val providerName = binding.renderedName(componentName) 21 | val providerType = providerImpl( 22 | providerName = providerName, 23 | returnType = variation.innerType.typeName()!!, 24 | dependencies = deps, 25 | providerBody = CodeBlock.of("return %N", deps.first().property) 26 | ) 27 | providerProperty( 28 | providerName.decapitalize(), 29 | deps, 30 | componentName.nestedClass(providerName), 31 | providerType, 32 | doubleCheck = false 33 | ).copy(type = ProviderType.Value) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/ 8 | 9 | # Gradle and Maven with auto-import 10 | # When using Gradle or Maven with auto-import, you should exclude module files, 11 | # since they will be recreated, and may cause churn. Uncomment if using 12 | # auto-import. 13 | # .idea/modules.xml 14 | # .idea/*.iml 15 | # .idea/modules 16 | # *.iml 17 | # *.ipr 18 | 19 | # CMake 20 | cmake-build-*/ 21 | 22 | # Mongo Explorer plugin 23 | .idea/**/mongoSettings.xml 24 | 25 | # File-based project format 26 | *.iws 27 | 28 | # IntelliJ 29 | out/ 30 | 31 | # mpeltonen/sbt-idea plugin 32 | .idea_modules/ 33 | 34 | # JIRA plugin 35 | atlassian-ide-plugin.xml 36 | 37 | # Cursive Clojure plugin 38 | .idea/replstate.xml 39 | 40 | # Crashlytics plugin (for Android Studio and IntelliJ) 41 | com_crashlytics_export_strings.xml 42 | crashlytics.properties 43 | crashlytics-build.properties 44 | fabric.properties 45 | 46 | # Editor-based Rest Client 47 | .idea/httpRequests 48 | 49 | # Android studio 3.1+ serialized cache file 50 | .idea/caches/build_file_checksums.ser 51 | 52 | ### Gradle template 53 | .gradle 54 | build/ 55 | 56 | # Ignore Gradle GUI config 57 | gradle-app.setting 58 | 59 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 60 | !gradle-wrapper.jar 61 | 62 | # Cache of project 63 | .gradletasknamecache 64 | 65 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 66 | # gradle/wrapper/gradle-wrapper.properties 67 | 68 | .DS_Store 69 | -------------------------------------------------------------------------------- /testing/dagger-google/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | jcenter() 5 | mavenLocal() 6 | } 7 | dependencies { 8 | classpath 'me.shika.di:dagger-compiler-plugin:0.0.3-preview' 9 | } 10 | } 11 | 12 | apply plugin: 'java-library' 13 | apply plugin: 'kotlin' 14 | apply plugin: 'me.shika.dagger-compiler-plugin' 15 | 16 | sourceSets.test.java { 17 | srcDir file('dagger/javatests') 18 | include 'dagger/functional/**' 19 | 20 | exclude 'dagger/functional/producers/**' 21 | exclude 'dagger/functional/tck/**' 22 | exclude 'dagger/functional/spi/**' 23 | } 24 | 25 | test.filter { 26 | 27 | } 28 | 29 | dependencies { 30 | testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 31 | testImplementation deps.dagger.reflect 32 | testImplementation deps.junit 33 | testImplementation deps.truth 34 | testImplementation deps.auto.value.annotations 35 | testImplementation deps.auto.factory 36 | testImplementation deps.auto.factory 37 | testAnnotationProcessor deps.auto.value.compiler 38 | testAnnotationProcessor deps.auto.factory 39 | } 40 | 41 | def ensureDaggerSubmodule = tasks.create('ensureDaggerSubmodule') { 42 | doFirst { 43 | if (!file('dagger/.git').exists()) { 44 | throw new RuntimeException( 45 | "Missing 'dagger' git submodule clone. Did you run 'git submodule update --init'?") 46 | } 47 | def describe = 'git describe'.execute(null, file('dagger')) 48 | if (describe.waitFor() != 0) { 49 | System.err.println(describe.errorStream.text) 50 | throw new RuntimeException("Could not run 'git describe' in 'dagger' git submodule clone") 51 | } 52 | } 53 | } 54 | compileTestJava.dependsOn(ensureDaggerSubmodule) 55 | 56 | -------------------------------------------------------------------------------- /testing/integration/src/test/kotlin/me/shika/di/factory/FactoryDependenciesTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Dagger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package me.shika.di.factory 17 | 18 | import com.google.common.truth.Truth.assertThat 19 | import dagger.Component 20 | import org.junit.Assert.fail 21 | import org.junit.Test 22 | import org.junit.runner.RunWith 23 | import org.junit.runners.JUnit4 24 | 25 | /** Tests for factories for components with a dependency. */ 26 | @RunWith(JUnit4::class) 27 | class FactoryDependenciesTest { 28 | @Component(dependencies = [Dependency::class]) 29 | internal interface DependencyComponent { 30 | fun `object`(): Any? 31 | @Component.Factory 32 | interface Factory { 33 | fun create(dependency: Dependency?): DependencyComponent? 34 | } 35 | } 36 | 37 | @Test 38 | fun dependency() { 39 | val component: DependencyComponent = 40 | DaggerFactoryDependenciesTest_DependencyComponent.factory().create(Dependency()) 41 | assertThat(component.`object`()).isEqualTo("bar") 42 | } 43 | 44 | @Test 45 | fun dependency_failsOnNull() { 46 | try { 47 | DaggerFactoryDependenciesTest_DependencyComponent.factory().create(null) 48 | fail() 49 | } catch (expected: NullPointerException) { 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/provider/StaticFunctionRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.provider 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.CodeBlock 5 | import com.squareup.kotlinpoet.TypeName 6 | import com.squareup.kotlinpoet.TypeSpec 7 | import me.shika.di.dagger.renderer.typeName 8 | import me.shika.di.model.Binding 9 | import me.shika.di.model.Binding.Variation.StaticFunction 10 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 11 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 12 | 13 | class StaticFunctionRenderer( 14 | private val componentName: ClassName, 15 | private val deps: List 16 | ) : BindingRenderer { 17 | override fun TypeSpec.Builder.render(binding: Binding, variation: StaticFunction): ProviderSpec { 18 | val parent = variation.source.containingDeclaration as? ClassDescriptor 19 | val parentType = parent?.typeName() 20 | val renderedName = binding.renderedName(parentType) 21 | val returnType = variation.returnType()!! 22 | val providerName = "${renderedName}_Provider" 23 | 24 | val providerType = providerImpl(providerName, returnType, deps, providerBody(variation.source, parentType)) 25 | 26 | return providerProperty( 27 | providerName.decapitalize(), 28 | deps, 29 | componentName.nestedClass(providerName), 30 | providerType, 31 | doubleCheck = binding.scopes.isNotEmpty() 32 | ) 33 | } 34 | 35 | private fun providerBody(source: FunctionDescriptor, parentType: TypeName?): CodeBlock { 36 | val params = deps.joinToString(",") { it.getValue() } 37 | return CodeBlock.of( 38 | "return %T.${source.name}($params)", 39 | parentType 40 | ) 41 | } 42 | 43 | private fun StaticFunction.returnType() = 44 | source.returnType?.typeName() 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is this? 2 | 3 | An experiment on the idea of Google's Dagger using different means of code generation: Kotlin compiler. 4 | 5 | For now I continue working on implementing richer feature-set. Most of the code is generated in frontend using KotlinPoet. 6 | IDE support works out of the box (at least for JB products) thanks to gradle integration. 7 | As a side effect of this project, I am [exploring](https://github.com/ShikaSD/kotlin-compiler-di/blob/kotlin-syntax-experiment/PROPOSAL.md) how DI can be done better using Kotlin (in compile time safe way). 8 | 9 | If you, for any reason, want to look through this: 10 | 11 | - The plugin files are in: `buildSrc/compiler-plugin/kotlin-plugin` (this is what attaches to compiler) 12 | - The test project files is in `src/main/kotlin` (this is what gets compiled) 13 | 14 | Right now I have implemented the concept of: 15 | - exposing dependencies through `Component`. 16 | - providing them through `Module` implemented as `object` or `class` instance. 17 | - providing using `@Inject` annotated constructor 18 | - inject dependencies into `@Inject` annotated fields 19 | - inject dependencies into `@Inject` annotated functions (one param only) 20 | - local scoping inside component using `@Scope` annotations 21 | - provide external dependencies using `@Factory` 22 | - Support of `@BindsInstance` for components 23 | - Support for `@Qualifier` 24 | - external dependencies through `@Builder` 25 | - Default builder 26 | - Type mapping using `@Binds` 27 | - `Lazy` and `Provider` support 28 | 29 | TODO (in any order): 30 | - Default module instantiation when class has an empty constructor 31 | - `Reusable` support 32 | - `IntoSet` / `IntoMap` 33 | - Subcomponents 34 | - Proper scope support 35 | - ... the rest 36 | 37 | ## To launch test file 38 | 39 | Use `./gradlew run`. It will build and install plugin to maven local, after it will compile the test project 40 | using freshly built plugin and run it. Yay! 41 | 42 | ## Now publishing to gradle plugin portal! 43 | 44 | To get the plugin, follow instructions [here](https://plugins.gradle.org/plugin/me.shika.dagger-compiler-plugin). 45 | -------------------------------------------------------------------------------- /testing/integration/src/test/kotlin/me/shika/di/factory/FactoryRequiredModulesTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Dagger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package me.shika.di.factory 17 | 18 | import com.google.common.truth.Truth.assertThat 19 | import dagger.Component 20 | import org.junit.Assert.fail 21 | import org.junit.Test 22 | import org.junit.runner.RunWith 23 | import org.junit.runners.JUnit4 24 | 25 | /** 26 | * Tests for factories for components that have a module that must have an instance provided by the 27 | * user. 28 | */ 29 | @RunWith(JUnit4::class) 30 | class FactoryRequiredModulesTest { 31 | @Component(modules = [UninstantiableConcreteModule::class]) 32 | internal interface UninstantiableConcreteModuleComponent { 33 | val long: Long 34 | 35 | @Component.Factory 36 | interface Factory { 37 | fun create(module: UninstantiableConcreteModule?): UninstantiableConcreteModuleComponent? 38 | } 39 | } 40 | 41 | @Test 42 | fun uninstantiableConcreteModule() { 43 | val component: UninstantiableConcreteModuleComponent = 44 | DaggerFactoryRequiredModulesTest_UninstantiableConcreteModuleComponent.factory() 45 | .create(UninstantiableConcreteModule(42L)) 46 | assertThat(component.long).isEqualTo(42L) 47 | } 48 | 49 | @Test 50 | fun uninstantiableConcreteModule_failsOnNull() { 51 | try { 52 | DaggerFactoryRequiredModulesTest_UninstantiableConcreteModuleComponent.factory().create(null) 53 | fail() 54 | } catch (expected: NullPointerException) { 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/provider/InstancePropertyRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.provider 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.CodeBlock 5 | import com.squareup.kotlinpoet.PropertySpec 6 | import com.squareup.kotlinpoet.TypeName 7 | import com.squareup.kotlinpoet.TypeSpec 8 | import me.shika.di.dagger.renderer.provider.ProviderSpec.ProviderType 9 | import me.shika.di.dagger.renderer.typeName 10 | import me.shika.di.model.Binding 11 | import me.shika.di.model.Binding.Variation.InstanceProperty 12 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 13 | import org.jetbrains.kotlin.descriptors.PropertyDescriptor 14 | 15 | class InstancePropertyRenderer(private val componentName: ClassName) : BindingRenderer { 16 | override fun TypeSpec.Builder.render(binding: Binding, variation: InstanceProperty): ProviderSpec { 17 | val parent = variation.source.containingDeclaration as? ClassDescriptor 18 | val parentType = parent?.typeName()!! 19 | val renderedName = binding.renderedName(parentType) 20 | val returnType = variation.returnType()!! 21 | val providerName = "${renderedName}_Provider" 22 | 23 | val instanceProperty = instanceProperty(parentType) 24 | val parentDependency = listOf(ProviderSpec(instanceProperty, ProviderType.Value)) 25 | 26 | val providerType = providerImpl( 27 | providerName, 28 | returnType, 29 | parentDependency, 30 | instanceProperty.providerBody(variation.source) 31 | ) 32 | 33 | return providerProperty( 34 | providerName.decapitalize(), 35 | parentDependency, 36 | componentName.nestedClass(providerName), 37 | providerType, 38 | doubleCheck = binding.scopes.isNotEmpty() 39 | ) 40 | } 41 | 42 | private fun PropertySpec.providerBody(source: PropertyDescriptor): CodeBlock { 43 | return CodeBlock.of( 44 | "return %N.${source.name}", 45 | this 46 | ) 47 | } 48 | 49 | private fun InstanceProperty.returnType() = 50 | source.returnType?.typeName() 51 | 52 | private fun TypeSpec.Builder.instanceProperty(type: TypeName): PropertySpec = 53 | propertySpecs.first { it.type == type } 54 | } 55 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/provider/InstanceFunctionRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.provider 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.CodeBlock 5 | import com.squareup.kotlinpoet.PropertySpec 6 | import com.squareup.kotlinpoet.TypeName 7 | import com.squareup.kotlinpoet.TypeSpec 8 | import me.shika.di.dagger.renderer.provider.ProviderSpec.ProviderType 9 | import me.shika.di.dagger.renderer.typeName 10 | import me.shika.di.model.Binding 11 | import me.shika.di.model.Binding.Variation.InstanceFunction 12 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 13 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 14 | 15 | class InstanceFunctionRenderer( 16 | private val componentName: ClassName, 17 | private val deps: List 18 | ) : BindingRenderer { 19 | override fun TypeSpec.Builder.render(binding: Binding, variation: InstanceFunction): ProviderSpec { 20 | val parent = variation.source.containingDeclaration as? ClassDescriptor 21 | val parentType = parent?.typeName()!! 22 | val renderedName = binding.renderedName(parentType).capitalize() 23 | val returnType = variation.returnType()!! 24 | val providerName = "${renderedName}_Provider" 25 | 26 | val instanceProperty = instanceProperty(parentType) 27 | val depsWithParent = deps + ProviderSpec(instanceProperty, ProviderType.Value) 28 | 29 | val providerType = providerImpl(providerName, returnType, depsWithParent, instanceProperty.providerBody(variation.source)) 30 | 31 | return providerProperty( 32 | providerName.decapitalize(), 33 | depsWithParent, 34 | componentName.nestedClass(providerName), 35 | providerType, 36 | doubleCheck = binding.scopes.isNotEmpty() 37 | ) 38 | } 39 | 40 | private fun PropertySpec.providerBody(source: FunctionDescriptor): CodeBlock { 41 | val params = deps.joinToString(",") { it.getValue() } 42 | return CodeBlock.of( 43 | "return %N.${source.name}($params)", 44 | this 45 | ) 46 | } 47 | 48 | private fun InstanceFunction.returnType() = 49 | source.returnType?.typeName() 50 | 51 | private fun TypeSpec.Builder.instanceProperty(type: TypeName): PropertySpec = 52 | propertySpecs.first { it.type == type } 53 | } 54 | -------------------------------------------------------------------------------- /common/gradle-base/src/main/kotlin/me/shika/di/AbstractDiCompilerSubplugin.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.tasks.compile.AbstractCompile 5 | import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions 6 | import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation 7 | import org.jetbrains.kotlin.gradle.plugin.KotlinGradleSubplugin 8 | import org.jetbrains.kotlin.gradle.plugin.SubpluginOption 9 | import java.io.File 10 | 11 | abstract class AbstractDiCompilerSubplugin: KotlinGradleSubplugin { 12 | override fun apply( 13 | project: Project, 14 | kotlinCompile: AbstractCompile, 15 | javaCompile: AbstractCompile?, 16 | variantData: Any?, 17 | androidProjectHandler: Any?, 18 | kotlinCompilation: KotlinCompilation? 19 | ): List { 20 | val extension = project.extensions.findByType(DiCompilerExtension::class.java) ?: DiCompilerExtension() 21 | 22 | val sourceSetName = if (variantData != null) { 23 | // Lol 24 | variantData.javaClass.getMethod("getName").run { 25 | isAccessible = true 26 | invoke(variantData) as String 27 | } 28 | } else { 29 | if (kotlinCompilation == null) error("In non-Android projects, Kotlin compilation should not be null") 30 | kotlinCompilation.compilationName 31 | } 32 | 33 | val sources = File(project.buildDir, "generated/source/me.shika.di-compiler/$sourceSetName/") 34 | kotlinCompilation?.allKotlinSourceSets?.forEach { 35 | it.kotlin.srcDir(sources) 36 | it.kotlin.exclude { it.file.startsWith(sources) } 37 | } 38 | 39 | // Lol #2 40 | variantData?.javaClass?.methods?.first { it.name =="addJavaSourceFoldersToModel" }?.apply { 41 | isAccessible = true 42 | invoke(variantData, sources) 43 | } 44 | 45 | return listOf( 46 | SubpluginOption( 47 | key = "enabled", 48 | value = extension.enabled.toString() 49 | ), 50 | SubpluginOption( 51 | key = "sources", 52 | value = sources.absolutePath 53 | ) 54 | ) 55 | } 56 | 57 | override fun getCompilerPluginId(): String = "dagger-compiler-plugin" 58 | 59 | override fun isApplicable(project: Project, task: AbstractCompile): Boolean = 60 | project.plugins.hasPlugin(DiCompilerPlugin::class.java) 61 | } 62 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /codegen/gradle-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.gradle.plugin-publish' version '0.10.1' 3 | } 4 | 5 | apply plugin: "java-gradle-plugin" 6 | apply plugin: "org.jetbrains.kotlin.jvm" 7 | apply plugin: "maven" 8 | 9 | group "me.shika.di" 10 | 11 | configurations { 12 | embedded { 13 | transitive = false 14 | } 15 | implementation.extendsFrom(embedded) 16 | } 17 | 18 | def ARTIFACT_ID = "dagger-compiler-plugin" 19 | 20 | pluginBundle { 21 | website = 'https://github.com/ShikaSD/kotlin-compiler-di' 22 | vcsUrl = 'https://github.com/ShikaSD/kotlin-compiler-di.git' 23 | tags = ['kotlin', 'compiler-plugin', 'di', 'dagger2'] 24 | 25 | plugins { 26 | diPlugin { 27 | displayName = "Dagger 2 Kotlin compiler plugin" 28 | description = "Experiment on implementation of dagger2 code generation through Kotlin compiler" 29 | } 30 | } 31 | 32 | mavenCoordinates { 33 | groupId = project.getGroup() 34 | artifactId = ARTIFACT_ID 35 | version = project.getVersion() 36 | } 37 | } 38 | 39 | install { 40 | repositories { 41 | mavenInstaller { 42 | pom.artifactId = ARTIFACT_ID 43 | } 44 | } 45 | } 46 | 47 | gradlePlugin { 48 | plugins { 49 | diPlugin { 50 | id = "me.shika.$ARTIFACT_ID" 51 | implementationClass = "me.shika.di.DiCompilerPlugin" 52 | } 53 | } 54 | } 55 | 56 | dependencies { 57 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 58 | implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api" 59 | 60 | embedded project(':codegen:kotlin-plugin') 61 | embedded project(':common:build-info') 62 | embedded project(':common:gradle-base') 63 | 64 | embeddedProjects().collect { it.configurations.findByName("published") } 65 | .forEach { 66 | if (it == null) return 67 | it.dependencies.forEach { 68 | runtimeOnly "$it.group:$it.name:$it.version" 69 | } 70 | } 71 | } 72 | 73 | jar { 74 | from project.configurations.embedded.collect { 75 | zipTree(it) 76 | } 77 | } 78 | 79 | task sourcesJar(type: Jar) { 80 | archiveClassifier = 'sources' 81 | from embeddedProjects().collect { it.sourceSets.main.allSource } 82 | } 83 | 84 | artifacts { 85 | archives sourcesJar 86 | } 87 | 88 | def embeddedProjects() { 89 | return project.configurations.findByName('embedded') 90 | .resolvedConfiguration 91 | .resolvedArtifacts 92 | .findAll { it.id.componentIdentifier instanceof ProjectComponentIdentifier } 93 | .collect { project(it.id.componentIdentifier.projectPath) } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/creator/DefaultBuilderRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.creator 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.KModifier.LATEINIT 5 | import com.squareup.kotlinpoet.KModifier.PRIVATE 6 | import com.squareup.kotlinpoet.ParameterSpec 7 | import com.squareup.kotlinpoet.PropertySpec 8 | import com.squareup.kotlinpoet.TypeSpec 9 | import me.shika.di.dagger.renderer.asString 10 | import me.shika.di.dagger.renderer.dsl.companionObject 11 | import me.shika.di.dagger.renderer.dsl.function 12 | import me.shika.di.dagger.renderer.dsl.nestedClass 13 | import me.shika.di.dagger.renderer.parameterName 14 | import me.shika.di.dagger.renderer.typeName 15 | import me.shika.di.model.Key 16 | 17 | class DefaultBuilderRenderer( 18 | private val componentClassName: ClassName, 19 | private val constructorParams: List, 20 | private val builder: TypeSpec.Builder 21 | ) { 22 | private val builderClassName = componentClassName.nestedClass(BUILDER_IMPL_NAME) 23 | 24 | fun render() { 25 | builder.apply { 26 | builderClass() 27 | builderPublicMethod() 28 | } 29 | } 30 | 31 | private fun TypeSpec.Builder.builderClass() { 32 | nestedClass(BUILDER_IMPL_NAME) { 33 | val paramToProperty = constructorParams.associateWith { 34 | PropertySpec.builder(it.parameterName(), it.type.typeName()!!, PRIVATE, LATEINIT) 35 | .mutable(true) 36 | .build() 37 | } 38 | addProperties(paramToProperty.values) 39 | paramToProperty.values.forEach { 40 | createMethod(it) 41 | } 42 | 43 | function("build") { 44 | val params = constructorParams.joinToString { paramToProperty[it]!!.name } 45 | addCode( 46 | "return %T(${params})", 47 | componentClassName 48 | ) 49 | } 50 | } 51 | } 52 | 53 | private fun TypeSpec.Builder.createMethod(param: PropertySpec) { 54 | val name = param.type.asString().decapitalize() 55 | function(name) { 56 | returns(builderClassName) 57 | addParameter(ParameterSpec.builder(name, param.type).build()) 58 | addCode("this.%N = ${name}\n", param) 59 | addCode("return this") 60 | } 61 | } 62 | 63 | private fun TypeSpec.Builder.builderPublicMethod() { 64 | companionObject { 65 | function("builder") { 66 | returns(builderClassName) 67 | addCode("return ${BUILDER_IMPL_NAME}()") 68 | } 69 | } 70 | } 71 | 72 | companion object { 73 | private const val BUILDER_IMPL_NAME = "Builder" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /testing/integration/src/test/kotlin/me/shika/di/factory/FactoryMixedParametersTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Dagger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package me.shika.di.factory 17 | 18 | import com.google.common.truth.Truth.assertThat 19 | import dagger.BindsInstance 20 | import dagger.Component 21 | import org.junit.Test 22 | import org.junit.runner.RunWith 23 | import org.junit.runners.JUnit4 24 | import java.util.Random 25 | import javax.inject.Provider 26 | 27 | /** Tests for component factories with multiple parameters. */ 28 | @RunWith(JUnit4::class) 29 | class FactoryMixedParametersTest { 30 | @Component( 31 | modules = [AbstractModule::class, UninstantiableConcreteModule::class, InstantiableConcreteModule::class], 32 | dependencies = [Dependency::class] 33 | ) 34 | internal interface MixedArgComponent { 35 | fun string(): String? 36 | val int: Int 37 | val long: Long 38 | fun `object`(): Any? 39 | val double: Double 40 | fun randomProvider(): Provider 41 | @Component.Factory 42 | interface Factory { 43 | fun create( 44 | @BindsInstance d: Double, 45 | dependency: Dependency?, 46 | module: UninstantiableConcreteModule?, 47 | @BindsInstance random: Random? 48 | ): MixedArgComponent? 49 | } 50 | } 51 | 52 | @Test 53 | fun mixedArgComponent() { 54 | val random = Random() 55 | val component: MixedArgComponent = 56 | DaggerFactoryMixedParametersTest_MixedArgComponent.factory() 57 | .create( 58 | 3.0, 59 | Dependency(), 60 | UninstantiableConcreteModule(2L), 61 | random 62 | ) 63 | assertThat(component.string()).isEqualTo("foo") 64 | assertThat(component.int).isEqualTo(42) 65 | assertThat(component.double).isEqualTo(3.0) 66 | assertThat(component.`object`()).isEqualTo("bar") 67 | assertThat(component.long).isEqualTo(2L) 68 | assertThat(component.randomProvider().get()).isSameInstanceAs(random) 69 | assertThat(component.randomProvider().get()).isSameInstanceAs(random) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/bindings/DependencyBindingResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.bindings 2 | 3 | import me.shika.di.dagger.resolver.ComponentAnnotationDescriptor 4 | import me.shika.di.dagger.resolver.ResolverContext 5 | import me.shika.di.dagger.resolver.allDescriptors 6 | import me.shika.di.dagger.resolver.isFromAny 7 | import me.shika.di.dagger.resolver.qualifiers 8 | import me.shika.di.dagger.resolver.scopeAnnotations 9 | import me.shika.di.model.Binding 10 | import me.shika.di.model.Binding.Variation.InstanceFunction 11 | import me.shika.di.model.Binding.Variation.InstanceProperty 12 | import me.shika.di.model.Key 13 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 14 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 15 | import org.jetbrains.kotlin.descriptors.PropertyDescriptor 16 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter 17 | 18 | class DependencyBindingResolver( 19 | private val componentAnnotation: ComponentAnnotationDescriptor, 20 | private val definition: ClassDescriptor, 21 | private val context: ResolverContext 22 | ): BindingResolver { 23 | override fun invoke(): List = 24 | componentAnnotation.dependencies 25 | .flatMap { resolveBindings(it) } 26 | 27 | private fun resolveBindings(dependency: ClassDescriptor): List = 28 | resolveDependencyFunctions(dependency) + resolveDependencyProperties(dependency) + 29 | Binding( 30 | Key(dependency.defaultType, dependency.qualifiers()), 31 | dependency.scopeAnnotations(), 32 | Binding.Variation.BoundInstance(dependency) 33 | ) 34 | 35 | private fun resolveDependencyFunctions(dependency: ClassDescriptor): List = 36 | dependency.allDescriptors(DescriptorKindFilter.FUNCTIONS) 37 | .asSequence() 38 | .filterIsInstance() 39 | .filterNot { it.isFromAny() } 40 | .filter { it.valueParameters.isEmpty() } 41 | .map { 42 | Binding( 43 | Key(it.returnType!!, it.qualifiers()), 44 | it.scopeAnnotations(), 45 | InstanceFunction(it) 46 | ) 47 | } 48 | .toList() 49 | 50 | private fun resolveDependencyProperties(dependency: ClassDescriptor): List = 51 | dependency.allDescriptors(DescriptorKindFilter.VARIABLES) 52 | .asSequence() 53 | .filterIsInstance() 54 | .map { 55 | Binding( 56 | Key(it.returnType!!, it.qualifiers()), 57 | it.scopeAnnotations(), 58 | InstanceProperty(it) 59 | ) 60 | } 61 | .toList() 62 | } 63 | 64 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/bindings/DependencyBindingResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.bindings 2 | 3 | import me.shika.di.dagger.resolver.ComponentAnnotationDescriptor 4 | import me.shika.di.dagger.resolver.ResolverContext 5 | import me.shika.di.dagger.resolver.allDescriptors 6 | import me.shika.di.dagger.resolver.isFromAny 7 | import me.shika.di.dagger.resolver.qualifiers 8 | import me.shika.di.dagger.resolver.scopeAnnotations 9 | import me.shika.di.model.Binding 10 | import me.shika.di.model.Binding.Variation.InstanceFunction 11 | import me.shika.di.model.Binding.Variation.InstanceProperty 12 | import me.shika.di.model.Key 13 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 14 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 15 | import org.jetbrains.kotlin.descriptors.PropertyDescriptor 16 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter 17 | 18 | class DependencyBindingResolver( 19 | private val componentAnnotation: ComponentAnnotationDescriptor, 20 | private val definition: ClassDescriptor, 21 | private val context: ResolverContext 22 | ): BindingResolver { 23 | override fun invoke(): List = 24 | componentAnnotation.dependencies 25 | .flatMap { resolveBindings(it) } 26 | 27 | private fun resolveBindings(dependency: ClassDescriptor): List = 28 | resolveDependencyFunctions(dependency) + resolveDependencyProperties(dependency) + 29 | Binding( 30 | Key(dependency.defaultType, dependency.qualifiers()), 31 | dependency.scopeAnnotations(), 32 | Binding.Variation.BoundInstance(dependency) 33 | ) 34 | 35 | private fun resolveDependencyFunctions(dependency: ClassDescriptor): List = 36 | dependency.allDescriptors(DescriptorKindFilter.FUNCTIONS) 37 | .asSequence() 38 | .filterIsInstance() 39 | .filterNot { it.isFromAny() } 40 | .filter { it.valueParameters.isEmpty() } 41 | .map { 42 | Binding( 43 | Key(it.returnType!!, it.qualifiers()), 44 | it.scopeAnnotations(), 45 | InstanceFunction(it) 46 | ) 47 | } 48 | .toList() 49 | 50 | private fun resolveDependencyProperties(dependency: ClassDescriptor): List = 51 | dependency.allDescriptors(DescriptorKindFilter.VARIABLES) 52 | .asSequence() 53 | .filterIsInstance() 54 | .map { 55 | Binding( 56 | Key(it.returnType!!, it.qualifiers()), 57 | it.scopeAnnotations(), 58 | InstanceProperty(it) 59 | ) 60 | } 61 | .toList() 62 | } 63 | 64 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/provider/LazyBindingRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.provider 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.CodeBlock 5 | import com.squareup.kotlinpoet.MemberName 6 | import com.squareup.kotlinpoet.ParameterizedTypeName 7 | import com.squareup.kotlinpoet.TypeName 8 | import com.squareup.kotlinpoet.TypeSpec 9 | import me.shika.di.dagger.renderer.dsl.markPrivate 10 | import me.shika.di.dagger.renderer.dsl.property 11 | import me.shika.di.dagger.renderer.provider.ProviderSpec.ProviderType 12 | import me.shika.di.dagger.renderer.typeName 13 | import me.shika.di.model.Binding 14 | import me.shika.di.model.Binding.Variation.Lazy 15 | 16 | class LazyBindingRenderer( 17 | private val componentName: ClassName, 18 | private val deps: List 19 | ) : BindingRenderer { 20 | override fun TypeSpec.Builder.render(binding: Binding, variation: Lazy): ProviderSpec { 21 | val parent = deps.first() 22 | val parentType = parent.property.type 23 | val currentType = binding.key.type.typeName() 24 | val cast = castIfNeeded(parentType, currentType) 25 | 26 | val initCodeBlock = when (parent.type) { 27 | ProviderType.Provider -> CodeBlock.of("%M(${parent.property.name})$cast", DOUBLE_CHECK_LAZY) 28 | ProviderType.Value -> { 29 | val providerName = binding.renderedName(componentName) 30 | val providerType = providerImpl( 31 | providerName = providerName, 32 | returnType = variation.innerType.typeName()!!, 33 | dependencies = deps, 34 | providerBody = CodeBlock.of("return %N", deps.first().property) 35 | ) 36 | CodeBlock.of("%M(%T(${parent.property.name}))$cast", DOUBLE_CHECK_LAZY, providerType) 37 | } 38 | } 39 | 40 | val lazyProperty = property( 41 | "${parent.property.name}_Lazy", 42 | binding.key.type.typeName()!! 43 | ) { 44 | markPrivate() 45 | initializer(initCodeBlock) 46 | } 47 | 48 | return ProviderSpec( 49 | lazyProperty, 50 | ProviderType.Value 51 | ) 52 | } 53 | 54 | private fun castIfNeeded(parentType: TypeName, currentType: TypeName?) = 55 | if (parentType is ParameterizedTypeName && currentType is ParameterizedTypeName) { 56 | val parentInnerType = parentType.typeArguments.first() 57 | val currentInnerType = currentType.typeArguments.first() 58 | if (parentInnerType != currentInnerType) "as Lazy<$currentInnerType>" else "" 59 | } else { "" } 60 | 61 | companion object { 62 | private val DOUBLE_CHECK_LAZY = MemberName(DOUBLE_CHECK_NAME, "lazy") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/creator/FactoryRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.creator 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.TypeName 5 | import com.squareup.kotlinpoet.TypeSpec 6 | import me.shika.di.dagger.renderer.dsl.companionObject 7 | import me.shika.di.dagger.renderer.dsl.function 8 | import me.shika.di.dagger.renderer.dsl.markPrivate 9 | import me.shika.di.dagger.renderer.dsl.nestedClass 10 | import me.shika.di.dagger.renderer.dsl.overrideFunction 11 | import me.shika.di.dagger.renderer.typeName 12 | import me.shika.di.dagger.resolver.creator.FactoryDescriptor 13 | import me.shika.di.model.Key 14 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 15 | import org.jetbrains.kotlin.descriptors.ClassKind 16 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 17 | 18 | class FactoryRenderer( 19 | private val componentClassName: ClassName, 20 | private val constructorParams: List, 21 | private val builder: TypeSpec.Builder 22 | ) { 23 | fun render(factoryDescriptor: FactoryDescriptor) { 24 | val method = factoryDescriptor.method ?: return 25 | val factoryClass = factoryDescriptor.method?.containingDeclaration as? ClassDescriptor ?: return 26 | val factoryInterfaceName = factoryClass.typeName() ?: return 27 | 28 | builder.apply { 29 | factoryClass(factoryInterfaceName, method, isInterface = factoryClass.kind == ClassKind.INTERFACE) 30 | factoryPublicMethod(factoryInterfaceName) 31 | } 32 | } 33 | 34 | private fun TypeSpec.Builder.factoryClass( 35 | factoryClassName: TypeName, 36 | factoryMethod: FunctionDescriptor, 37 | isInterface: Boolean 38 | ) { 39 | nestedClass(FACTORY_IMPL_NAME) { 40 | markPrivate() 41 | if (isInterface) { 42 | addSuperinterface(factoryClassName) 43 | } else { 44 | superclass(factoryClassName) 45 | } 46 | overrideFunction(factoryMethod) { 47 | addCode( 48 | "return %T(${factoryMethod.params()})", 49 | componentClassName 50 | ) 51 | } 52 | } 53 | } 54 | 55 | private fun TypeSpec.Builder.factoryPublicMethod(factoryClassName: TypeName) { 56 | companionObject { 57 | function("factory") { 58 | returns(factoryClassName) 59 | addCode("return $FACTORY_IMPL_NAME()") 60 | } 61 | } 62 | } 63 | 64 | private fun FunctionDescriptor.params(): String = 65 | constructorParams.mapNotNull { paramKey -> 66 | valueParameters.find { param -> 67 | param.type == paramKey.type && paramKey.qualifiers.all { param.annotations.hasAnnotation(it.fqName!!) } 68 | } 69 | }.joinToString { it.name.asString() } 70 | 71 | companion object { 72 | private const val FACTORY_IMPL_NAME = "Factory" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/src/main/java/me/shika/di/dagger/renderer/creator/DefaultBuilderRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.creator 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.KModifier.ABSTRACT 5 | import com.squareup.kotlinpoet.KModifier.LATEINIT 6 | import com.squareup.kotlinpoet.KModifier.PRIVATE 7 | import com.squareup.kotlinpoet.MemberName 8 | import com.squareup.kotlinpoet.ParameterSpec 9 | import com.squareup.kotlinpoet.PropertySpec 10 | import com.squareup.kotlinpoet.TypeSpec 11 | import me.shika.di.dagger.renderer.asString 12 | import me.shika.di.dagger.renderer.dsl.companionObject 13 | import me.shika.di.dagger.renderer.dsl.function 14 | import me.shika.di.dagger.renderer.dsl.nestedInterface 15 | import me.shika.di.dagger.renderer.parameterName 16 | import me.shika.di.dagger.renderer.typeName 17 | import me.shika.di.model.Key 18 | 19 | class DefaultBuilderRenderer( 20 | private val originalComponentClassName: ClassName, 21 | private val componentClassName: ClassName, 22 | private val constructorParams: List, 23 | private val builder: TypeSpec.Builder 24 | ) { 25 | private val builderClassName = componentClassName.nestedClass(BUILDER_IMPL_NAME) 26 | 27 | fun render() { 28 | builder.apply { 29 | builderClass() 30 | builderPublicMethod() 31 | } 32 | } 33 | 34 | private fun TypeSpec.Builder.builderClass() { 35 | nestedInterface(BUILDER_IMPL_NAME) { 36 | addAnnotation(DAGGER_ANNOTATION_NAME) 37 | val paramToProperty = constructorParams.associateWith { 38 | PropertySpec.builder(it.parameterName(), it.type.typeName()!!, PRIVATE, LATEINIT) 39 | .mutable(true) 40 | .build() 41 | } 42 | paramToProperty.values.forEach { 43 | createMethod(it) 44 | } 45 | 46 | function("build") { 47 | addModifiers(ABSTRACT) 48 | returns(componentClassName) 49 | } 50 | } 51 | } 52 | 53 | private fun TypeSpec.Builder.createMethod(param: PropertySpec) { 54 | val name = param.type.asString().decapitalize() 55 | function(name) { 56 | addModifiers(ABSTRACT) 57 | returns(builderClassName) 58 | addParameter(ParameterSpec.builder(name, param.type).build()) 59 | } 60 | } 61 | 62 | private fun TypeSpec.Builder.builderPublicMethod() { 63 | companionObject { 64 | function("builder") { 65 | addAnnotation(JvmStatic::class) 66 | returns(builderClassName) 67 | addCode("return %M(%T::class.java)", DAGGER_BUILDER_NAME, builderClassName) 68 | } 69 | } 70 | } 71 | 72 | companion object { 73 | private const val BUILDER_IMPL_NAME = "Builder" 74 | private val DAGGER_BUILDER_NAME = MemberName("dagger.Dagger", "builder") 75 | private val DAGGER_ANNOTATION_NAME = ClassName("dagger", "Component", "Builder") 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/model/resolution.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.model 2 | 3 | import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor 4 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 5 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 6 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 7 | import org.jetbrains.kotlin.descriptors.PropertyDescriptor 8 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 9 | import org.jetbrains.kotlin.types.KotlinType 10 | 11 | data class GraphNode(val value: Binding, val dependencies: List) 12 | 13 | data class ResolveResult(val endpoint: Endpoint, val graph: List) 14 | 15 | sealed class Endpoint { 16 | abstract val source: FunctionDescriptor 17 | abstract val qualifiers: List 18 | 19 | data class Provided(override val source: FunctionDescriptor, override val qualifiers: List) : Endpoint() 20 | data class Injected( 21 | override val source: FunctionDescriptor, 22 | val value: Injectable, 23 | override val qualifiers: List 24 | ) : Endpoint() 25 | 26 | val types get() = when (this) { 27 | is Injected -> value.types 28 | is Provided -> listOf(source.returnType) 29 | } 30 | } 31 | 32 | sealed class Injectable { 33 | data class Setter(val cls: ClassDescriptor, val descriptor: FunctionDescriptor): Injectable() 34 | data class Property(val cls: ClassDescriptor, val descriptor: PropertyDescriptor): Injectable() 35 | 36 | val types get() = when(this) { 37 | is Setter -> descriptor.valueParameters.map { it.type } 38 | is Property -> listOf(descriptor.returnType) 39 | } 40 | } 41 | 42 | data class Key( 43 | val type: KotlinType, 44 | val qualifiers: List = emptyList() 45 | ) 46 | 47 | data class Binding( 48 | val key: Key, 49 | val scopes: List, 50 | val bindingType: Variation 51 | ) { 52 | sealed class Variation { 53 | abstract val source: DeclarationDescriptor 54 | 55 | data class Constructor(override val source: ClassConstructorDescriptor): Variation() 56 | data class InstanceFunction(override val source: FunctionDescriptor): Variation() 57 | data class InstanceProperty(override val source: PropertyDescriptor): Variation() 58 | data class StaticFunction(override val source: FunctionDescriptor): Variation() 59 | data class BoundInstance(override val source: DeclarationDescriptor): Variation() 60 | data class Component(override val source: ClassDescriptor) : Variation() 61 | 62 | data class Equality(override val source: FunctionDescriptor): Variation() 63 | data class Recursive(override val source: DeclarationDescriptor, val delegate: Binding): Variation() 64 | 65 | data class Provider(override val source: DeclarationDescriptor, val innerType: KotlinType): Variation() 66 | data class Lazy(override val source: DeclarationDescriptor, val innerType: KotlinType): Variation() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /common/kotlin-base/src/main/kotlin/me/shika/di/model/resolution.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.model 2 | 3 | import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor 4 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 5 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 6 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 7 | import org.jetbrains.kotlin.descriptors.PropertyDescriptor 8 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 9 | import org.jetbrains.kotlin.types.KotlinType 10 | 11 | data class GraphNode(val value: Binding, val dependencies: List) 12 | 13 | data class ResolveResult(val endpoint: Endpoint, val graph: List) 14 | 15 | sealed class Endpoint { 16 | abstract val source: FunctionDescriptor 17 | abstract val qualifiers: List 18 | 19 | data class Provided(override val source: FunctionDescriptor, override val qualifiers: List) : Endpoint() 20 | data class Injected( 21 | override val source: FunctionDescriptor, 22 | val value: Injectable, 23 | override val qualifiers: List 24 | ) : Endpoint() 25 | 26 | val types get() = when (this) { 27 | is Injected -> value.types 28 | is Provided -> listOf(source.returnType) 29 | } 30 | } 31 | 32 | sealed class Injectable { 33 | data class Setter(val cls: ClassDescriptor, val descriptor: FunctionDescriptor): Injectable() 34 | data class Property(val cls: ClassDescriptor, val descriptor: PropertyDescriptor): Injectable() 35 | 36 | val types get() = when(this) { 37 | is Setter -> descriptor.valueParameters.map { it.type } 38 | is Property -> listOf(descriptor.returnType) 39 | } 40 | } 41 | 42 | data class Key( 43 | val type: KotlinType, 44 | val qualifiers: List = emptyList() 45 | ) 46 | 47 | data class Binding( 48 | val key: Key, 49 | val scopes: List, 50 | val bindingType: Variation 51 | ) { 52 | sealed class Variation { 53 | abstract val source: DeclarationDescriptor 54 | 55 | data class Constructor(override val source: ClassConstructorDescriptor): Variation() 56 | data class InstanceFunction(override val source: FunctionDescriptor): Variation() 57 | data class InstanceProperty(override val source: PropertyDescriptor): Variation() 58 | data class StaticFunction(override val source: FunctionDescriptor): Variation() 59 | data class BoundInstance(override val source: DeclarationDescriptor): Variation() 60 | data class Component(override val source: ClassDescriptor) : Variation() 61 | 62 | data class Equality(override val source: FunctionDescriptor): Variation() 63 | data class Recursive(override val source: DeclarationDescriptor, val delegate: Binding): Variation() 64 | 65 | data class Provider(override val source: DeclarationDescriptor, val innerType: KotlinType): Variation() 66 | data class Lazy(override val source: DeclarationDescriptor, val innerType: KotlinType): Variation() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/endpoints/InjectionEndpointResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.endpoints 2 | 3 | import me.shika.di.dagger.resolver.ComponentAnnotationDescriptor 4 | import me.shika.di.dagger.resolver.INJECT_FQ_NAME 5 | import me.shika.di.dagger.resolver.ResolverContext 6 | import me.shika.di.dagger.resolver.allDescriptors 7 | import me.shika.di.dagger.resolver.classDescriptor 8 | import me.shika.di.dagger.resolver.isFromAny 9 | import me.shika.di.dagger.resolver.qualifiers 10 | import me.shika.di.model.Endpoint 11 | import me.shika.di.model.Injectable 12 | import org.jetbrains.kotlin.builtins.KotlinBuiltIns 13 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 14 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 15 | import org.jetbrains.kotlin.descriptors.PropertyDescriptor 16 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion.FUNCTIONS 17 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion.VARIABLES 18 | 19 | class InjectionEndpointResolver( 20 | componentAnnotation: ComponentAnnotationDescriptor, 21 | private val definition: ClassDescriptor, 22 | private val context: ResolverContext 23 | ): EndpointResolver { 24 | // TODO: validate 25 | 26 | override fun invoke(): List { 27 | val componentFunctions = definition.allDescriptors(FUNCTIONS) 28 | .asSequence() 29 | .filterIsInstance() 30 | .filterNot { it.isFromAny() } 31 | 32 | return componentFunctions.filter { it.isInjection() } 33 | .flatMap { func -> 34 | val cls = func.valueParameters.first().type.classDescriptor()!! 35 | val fields = cls.findInjectedFields() 36 | .map { 37 | Endpoint.Injected(func, Injectable.Property(cls, it), it.qualifiers()) 38 | } 39 | 40 | val setters = cls.findInjectedSetters() 41 | .map { 42 | Endpoint.Injected(func, Injectable.Setter(cls, it), it.qualifiers()) 43 | } 44 | 45 | fields + setters 46 | } 47 | .toList() 48 | } 49 | 50 | private fun FunctionDescriptor.isInjection() = 51 | valueParameters.size == 1 && 52 | (KotlinBuiltIns.isUnit(returnType!!) || returnType == valueParameters.first().returnType) 53 | 54 | private fun ClassDescriptor.findInjectedFields() = 55 | allDescriptors(VARIABLES) 56 | .asSequence() 57 | .filterIsInstance() 58 | .filter { 59 | it.annotations.hasAnnotation(INJECT_FQ_NAME) || 60 | it.backingField?.annotations?.hasAnnotation(INJECT_FQ_NAME) == true || 61 | it.setter?.annotations?.hasAnnotation(INJECT_FQ_NAME) == true 62 | } 63 | 64 | private fun ClassDescriptor.findInjectedSetters() = 65 | allDescriptors(FUNCTIONS) 66 | .asSequence() 67 | .filterIsInstance() 68 | .filter { it.annotations.hasAnnotation(INJECT_FQ_NAME) } 69 | } 70 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/endpoints/InjectionEndpointResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.endpoints 2 | 3 | import me.shika.di.dagger.resolver.ComponentAnnotationDescriptor 4 | import me.shika.di.dagger.resolver.INJECT_FQ_NAME 5 | import me.shika.di.dagger.resolver.ResolverContext 6 | import me.shika.di.dagger.resolver.allDescriptors 7 | import me.shika.di.dagger.resolver.classDescriptor 8 | import me.shika.di.dagger.resolver.isFromAny 9 | import me.shika.di.dagger.resolver.qualifiers 10 | import me.shika.di.model.Endpoint 11 | import me.shika.di.model.Injectable 12 | import org.jetbrains.kotlin.builtins.KotlinBuiltIns 13 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 14 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 15 | import org.jetbrains.kotlin.descriptors.PropertyDescriptor 16 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion.FUNCTIONS 17 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion.VARIABLES 18 | 19 | class InjectionEndpointResolver( 20 | componentAnnotation: ComponentAnnotationDescriptor, 21 | private val definition: ClassDescriptor, 22 | private val context: ResolverContext 23 | ): EndpointResolver { 24 | // TODO: validate 25 | 26 | override fun invoke(): List { 27 | val componentFunctions = definition.allDescriptors(FUNCTIONS) 28 | .asSequence() 29 | .filterIsInstance() 30 | .filterNot { it.isFromAny() } 31 | 32 | return componentFunctions.filter { it.isInjection() } 33 | .flatMap { func -> 34 | val cls = func.valueParameters.first().type.classDescriptor()!! 35 | val fields = cls.findInjectedFields() 36 | .map { 37 | Endpoint.Injected(func, Injectable.Property(cls, it), it.qualifiers()) 38 | } 39 | 40 | val setters = cls.findInjectedSetters() 41 | .map { 42 | Endpoint.Injected(func, Injectable.Setter(cls, it), it.qualifiers()) 43 | } 44 | 45 | fields + setters 46 | } 47 | .toList() 48 | } 49 | 50 | private fun FunctionDescriptor.isInjection() = 51 | valueParameters.size == 1 && 52 | (KotlinBuiltIns.isUnit(returnType!!) || returnType == valueParameters.first().returnType) 53 | 54 | private fun ClassDescriptor.findInjectedFields() = 55 | allDescriptors(VARIABLES) 56 | .asSequence() 57 | .filterIsInstance() 58 | .filter { 59 | it.annotations.hasAnnotation(INJECT_FQ_NAME) || 60 | it.backingField?.annotations?.hasAnnotation(INJECT_FQ_NAME) == true || 61 | it.setter?.annotations?.hasAnnotation(INJECT_FQ_NAME) == true 62 | } 63 | 64 | private fun ClassDescriptor.findInjectedSetters() = 65 | allDescriptors(FUNCTIONS) 66 | .asSequence() 67 | .filterIsInstance() 68 | .filter { it.annotations.hasAnnotation(INJECT_FQ_NAME) } 69 | } 70 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/src/main/java/me/shika/di/dagger/renderer/creator/FactoryRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.creator 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.MemberName 5 | import com.squareup.kotlinpoet.TypeName 6 | import com.squareup.kotlinpoet.TypeSpec 7 | import me.shika.di.dagger.renderer.dsl.companionObject 8 | import me.shika.di.dagger.renderer.dsl.function 9 | import me.shika.di.dagger.renderer.dsl.markPrivate 10 | import me.shika.di.dagger.renderer.dsl.nestedClass 11 | import me.shika.di.dagger.renderer.dsl.overrideFunction 12 | import me.shika.di.dagger.renderer.typeName 13 | import me.shika.di.dagger.resolver.creator.FactoryDescriptor 14 | import me.shika.di.model.Key 15 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 16 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 17 | 18 | class FactoryRenderer( 19 | private val componentClassName: ClassName, 20 | private val constructorParams: List, 21 | private val builder: TypeSpec.Builder 22 | ) { 23 | fun render(factoryDescriptor: FactoryDescriptor) { 24 | val method = factoryDescriptor.method ?: return 25 | val factoryClass = factoryDescriptor.method?.containingDeclaration as? ClassDescriptor ?: return 26 | val factoryInterfaceName = factoryClass.typeName() ?: return 27 | 28 | builder.apply { 29 | // factoryClass(factoryInterfaceName, method, isInterface = factoryClass.kind == ClassKind.INTERFACE) 30 | factoryPublicMethod(factoryInterfaceName) 31 | } 32 | } 33 | 34 | private fun TypeSpec.Builder.factoryClass( 35 | factoryClassName: TypeName, 36 | factoryMethod: FunctionDescriptor, 37 | isInterface: Boolean 38 | ) { 39 | nestedClass(FACTORY_IMPL_NAME) { 40 | markPrivate() 41 | if (isInterface) { 42 | addSuperinterface(factoryClassName) 43 | } else { 44 | superclass(factoryClassName) 45 | } 46 | overrideFunction(factoryMethod) { 47 | addCode( 48 | "return %T(${factoryMethod.params()})", 49 | componentClassName 50 | ) 51 | } 52 | } 53 | } 54 | 55 | private fun TypeSpec.Builder.factoryPublicMethod(factoryClassName: TypeName) { 56 | companionObject { 57 | function("factory") { 58 | addAnnotation(JvmStatic::class) 59 | returns(factoryClassName) 60 | addCode("return %M(%T::class.java)", DAGGER_FACTORY_NAME, factoryClassName) 61 | } 62 | } 63 | } 64 | 65 | private fun FunctionDescriptor.params(): String = 66 | constructorParams.mapNotNull { paramKey -> 67 | valueParameters.find { param -> 68 | param.type == paramKey.type && paramKey.qualifiers.all { param.annotations.hasAnnotation(it.fqName!!) } 69 | } 70 | }.joinToString { it.name.asString() } 71 | 72 | companion object { 73 | private const val FACTORY_IMPL_NAME = "Factory" 74 | private val DAGGER_FACTORY_NAME = MemberName("dagger.Dagger", "factory") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/utils.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.Dynamic 5 | import com.squareup.kotlinpoet.LambdaTypeName 6 | import com.squareup.kotlinpoet.ParameterSpec 7 | import com.squareup.kotlinpoet.ParameterizedTypeName 8 | import com.squareup.kotlinpoet.PropertySpec 9 | import com.squareup.kotlinpoet.TypeName 10 | import com.squareup.kotlinpoet.TypeVariableName 11 | import com.squareup.kotlinpoet.WildcardTypeName 12 | import me.shika.di.dagger.resolver.classDescriptor 13 | import me.shika.di.model.Key 14 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 15 | import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe 16 | import org.jetbrains.kotlin.types.KotlinType 17 | 18 | internal fun T?.ifNull(action: () -> T?): T? = 19 | this ?: action() 20 | 21 | internal fun T.letIf(condition: Boolean, block: (T) -> T) = 22 | if (condition) { 23 | block(this) 24 | } else { 25 | this 26 | } 27 | 28 | internal fun PropertySpec.toParameter() = 29 | ParameterSpec.builder(name, type).build() 30 | 31 | internal fun KotlinType.typeName(): TypeName? = classDescriptor()?.fqNameSafe?.let { 32 | val types = arguments.map { it.type.typeName()!! } 33 | val typed = ClassName(it.parent().asString(), it.shortName().asString()) 34 | .let { 35 | if (types.isNotEmpty()) { 36 | with(ParameterizedTypeName) { 37 | it.parameterizedBy(*types.toTypedArray()) 38 | } 39 | } else { 40 | it 41 | } 42 | } 43 | typed.copy(nullable = this.isMarkedNullable) 44 | } 45 | 46 | internal fun ClassDescriptor.typeName(): TypeName? = 47 | if (isCompanionObject) { 48 | (containingDeclaration as ClassDescriptor).defaultType.typeName() 49 | } else { 50 | defaultType.typeName() 51 | } 52 | 53 | internal fun TypeName.asFqString(): String = 54 | when (this) { 55 | is ClassName -> canonicalName.replace(".", "_") 56 | is ParameterizedTypeName -> { 57 | rawType.asFqString() + typeArguments.joinToString(separator = "_", prefix = "_") { it.asFqString() } 58 | } 59 | is WildcardTypeName, 60 | is Dynamic, 61 | is LambdaTypeName, 62 | is TypeVariableName -> TODO() 63 | } + ("_nullable".takeIf { isNullable } ?: "") 64 | 65 | internal fun TypeName.asString(): String = 66 | when (this) { 67 | is ClassName -> simpleName 68 | is ParameterizedTypeName -> rawType.simpleName + typeArguments.joinToString(separator = "_", prefix = "_") { it.asString() } 69 | is WildcardTypeName, 70 | is Dynamic, 71 | is LambdaTypeName, 72 | is TypeVariableName -> TODO() 73 | } + ("_nullable".takeIf { isNullable } ?: "") 74 | 75 | internal fun Key.parameterName(): String { 76 | val type = type.typeName()?.asFqString()?.decapitalize() 77 | val qualifiers = if (qualifiers.isNotEmpty()) { 78 | qualifiers.joinToString(separator = "_", prefix = "_") { 79 | it.type.typeName()?.asFqString().orEmpty().decapitalize() 80 | } 81 | } else { 82 | "" 83 | } 84 | return "$type$qualifiers" 85 | } 86 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/src/main/java/me/shika/di/dagger/renderer/utils.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.Dynamic 5 | import com.squareup.kotlinpoet.LambdaTypeName 6 | import com.squareup.kotlinpoet.ParameterSpec 7 | import com.squareup.kotlinpoet.ParameterizedTypeName 8 | import com.squareup.kotlinpoet.PropertySpec 9 | import com.squareup.kotlinpoet.TypeName 10 | import com.squareup.kotlinpoet.TypeVariableName 11 | import com.squareup.kotlinpoet.WildcardTypeName 12 | import me.shika.di.dagger.resolver.classDescriptor 13 | import me.shika.di.model.Key 14 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 15 | import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe 16 | import org.jetbrains.kotlin.types.KotlinType 17 | 18 | internal fun T?.ifNull(action: () -> T?): T? = 19 | this ?: action() 20 | 21 | internal fun T.letIf(condition: Boolean, block: (T) -> T) = 22 | if (condition) { 23 | block(this) 24 | } else { 25 | this 26 | } 27 | 28 | internal fun PropertySpec.toParameter() = 29 | ParameterSpec.builder(name, type).build() 30 | 31 | internal fun KotlinType.typeName(): TypeName? = classDescriptor()?.fqNameSafe?.let { 32 | val types = arguments.map { it.type.typeName()!! } 33 | val typed = ClassName(it.parent().asString(), it.shortName().asString()) 34 | .let { 35 | if (types.isNotEmpty()) { 36 | with(ParameterizedTypeName) { 37 | it.parameterizedBy(*types.toTypedArray()) 38 | } 39 | } else { 40 | it 41 | } 42 | } 43 | typed.copy(nullable = this.isMarkedNullable) 44 | } 45 | 46 | internal fun ClassDescriptor.typeName(): TypeName? = 47 | if (isCompanionObject) { 48 | (containingDeclaration as ClassDescriptor).defaultType.typeName() 49 | } else { 50 | defaultType.typeName() 51 | } 52 | 53 | internal fun TypeName.asFqString(): String = 54 | when (this) { 55 | is ClassName -> canonicalName.replace(".", "_") 56 | is ParameterizedTypeName -> { 57 | rawType.asFqString() + typeArguments.joinToString(separator = "_", prefix = "_") { it.asFqString() } 58 | } 59 | is WildcardTypeName, 60 | is Dynamic, 61 | is LambdaTypeName, 62 | is TypeVariableName -> TODO() 63 | } + ("_nullable".takeIf { isNullable } ?: "") 64 | 65 | internal fun TypeName.asString(): String = 66 | when (this) { 67 | is ClassName -> simpleName 68 | is ParameterizedTypeName -> rawType.simpleName + typeArguments.joinToString(separator = "_", prefix = "_") { it.asString() } 69 | is WildcardTypeName, 70 | is Dynamic, 71 | is LambdaTypeName, 72 | is TypeVariableName -> TODO() 73 | } + ("_nullable".takeIf { isNullable } ?: "") 74 | 75 | internal fun Key.parameterName(): String { 76 | val type = type.typeName()?.asFqString()?.decapitalize() 77 | val qualifiers = if (qualifiers.isNotEmpty()) { 78 | qualifiers.joinToString(separator = "_", prefix = "_") { 79 | it.type.typeName()?.asFqString().orEmpty().decapitalize() 80 | } 81 | } else { 82 | "" 83 | } 84 | return "$type$qualifiers" 85 | } 86 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/DiCompilerAnalysisExtension.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di 2 | 3 | import me.shika.di.dagger.renderer.DaggerComponentRenderer 4 | import me.shika.di.dagger.resolver.DaggerComponentDescriptor 5 | import me.shika.di.dagger.resolver.ResolverContext 6 | import me.shika.di.dagger.resolver.isComponent 7 | import org.jetbrains.kotlin.analyzer.AnalysisResult 8 | import org.jetbrains.kotlin.com.intellij.openapi.project.Project 9 | import org.jetbrains.kotlin.container.ComponentProvider 10 | import org.jetbrains.kotlin.context.ProjectContext 11 | import org.jetbrains.kotlin.descriptors.ModuleDescriptor 12 | import org.jetbrains.kotlin.psi.KtFile 13 | import org.jetbrains.kotlin.psi.classRecursiveVisitor 14 | import org.jetbrains.kotlin.resolve.BindingContext.CLASS 15 | import org.jetbrains.kotlin.resolve.BindingTrace 16 | import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension 17 | import java.io.File 18 | 19 | class DiCompilerAnalysisExtension( 20 | private val sourcesDir: File 21 | ) : AnalysisHandlerExtension { 22 | private var generatedFiles = false 23 | 24 | override fun doAnalysis( 25 | project: Project, 26 | module: ModuleDescriptor, 27 | projectContext: ProjectContext, 28 | files: Collection, 29 | bindingTrace: BindingTrace, 30 | componentProvider: ComponentProvider 31 | ): AnalysisResult? = null 32 | 33 | override fun analysisCompleted( 34 | project: Project, 35 | module: ModuleDescriptor, 36 | bindingTrace: BindingTrace, 37 | files: Collection 38 | ): AnalysisResult? { 39 | if (generatedFiles) return null 40 | 41 | val initialDiagnosticCount = bindingTrace.bindingContext.diagnostics.all().size 42 | val resolverContext = ResolverContext(module, bindingTrace) 43 | 44 | files.forEach { file -> 45 | val diagnosticCount = bindingTrace.bindingContext.diagnostics.all().size 46 | 47 | file.accept( 48 | classRecursiveVisitor { ktClass -> 49 | val classDescriptor = bindingTrace[CLASS, ktClass] 50 | if (classDescriptor?.isComponent() == true) { 51 | val component = DaggerComponentDescriptor( 52 | classDescriptor, 53 | file, 54 | resolverContext 55 | ) 56 | 57 | if (bindingTrace.bindingContext.diagnostics.all().size != diagnosticCount) { 58 | // No need to render, something is wrong with component 59 | return@classRecursiveVisitor 60 | } 61 | 62 | val renderer = DaggerComponentRenderer(component) 63 | renderer.render(sourcesDir) 64 | } 65 | } 66 | ) 67 | } 68 | 69 | generatedFiles = true 70 | return if (bindingTrace.bindingContext.diagnostics.all().size == initialDiagnosticCount) { 71 | AnalysisResult.RetryWithAdditionalRoots( 72 | bindingContext = bindingTrace.bindingContext, 73 | moduleDescriptor = module, 74 | additionalJavaRoots = emptyList(), 75 | additionalKotlinRoots = listOf(sourcesDir) 76 | ) 77 | } else { 78 | AnalysisResult.compilationError(bindingTrace.bindingContext) 79 | } 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /testing/integration/src/test/kotlin/me/shika/di/factory/FactoryBindsInstanceTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Dagger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package me.shika.di.factory 17 | 18 | import com.google.common.truth.Truth.assertThat 19 | import dagger.BindsInstance 20 | import dagger.Component 21 | import org.junit.Assert.fail 22 | import org.junit.Test 23 | import org.junit.runner.RunWith 24 | import org.junit.runners.JUnit4 25 | import java.lang.annotation.Retention 26 | import java.lang.annotation.RetentionPolicy 27 | 28 | /** Tests for component factories with [BindsInstance] parameters. */ 29 | @RunWith(JUnit4::class) 30 | class FactoryBindsInstanceTest { 31 | @Component 32 | internal interface BindsInstanceComponent { 33 | fun string(): String? 34 | @Component.Factory 35 | interface Factory { 36 | fun create(@BindsInstance string: String?): BindsInstanceComponent? 37 | } 38 | } 39 | 40 | @Test 41 | fun bindsInstance() { 42 | val component: BindsInstanceComponent = 43 | DaggerFactoryBindsInstanceTest_BindsInstanceComponent.factory().create("baz") 44 | assertThat(component.string()).isEqualTo("baz") 45 | } 46 | 47 | @Test 48 | fun nonNullableBindsInstance_failsOnNull() { 49 | try { 50 | DaggerFactoryBindsInstanceTest_BindsInstanceComponent.factory().create(null) 51 | fail() 52 | } catch (expected: NullPointerException) { 53 | } 54 | } 55 | 56 | @Target( 57 | AnnotationTarget.FUNCTION, 58 | AnnotationTarget.PROPERTY_GETTER, 59 | AnnotationTarget.PROPERTY_SETTER, 60 | AnnotationTarget.VALUE_PARAMETER 61 | ) 62 | @Retention(RetentionPolicy.RUNTIME) 63 | internal annotation class Nullable 64 | 65 | @Component 66 | internal interface NullableBindsInstanceComponent { 67 | @Nullable 68 | fun string(): String? 69 | 70 | @Component.Factory 71 | interface Factory { 72 | fun create(@BindsInstance @Nullable string: String?): NullableBindsInstanceComponent? 73 | } 74 | } 75 | 76 | @Test 77 | fun nullableBindsInstance_doesNotFailOnNull() { 78 | val component: NullableBindsInstanceComponent = 79 | DaggerFactoryBindsInstanceTest_NullableBindsInstanceComponent.factory().create(null) 80 | assertThat(component.string()).isEqualTo(null) 81 | } 82 | 83 | @Component 84 | internal interface PrimitiveBindsInstanceComponent { 85 | val int: Int 86 | 87 | @Component.Factory 88 | interface Factory { 89 | fun create(@BindsInstance i: Int): PrimitiveBindsInstanceComponent? 90 | } 91 | } 92 | 93 | @Test 94 | fun primitiveBindsInstance() { 95 | val component: PrimitiveBindsInstanceComponent = 96 | DaggerFactoryBindsInstanceTest_PrimitiveBindsInstanceComponent.factory().create(1) 97 | assertThat(component.int).isEqualTo(1) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /testing/integration/src/test/kotlin/me/shika/di/factory/SubcomponentFactoryTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Dagger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package me.shika.di.factory 17 | 18 | import com.google.common.truth.Truth.assertThat 19 | import dagger.BindsInstance 20 | import dagger.Component 21 | import dagger.Provides 22 | import dagger.Subcomponent 23 | import org.junit.Test 24 | import org.junit.runner.RunWith 25 | import org.junit.runners.JUnit4 26 | import javax.inject.Inject 27 | 28 | /** 29 | * Tests for [subcomponent factories][Subcomponent.Factory]. 30 | * 31 | * 32 | * Most things are tested in `FactoryTest`; this is just intended to test some things like 33 | * injecting subcomponent factories and returning them from component methods. 34 | */ 35 | @RunWith(JUnit4::class) 36 | class SubcomponentFactoryTest { 37 | @Component 38 | internal interface ParentWithSubcomponentFactory { 39 | fun subcomponentFactory(): Sub.Factory 40 | @Component.Factory 41 | interface Factory { 42 | fun create(@BindsInstance i: Int): ParentWithSubcomponentFactory? 43 | } 44 | } 45 | 46 | @Subcomponent 47 | internal interface Sub { 48 | fun i(): Int 49 | fun s(): String? 50 | @Subcomponent.Factory 51 | interface Factory { 52 | fun create(@BindsInstance s: String?): Sub 53 | } 54 | } 55 | 56 | @Test 57 | fun parentComponentWithSubcomponentFactoryEntryPoint() { 58 | val parent: ParentWithSubcomponentFactory = 59 | DaggerSubcomponentFactoryTest_ParentWithSubcomponentFactory.factory().create(3) 60 | val subcomponent = parent.subcomponentFactory().create("foo") 61 | assertThat(subcomponent.i()).isEqualTo(3) 62 | assertThat(subcomponent.s()).isEqualTo("foo") 63 | } 64 | 65 | internal object ModuleWithSubcomponent { 66 | @Provides 67 | fun provideInt(): Int { 68 | return 42 69 | } 70 | } 71 | 72 | internal class UsesSubcomponentFactory @Inject constructor(private val subFactory: Sub.Factory) { 73 | fun getSubcomponent(s: String?): Sub { 74 | return subFactory.create(s) 75 | } 76 | 77 | } 78 | 79 | @Component(modules = [ModuleWithSubcomponent::class]) 80 | internal interface ParentWithModuleWithSubcomponent { 81 | fun usesSubcomponentFactory(): UsesSubcomponentFactory 82 | } 83 | 84 | @Test 85 | fun parentComponentWithModuleWithSubcomponent() { 86 | val parent: ParentWithModuleWithSubcomponent = 87 | DaggerSubcomponentFactoryTest_ParentWithModuleWithSubcomponent.create() 88 | val usesSubcomponentFactory = 89 | parent.usesSubcomponentFactory() 90 | val subcomponent1 = 91 | usesSubcomponentFactory.getSubcomponent("foo") 92 | assertThat(subcomponent1.i()).isEqualTo(42) 93 | assertThat(subcomponent1.s()).isEqualTo("foo") 94 | val subcomponent2 = 95 | usesSubcomponentFactory.getSubcomponent("bar") 96 | assertThat(subcomponent2.i()).isEqualTo(42) 97 | assertThat(subcomponent2.s()).isEqualTo("bar") 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/creator/BuilderRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.creator 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.KModifier.LATEINIT 5 | import com.squareup.kotlinpoet.KModifier.PRIVATE 6 | import com.squareup.kotlinpoet.PropertySpec 7 | import com.squareup.kotlinpoet.TypeName 8 | import com.squareup.kotlinpoet.TypeSpec 9 | import me.shika.di.dagger.renderer.dsl.companionObject 10 | import me.shika.di.dagger.renderer.dsl.function 11 | import me.shika.di.dagger.renderer.dsl.nestedClass 12 | import me.shika.di.dagger.renderer.dsl.overrideFunction 13 | import me.shika.di.dagger.renderer.parameterName 14 | import me.shika.di.dagger.renderer.typeName 15 | import me.shika.di.dagger.resolver.creator.BuilderDescriptor 16 | import me.shika.di.dagger.resolver.qualifiers 17 | import me.shika.di.model.Key 18 | import org.jetbrains.kotlin.builtins.KotlinBuiltIns 19 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 20 | import org.jetbrains.kotlin.descriptors.ClassKind 21 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 22 | 23 | class BuilderRenderer( 24 | private val componentClassName: ClassName, 25 | private val constructorParams: List, 26 | private val builder: TypeSpec.Builder 27 | ) { 28 | fun render(builderDescriptor: BuilderDescriptor) { 29 | val builderClass = builderDescriptor.buildMethod?.containingDeclaration as? ClassDescriptor ?: return 30 | val builderClassName = builderClass.typeName()!! 31 | builder.apply { 32 | builderClass( 33 | builderClassName, 34 | builderDescriptor.setters, 35 | builderDescriptor.buildMethod, 36 | isInterface = builderClass.kind == ClassKind.INTERFACE 37 | ) 38 | builderPublicMethod(builderClassName) 39 | } 40 | } 41 | 42 | private fun TypeSpec.Builder.builderClass( 43 | builderClassName: TypeName, 44 | setters: List, 45 | buildMethod: FunctionDescriptor?, 46 | isInterface: Boolean 47 | ) { 48 | nestedClass(BUILDER_IMPL_NAME) { 49 | addModifiers(PRIVATE) 50 | if (isInterface) { 51 | addSuperinterface(builderClassName) 52 | } else { 53 | superclass(builderClassName) 54 | } 55 | 56 | val paramToProperty = constructorParams.associateWith { 57 | PropertySpec.builder(it.parameterName(), it.type.typeName()!!, PRIVATE, LATEINIT) 58 | .mutable(true) 59 | .build() 60 | } 61 | addProperties(paramToProperty.values) 62 | 63 | setters.forEach { 64 | overrideFunction(it) { 65 | val param = it.valueParameters.first() 66 | val qualifiers = it.qualifiers() 67 | val property = paramToProperty[Key(param.type, qualifiers)] 68 | addCode("this.%N = ${param.name.asString()}\n", property) 69 | 70 | if (!KotlinBuiltIns.isUnit(it.returnType!!)) { 71 | addCode("return this") 72 | } 73 | } 74 | } 75 | 76 | overrideFunction(buildMethod!!) { 77 | val params = constructorParams.joinToString { paramToProperty[it]!!.name } 78 | addCode( 79 | "return %T(${params})", 80 | componentClassName 81 | ) 82 | } 83 | } 84 | } 85 | 86 | private fun TypeSpec.Builder.builderPublicMethod(builderClassName: TypeName) { 87 | companionObject { 88 | function("builder") { 89 | returns(builderClassName) 90 | addCode("return ${BUILDER_IMPL_NAME}()") 91 | } 92 | } 93 | } 94 | 95 | companion object { 96 | private const val BUILDER_IMPL_NAME = "Builder" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/utils.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver 2 | 3 | import me.shika.di.DaggerErrorMessages 4 | import org.jetbrains.kotlin.builtins.KotlinBuiltIns 5 | import org.jetbrains.kotlin.com.intellij.psi.PsiElement 6 | import org.jetbrains.kotlin.contracts.parsing.isEqualsDescriptor 7 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 8 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 9 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 10 | import org.jetbrains.kotlin.descriptors.Modality 11 | import org.jetbrains.kotlin.descriptors.annotations.Annotated 12 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 13 | import org.jetbrains.kotlin.diagnostics.Diagnostic 14 | import org.jetbrains.kotlin.diagnostics.reportFromPlugin 15 | import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi 16 | import org.jetbrains.kotlin.name.FqName 17 | import org.jetbrains.kotlin.name.Name 18 | import org.jetbrains.kotlin.resolve.BindingTrace 19 | import org.jetbrains.kotlin.resolve.DescriptorUtils 20 | import org.jetbrains.kotlin.resolve.annotations.argumentValue 21 | import org.jetbrains.kotlin.resolve.constants.KClassValue 22 | import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass 23 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter 24 | import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered 25 | import org.jetbrains.kotlin.types.KotlinType 26 | import org.jetbrains.kotlin.types.checker.KotlinTypeChecker 27 | import org.jetbrains.kotlin.types.typeUtil.isInt 28 | import org.jetbrains.kotlin.types.typeUtil.makeNotNullable 29 | 30 | internal fun KotlinType.classDescriptor() = constructor.declarationDescriptor as? ClassDescriptor 31 | 32 | internal fun DeclarationDescriptor.scopeAnnotations(): List = 33 | annotations.filter { 34 | it.annotationClass?.annotations?.hasAnnotation(SCOPE_FQ_NAME) == true 35 | && it.fqName != REUSABLE_FQ_NAME 36 | } 37 | 38 | internal fun Annotated.qualifiers(): List = 39 | annotations.filter { 40 | it.annotationClass?.annotations?.hasAnnotation(QUALIFIER_FQ_NAME) == true 41 | } 42 | 43 | internal val SCOPE_FQ_NAME = FqName("javax.inject.Scope") 44 | internal val QUALIFIER_FQ_NAME = FqName("javax.inject.Qualifier") 45 | internal val REUSABLE_FQ_NAME = FqName("dagger.Reusable") 46 | internal val INJECT_FQ_NAME = FqName("javax.inject.Inject") 47 | internal val DAGGER_BINDS_INSTANCE_FQ_NAME = FqName("dagger.BindsInstance") 48 | 49 | internal fun ClassDescriptor.allDescriptors(kindFilter: DescriptorKindFilter) = 50 | unsubstitutedMemberScope.getDescriptorsFiltered(kindFilter) { true } 51 | 52 | private fun DeclarationDescriptor.isHashCodeDescriptor() = 53 | this is FunctionDescriptor && name == Name.identifier("hashCode") 54 | && returnType?.isInt() == true && valueParameters.isEmpty() 55 | 56 | private fun DeclarationDescriptor.isToStringDescriptor() = 57 | this is FunctionDescriptor && name == Name.identifier("toString") 58 | && KotlinBuiltIns.isString(returnType) && valueParameters.isEmpty() 59 | 60 | internal fun FunctionDescriptor.isFromAny() = 61 | isEqualsDescriptor() || isHashCodeDescriptor() || isToStringDescriptor() 62 | 63 | 64 | internal fun DeclarationDescriptor.report(trace: BindingTrace, diagnostic: (PsiElement) -> Diagnostic) = 65 | findPsi()?.let { trace.reportFromPlugin(diagnostic(it), DaggerErrorMessages) } 66 | 67 | internal fun AnnotationDescriptor.classListValue(context: ResolverContext, name: String) = 68 | (argumentValue(name)?.value as? List) 69 | ?.mapNotNull { 70 | it.getArgumentType(context.module).classDescriptor() 71 | } 72 | ?: emptyList() 73 | 74 | internal fun ClassDescriptor.isInstance() = 75 | !DescriptorUtils.isObject(this) && modality != Modality.ABSTRACT 76 | 77 | internal infix fun KotlinType.applicableTo(type: KotlinType) = 78 | KotlinTypeChecker.DEFAULT.equalTypes(this, type) 79 | || (type.isMarkedNullable && KotlinTypeChecker.DEFAULT.equalTypes(this, type.makeNotNullable())) 80 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/utils.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver 2 | 3 | import me.shika.di.DaggerErrorMessages 4 | import org.jetbrains.kotlin.builtins.KotlinBuiltIns 5 | import org.jetbrains.kotlin.com.intellij.psi.PsiElement 6 | import org.jetbrains.kotlin.contracts.parsing.isEqualsDescriptor 7 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 8 | import org.jetbrains.kotlin.descriptors.DeclarationDescriptor 9 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 10 | import org.jetbrains.kotlin.descriptors.Modality 11 | import org.jetbrains.kotlin.descriptors.annotations.Annotated 12 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 13 | import org.jetbrains.kotlin.diagnostics.Diagnostic 14 | import org.jetbrains.kotlin.diagnostics.reportFromPlugin 15 | import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi 16 | import org.jetbrains.kotlin.name.FqName 17 | import org.jetbrains.kotlin.name.Name 18 | import org.jetbrains.kotlin.resolve.BindingTrace 19 | import org.jetbrains.kotlin.resolve.DescriptorUtils 20 | import org.jetbrains.kotlin.resolve.annotations.argumentValue 21 | import org.jetbrains.kotlin.resolve.constants.KClassValue 22 | import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass 23 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter 24 | import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered 25 | import org.jetbrains.kotlin.types.KotlinType 26 | import org.jetbrains.kotlin.types.checker.KotlinTypeChecker 27 | import org.jetbrains.kotlin.types.typeUtil.isInt 28 | import org.jetbrains.kotlin.types.typeUtil.makeNotNullable 29 | 30 | fun KotlinType.classDescriptor() = constructor.declarationDescriptor as? ClassDescriptor 31 | 32 | fun DeclarationDescriptor.scopeAnnotations(): List = 33 | annotations.filter { 34 | it.annotationClass?.annotations?.hasAnnotation(SCOPE_FQ_NAME) == true 35 | && it.fqName != REUSABLE_FQ_NAME 36 | } 37 | 38 | fun Annotated.qualifiers(): List = 39 | annotations.filter { 40 | it.annotationClass?.annotations?.hasAnnotation(QUALIFIER_FQ_NAME) == true 41 | } 42 | 43 | //TODO: replace with actual class refs 44 | internal val SCOPE_FQ_NAME = FqName("javax.inject.Scope") 45 | internal val QUALIFIER_FQ_NAME = FqName("javax.inject.Qualifier") 46 | internal val REUSABLE_FQ_NAME = FqName("dagger.Reusable") 47 | internal val INJECT_FQ_NAME = FqName("javax.inject.Inject") 48 | internal val DAGGER_BINDS_INSTANCE_FQ_NAME = FqName("dagger.BindsInstance") 49 | 50 | internal fun ClassDescriptor.allDescriptors(kindFilter: DescriptorKindFilter) = 51 | unsubstitutedMemberScope.getDescriptorsFiltered(kindFilter) { true } 52 | 53 | private fun DeclarationDescriptor.isHashCodeDescriptor() = 54 | this is FunctionDescriptor && name == Name.identifier("hashCode") 55 | && returnType?.isInt() == true && valueParameters.isEmpty() 56 | 57 | private fun DeclarationDescriptor.isToStringDescriptor() = 58 | this is FunctionDescriptor && name == Name.identifier("toString") 59 | && KotlinBuiltIns.isString(returnType) && valueParameters.isEmpty() 60 | 61 | internal fun FunctionDescriptor.isFromAny() = 62 | isEqualsDescriptor() || isHashCodeDescriptor() || isToStringDescriptor() 63 | 64 | 65 | internal fun DeclarationDescriptor.report(trace: BindingTrace, diagnostic: (PsiElement) -> Diagnostic) = 66 | findPsi()?.let { trace.reportFromPlugin(diagnostic(it), DaggerErrorMessages) } 67 | 68 | internal fun AnnotationDescriptor.classListValue(context: ResolverContext, name: String) = 69 | (argumentValue(name)?.value as? List) 70 | ?.mapNotNull { 71 | it.getArgumentType(context.module).classDescriptor() 72 | } 73 | ?: emptyList() 74 | 75 | internal fun ClassDescriptor.isInstance() = 76 | (!DescriptorUtils.isObject(this) && modality != Modality.ABSTRACT) 77 | 78 | internal infix fun KotlinType.applicableTo(type: KotlinType) = 79 | KotlinTypeChecker.DEFAULT.equalTypes(this, type) 80 | || (type.isMarkedNullable && KotlinTypeChecker.DEFAULT.equalTypes(this, type.makeNotNullable())) 81 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/creator/FactoryDescriptor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.creator 2 | 3 | import me.shika.di.FACTORY_DEPENDENCIES_NOT_DECLARED 4 | import me.shika.di.FACTORY_DEPENDENCIES_NOT_PROVIDED 5 | import me.shika.di.FACTORY_MODULE_NOT_PROVIDED 6 | import me.shika.di.FACTORY_WRONG_METHOD 7 | import me.shika.di.dagger.resolver.ComponentAnnotationDescriptor 8 | import me.shika.di.dagger.resolver.DAGGER_BINDS_INSTANCE_FQ_NAME 9 | import me.shika.di.dagger.resolver.ResolverContext 10 | import me.shika.di.dagger.resolver.allDescriptors 11 | import me.shika.di.dagger.resolver.report 12 | import me.shika.di.model.Binding 13 | import org.jetbrains.kotlin.com.intellij.psi.PsiElement 14 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 15 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 16 | import org.jetbrains.kotlin.descriptors.Modality 17 | import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor 18 | import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1 19 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion.FUNCTIONS 20 | import org.jetbrains.kotlin.types.KotlinType 21 | import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf 22 | 23 | class FactoryDescriptor( 24 | private val component: ClassDescriptor, 25 | private val definition: ClassDescriptor, 26 | private val componentAnnotation: ComponentAnnotationDescriptor, 27 | private val context: ResolverContext 28 | ) : CreatorDescriptor { 29 | var method: FunctionDescriptor? = null 30 | private set 31 | 32 | override var instances: List = emptyList() 33 | private set 34 | 35 | init { 36 | parseDefinition() 37 | } 38 | 39 | private fun parseDefinition() { 40 | val factoryMethods = definition.allDescriptors(FUNCTIONS) 41 | .filterIsInstance() 42 | .filter { it.modality == Modality.ABSTRACT } 43 | 44 | if (factoryMethods.size != 1 || factoryMethods.none { component.defaultType.isSubtypeOf(it.returnType!!) }) { 45 | definition.report(context.trace) { FACTORY_WRONG_METHOD.on(it, factoryMethods) } 46 | return 47 | } 48 | 49 | method = factoryMethods.first() 50 | 51 | val instanceParams = method!!.valueParameters.filter { it.annotations.hasAnnotation(DAGGER_BINDS_INSTANCE_FQ_NAME) } 52 | val dependencyParams = method!!.valueParameters.asSequence().filterNot { it in instanceParams } 53 | checkFactoryParams(dependencyParams) 54 | 55 | instances = instanceParams.map { it.toInstanceBinding() } 56 | } 57 | 58 | private fun checkFactoryParams(dependencyParams: Sequence) { 59 | val declaredDependencies = componentAnnotation.dependencies 60 | val declaredModuleInstances = componentAnnotation.moduleInstances 61 | 62 | val notProvidedDependencies = declaredDependencies.filterNot { it.defaultType in dependencyParams.map { it.type } } 63 | reportOnMethod(notProvidedDependencies.map { it.defaultType }, FACTORY_DEPENDENCIES_NOT_PROVIDED) 64 | 65 | val notProvidedModules = declaredModuleInstances.filterNot { 66 | it.defaultType in dependencyParams.map { it.type } || it.constructors.any { it.valueParameters.isEmpty() } 67 | } 68 | reportOnMethod(notProvidedModules.map { it.defaultType }, FACTORY_MODULE_NOT_PROVIDED) 69 | 70 | val notDeclared = dependencyParams.filterNot { param -> 71 | declaredDependencies.any { param.type == it.defaultType } || declaredModuleInstances.any { param.type == it.defaultType } 72 | } 73 | reportNotDeclared(notDeclared.toList()) 74 | } 75 | 76 | private fun reportNotDeclared(list: List) { 77 | list.forEach { 78 | it.report(context.trace) { FACTORY_DEPENDENCIES_NOT_DECLARED.on(it) } 79 | } 80 | } 81 | 82 | private fun reportOnMethod(list: List, diagnosticFactory: DiagnosticFactory1) { 83 | list.forEach { type -> 84 | method?.report(context.trace) { diagnosticFactory.on(it, type) } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/creator/FactoryDescriptor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.creator 2 | 3 | import me.shika.di.FACTORY_DEPENDENCIES_NOT_DECLARED 4 | import me.shika.di.FACTORY_DEPENDENCIES_NOT_PROVIDED 5 | import me.shika.di.FACTORY_MODULE_NOT_PROVIDED 6 | import me.shika.di.FACTORY_WRONG_METHOD 7 | import me.shika.di.dagger.resolver.ComponentAnnotationDescriptor 8 | import me.shika.di.dagger.resolver.DAGGER_BINDS_INSTANCE_FQ_NAME 9 | import me.shika.di.dagger.resolver.ResolverContext 10 | import me.shika.di.dagger.resolver.allDescriptors 11 | import me.shika.di.dagger.resolver.report 12 | import me.shika.di.model.Binding 13 | import org.jetbrains.kotlin.com.intellij.psi.PsiElement 14 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 15 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 16 | import org.jetbrains.kotlin.descriptors.Modality 17 | import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor 18 | import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1 19 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion.FUNCTIONS 20 | import org.jetbrains.kotlin.types.KotlinType 21 | import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf 22 | import org.jetbrains.kotlin.types.typeUtil.makeNotNullable 23 | 24 | class FactoryDescriptor( 25 | private val component: ClassDescriptor, 26 | private val definition: ClassDescriptor, 27 | private val componentAnnotation: ComponentAnnotationDescriptor, 28 | private val context: ResolverContext 29 | ) : CreatorDescriptor { 30 | var method: FunctionDescriptor? = null 31 | private set 32 | 33 | override var instances: List = emptyList() 34 | private set 35 | 36 | init { 37 | parseDefinition() 38 | } 39 | 40 | private fun parseDefinition() { 41 | val factoryMethods = definition.allDescriptors(FUNCTIONS) 42 | .filterIsInstance() 43 | .filter { it.modality == Modality.ABSTRACT } 44 | 45 | if (factoryMethods.size != 1 || factoryMethods.none { component.defaultType.makeNotNullable().isSubtypeOf(it.returnType!!) }) { 46 | definition.report(context.trace) { FACTORY_WRONG_METHOD.on(it, factoryMethods) } 47 | return 48 | } 49 | 50 | method = factoryMethods.first() 51 | 52 | val instanceParams = method!!.valueParameters.filter { it.annotations.hasAnnotation(DAGGER_BINDS_INSTANCE_FQ_NAME) } 53 | val dependencyParams = method!!.valueParameters.asSequence().filterNot { it in instanceParams } 54 | checkFactoryParams(dependencyParams) 55 | 56 | instances = instanceParams.map { it.toInstanceBinding() } 57 | } 58 | 59 | private fun checkFactoryParams(dependencyParams: Sequence) { 60 | val declaredDependencies = componentAnnotation.dependencies 61 | val declaredModuleInstances = componentAnnotation.moduleInstances 62 | 63 | val notProvidedDependencies = declaredDependencies.filterNot { it.defaultType in dependencyParams.map { it.type.makeNotNullable() } } 64 | reportOnMethod(notProvidedDependencies.map { it.defaultType }, FACTORY_DEPENDENCIES_NOT_PROVIDED) 65 | 66 | val notProvidedModules = declaredModuleInstances.filterNot { it.defaultType in dependencyParams.map { it.type.makeNotNullable() } } 67 | reportOnMethod(notProvidedModules.map { it.defaultType }, FACTORY_MODULE_NOT_PROVIDED) 68 | 69 | val notDeclared = dependencyParams.filterNot { param -> 70 | declaredDependencies.any { param.type.makeNotNullable() == it.defaultType } || declaredModuleInstances.any { param.type.makeNotNullable() == it.defaultType } 71 | } 72 | reportNotDeclared(notDeclared.toList()) 73 | } 74 | 75 | private fun reportNotDeclared(list: List) { 76 | list.forEach { 77 | it.report(context.trace) { FACTORY_DEPENDENCIES_NOT_DECLARED.on(it) } 78 | } 79 | } 80 | 81 | private fun reportOnMethod(list: List, diagnosticFactory: DiagnosticFactory1) { 82 | list.forEach { type -> 83 | method?.report(context.trace) { diagnosticFactory.on(it, type) } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/src/main/java/me/shika/di/dagger/renderer/creator/BuilderRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.creator 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.KModifier.LATEINIT 5 | import com.squareup.kotlinpoet.KModifier.PRIVATE 6 | import com.squareup.kotlinpoet.MemberName 7 | import com.squareup.kotlinpoet.PropertySpec 8 | import com.squareup.kotlinpoet.TypeName 9 | import com.squareup.kotlinpoet.TypeSpec 10 | import me.shika.di.dagger.renderer.dsl.companionObject 11 | import me.shika.di.dagger.renderer.dsl.function 12 | import me.shika.di.dagger.renderer.dsl.nestedClass 13 | import me.shika.di.dagger.renderer.dsl.overrideFunction 14 | import me.shika.di.dagger.renderer.parameterName 15 | import me.shika.di.dagger.renderer.typeName 16 | import me.shika.di.dagger.resolver.creator.BuilderDescriptor 17 | import me.shika.di.dagger.resolver.qualifiers 18 | import me.shika.di.model.Key 19 | import org.jetbrains.kotlin.builtins.KotlinBuiltIns 20 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 21 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 22 | 23 | class BuilderRenderer( 24 | private val componentClassName: ClassName, 25 | private val constructorParams: List, 26 | private val builder: TypeSpec.Builder 27 | ) { 28 | fun render(builderDescriptor: BuilderDescriptor) { 29 | val builderClass = builderDescriptor.buildMethod?.containingDeclaration as? ClassDescriptor ?: return 30 | val builderClassName = builderClass.typeName()!! 31 | builder.apply { 32 | // builderClass( 33 | // builderClassName, 34 | // builderDescriptor.setters, 35 | // builderDescriptor.buildMethod, 36 | // isInterface = builderClass.kind == ClassKind.INTERFACE 37 | // ) 38 | builderPublicMethod(builderClassName) 39 | } 40 | } 41 | 42 | private fun TypeSpec.Builder.builderClass( 43 | builderClassName: TypeName, 44 | setters: List, 45 | buildMethod: FunctionDescriptor?, 46 | isInterface: Boolean 47 | ) { 48 | nestedClass(BUILDER_IMPL_NAME) { 49 | addModifiers(PRIVATE) 50 | if (isInterface) { 51 | addSuperinterface(builderClassName) 52 | } else { 53 | superclass(builderClassName) 54 | } 55 | 56 | val paramToProperty = constructorParams.associateWith { 57 | PropertySpec.builder(it.parameterName(), it.type.typeName()!!, PRIVATE, LATEINIT) 58 | .mutable(true) 59 | .build() 60 | } 61 | addProperties(paramToProperty.values) 62 | 63 | setters.forEach { 64 | overrideFunction(it) { 65 | val param = it.valueParameters.first() 66 | val qualifiers = it.qualifiers() 67 | val property = paramToProperty[Key(param.type, qualifiers)] 68 | addCode("this.%N = ${param.name.asString()}\n", property) 69 | 70 | if (!KotlinBuiltIns.isUnit(it.returnType!!)) { 71 | addCode("return this") 72 | } 73 | } 74 | } 75 | 76 | overrideFunction(buildMethod!!) { 77 | val params = constructorParams.joinToString { paramToProperty[it]!!.name } 78 | addCode( 79 | "return %T(${params})", 80 | componentClassName 81 | ) 82 | } 83 | } 84 | } 85 | 86 | private fun TypeSpec.Builder.builderPublicMethod(builderClassName: TypeName) { 87 | companionObject { 88 | function("builder") { 89 | addAnnotation(JvmStatic::class) 90 | returns(builderClassName) 91 | addCode("return %M(%T::class.java)", DAGGER_BUILDER_NAME, builderClassName) 92 | } 93 | } 94 | } 95 | 96 | companion object { 97 | private const val BUILDER_IMPL_NAME = "Builder" 98 | private val DAGGER_BUILDER_NAME = MemberName("dagger.Dagger", "builder") 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/creator/BuilderDescriptor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.creator 2 | 3 | import me.shika.di.BUILDER_DEPENDENCIES_NOT_PROVIDED 4 | import me.shika.di.BUILDER_MODULE_NOT_PROVIDED 5 | import me.shika.di.BUILDER_SETTER_TOO_MANY_PARAMS 6 | import me.shika.di.BUILDER_SETTER_WRONG_RETURN_TYPE 7 | import me.shika.di.BUILDER_WRONG_BUILD_METHOD 8 | import me.shika.di.dagger.resolver.ComponentAnnotationDescriptor 9 | import me.shika.di.dagger.resolver.DAGGER_BINDS_INSTANCE_FQ_NAME 10 | import me.shika.di.dagger.resolver.ResolverContext 11 | import me.shika.di.dagger.resolver.allDescriptors 12 | import me.shika.di.dagger.resolver.report 13 | import me.shika.di.model.Binding 14 | import org.jetbrains.kotlin.builtins.KotlinBuiltIns 15 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 16 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 17 | import org.jetbrains.kotlin.descriptors.Modality 18 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter 19 | import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf 20 | 21 | class BuilderDescriptor( 22 | private val component: ClassDescriptor, 23 | private val definition: ClassDescriptor, 24 | private val componentAnnotation: ComponentAnnotationDescriptor, 25 | private val context: ResolverContext 26 | ) : CreatorDescriptor { 27 | var buildMethod: FunctionDescriptor? = null 28 | private set 29 | 30 | var setters: List = emptyList() 31 | private set 32 | 33 | override var instances: List = emptyList() 34 | private set 35 | 36 | init { 37 | parseDefinition() 38 | } 39 | 40 | private fun parseDefinition() { 41 | val methods = definition.allDescriptors(DescriptorKindFilter.FUNCTIONS) 42 | .filterIsInstance() 43 | .filter { it.modality == Modality.ABSTRACT } 44 | 45 | val buildMethods = methods.filter { 46 | it.valueParameters.size == 0 && component.defaultType.isSubtypeOf(it.returnType!!) 47 | } 48 | 49 | if (buildMethods.size != 1) { 50 | definition.report(context.trace) { BUILDER_WRONG_BUILD_METHOD.on(it, buildMethods) } 51 | return 52 | } 53 | buildMethod = buildMethods.firstOrNull() 54 | 55 | val setterMethods = methods - buildMethods 56 | if (!validateSetters(setterMethods)) return 57 | validateDependencies(setterMethods) 58 | setters = setterMethods 59 | 60 | instances = setterMethods.filter { it.annotations.hasAnnotation(DAGGER_BINDS_INSTANCE_FQ_NAME) } 61 | .map { it.valueParameters.first().toInstanceBinding() } 62 | } 63 | 64 | private fun validateSetters(setters: List): Boolean { 65 | var isValid = true 66 | setters.forEach { setter -> 67 | if (setter.valueParameters.size != 1) { 68 | setter.report(context.trace) { BUILDER_SETTER_TOO_MANY_PARAMS.on(it) } 69 | isValid = false 70 | } 71 | 72 | if (!KotlinBuiltIns.isUnit(setter.returnType!!) && !definition.defaultType.isSubtypeOf(setter.returnType!!)) { 73 | setter.report(context.trace) { BUILDER_SETTER_WRONG_RETURN_TYPE.on(it) } 74 | isValid = false 75 | } 76 | } 77 | 78 | return isValid 79 | } 80 | 81 | private fun validateDependencies(setters: List) { 82 | val declaredDependencies = componentAnnotation.dependencies 83 | val declaredModuleInstances = componentAnnotation.moduleInstances 84 | 85 | val notSetDependency = declaredDependencies.filterNot { dep -> 86 | setters.any { it.paramType() == dep.defaultType } 87 | } 88 | val notSetModule = declaredModuleInstances.filterNot { module -> 89 | setters.any { it.paramType() == module.defaultType } 90 | } 91 | 92 | notSetDependency.forEach { dep -> definition.report(context.trace) { BUILDER_DEPENDENCIES_NOT_PROVIDED.on(it, dep.defaultType) } } 93 | notSetModule.forEach { module -> definition.report(context.trace) { BUILDER_MODULE_NOT_PROVIDED.on(it, module.defaultType) } } 94 | } 95 | } 96 | 97 | private fun FunctionDescriptor.paramType() = valueParameters.first().type 98 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/MembersInjectorRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.CodeBlock 5 | import com.squareup.kotlinpoet.KModifier 6 | import com.squareup.kotlinpoet.ParameterizedTypeName 7 | import com.squareup.kotlinpoet.PropertySpec 8 | import com.squareup.kotlinpoet.TypeName 9 | import com.squareup.kotlinpoet.TypeSpec 10 | import me.shika.di.dagger.renderer.dsl.function 11 | import me.shika.di.dagger.renderer.dsl.markOverride 12 | import me.shika.di.dagger.renderer.dsl.markPrivate 13 | import me.shika.di.dagger.renderer.dsl.nestedClass 14 | import me.shika.di.dagger.renderer.provider.ProviderSpec 15 | import me.shika.di.dagger.renderer.provider.getValue 16 | import me.shika.di.dagger.renderer.provider.initializeDeps 17 | import me.shika.di.model.Endpoint 18 | import me.shika.di.model.Injectable 19 | import me.shika.di.model.ResolveResult 20 | 21 | class MembersInjectorRenderer( 22 | private val componentBuilder: TypeSpec.Builder, 23 | private val componentName: ClassName, 24 | private val factoryRenderer: GraphRenderer 25 | ) { 26 | fun getMembersInjector(injectedType: TypeName, results: List): PropertySpec? { 27 | val membersInjectorType = injectedType.injector() 28 | 29 | return componentBuilder.propertySpecs.find { it.type == membersInjectorType } 30 | ?: componentBuilder.addMembersInjector(injectedType, results) 31 | } 32 | 33 | private fun TypeSpec.Builder.addMembersInjector(injectedTypeName: TypeName, results: List): PropertySpec { 34 | val renderedName = injectedTypeName.asString().capitalize() 35 | val injectorName = "${renderedName}_MembersInjector" 36 | val injectorTypeName = componentName.nestedClass(injectorName) 37 | val injectedParamName = renderedName.decapitalize() 38 | 39 | val injectedFactories = results.map { (it.endpoint as Endpoint.Injected).value } 40 | .zip(results.map { it.graph.map { factoryRenderer.getProvider(it) } }) 41 | 42 | val factoryParams = injectedFactories.flatMap { it.second }.filterNotNull().distinct() 43 | 44 | nestedClass(injectorName) { 45 | markPrivate() 46 | addSuperinterface(injectedTypeName.injector()) 47 | initializeDeps(factoryParams) 48 | function("injectMembers") { 49 | markOverride() 50 | addParameter(injectedParamName, injectedTypeName) 51 | injectedFactories.generateInjects(injectedParamName).forEach { 52 | addCode(it) 53 | } 54 | } 55 | } 56 | 57 | val property = PropertySpec.builder( 58 | injectorTypeName.asFqString().decapitalize(), 59 | injectedTypeName.injector(), 60 | KModifier.PRIVATE 61 | ).initializer( 62 | injectorTypeName.let { 63 | val params = Array(factoryParams.size) { "%N" }.joinToString() 64 | CodeBlock.of("%T($params)", it, *factoryParams.map { it.property }.toTypedArray()) 65 | } 66 | ) 67 | .build() 68 | addProperty(property) 69 | 70 | return property 71 | } 72 | 73 | private fun List>>.generateInjects(injectedParamName: String): List = 74 | map { (injectable, factories) -> 75 | when (injectable) { 76 | is Injectable.Setter -> { 77 | val setter = injectable.descriptor.name.asString() 78 | val params = factories.filterNotNull().joinToString { it.getValue() } 79 | CodeBlock.of("$injectedParamName.$setter($params)\n") 80 | } 81 | is Injectable.Property -> { 82 | val parameter = injectable.descriptor.name.asString() 83 | val factory = factories.single() 84 | val value = factory?.getValue() 85 | CodeBlock.of("$injectedParamName.$parameter = $value\n") 86 | } 87 | } 88 | } 89 | 90 | private fun TypeName.injector() = with(ParameterizedTypeName.Companion) { 91 | ClassName("dagger", "MembersInjector") 92 | .parameterizedBy(this@injector) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/creator/BuilderDescriptor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.creator 2 | 3 | import me.shika.di.BUILDER_DEPENDENCIES_NOT_PROVIDED 4 | import me.shika.di.BUILDER_MODULE_NOT_PROVIDED 5 | import me.shika.di.BUILDER_SETTER_TOO_MANY_PARAMS 6 | import me.shika.di.BUILDER_SETTER_WRONG_RETURN_TYPE 7 | import me.shika.di.BUILDER_WRONG_BUILD_METHOD 8 | import me.shika.di.dagger.resolver.ComponentAnnotationDescriptor 9 | import me.shika.di.dagger.resolver.DAGGER_BINDS_INSTANCE_FQ_NAME 10 | import me.shika.di.dagger.resolver.ResolverContext 11 | import me.shika.di.dagger.resolver.allDescriptors 12 | import me.shika.di.dagger.resolver.report 13 | import me.shika.di.model.Binding 14 | import org.jetbrains.kotlin.builtins.KotlinBuiltIns 15 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 16 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 17 | import org.jetbrains.kotlin.descriptors.Modality 18 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter 19 | import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf 20 | 21 | class BuilderDescriptor( 22 | private val component: ClassDescriptor, 23 | private val definition: ClassDescriptor, 24 | private val componentAnnotation: ComponentAnnotationDescriptor, 25 | private val context: ResolverContext 26 | ) : CreatorDescriptor { 27 | var buildMethod: FunctionDescriptor? = null 28 | private set 29 | 30 | var setters: List = emptyList() 31 | private set 32 | 33 | override var instances: List = emptyList() 34 | private set 35 | 36 | init { 37 | parseDefinition() 38 | } 39 | 40 | private fun parseDefinition() { 41 | val methods = definition.allDescriptors(DescriptorKindFilter.FUNCTIONS) 42 | .filterIsInstance() 43 | .filter { it.modality == Modality.ABSTRACT } 44 | 45 | val buildMethods = methods.filter { 46 | it.valueParameters.size == 0 && component.defaultType.isSubtypeOf(it.returnType!!) 47 | } 48 | 49 | if (buildMethods.size != 1) { 50 | definition.report(context.trace) { BUILDER_WRONG_BUILD_METHOD.on(it, buildMethods) } 51 | return 52 | } 53 | buildMethod = buildMethods.firstOrNull() 54 | 55 | val setterMethods = methods - buildMethods 56 | if (!validateSetters(setterMethods)) return 57 | validateDependencies(setterMethods) 58 | setters = setterMethods 59 | 60 | instances = setterMethods.filter { it.annotations.hasAnnotation(DAGGER_BINDS_INSTANCE_FQ_NAME) } 61 | .map { it.valueParameters.first().toInstanceBinding() } 62 | } 63 | 64 | private fun validateSetters(setters: List): Boolean { 65 | var isValid = true 66 | setters.forEach { setter -> 67 | if (setter.valueParameters.size != 1) { 68 | setter.report(context.trace) { BUILDER_SETTER_TOO_MANY_PARAMS.on(it) } 69 | isValid = false 70 | } 71 | 72 | if (!KotlinBuiltIns.isUnit(setter.returnType!!) && !definition.defaultType.isSubtypeOf(setter.returnType!!)) { 73 | setter.report(context.trace) { BUILDER_SETTER_WRONG_RETURN_TYPE.on(it) } 74 | isValid = false 75 | } 76 | } 77 | 78 | return isValid 79 | } 80 | 81 | private fun validateDependencies(setters: List) { 82 | val declaredDependencies = componentAnnotation.dependencies 83 | val declaredModuleInstances = componentAnnotation.moduleInstances 84 | 85 | val notSetDependency = declaredDependencies.filterNot { dep -> 86 | setters.any { it.paramType() == dep.defaultType } 87 | } 88 | val notSetModule = declaredModuleInstances.filterNot { module -> 89 | setters.any { it.paramType() == module.defaultType } || module.constructors.any { it.valueParameters.isEmpty() } 90 | } 91 | 92 | notSetDependency.forEach { dep -> definition.report(context.trace) { BUILDER_DEPENDENCIES_NOT_PROVIDED.on(it, dep.defaultType) } } 93 | notSetModule.forEach { module -> definition.report(context.trace) { BUILDER_MODULE_NOT_PROVIDED.on(it, module.defaultType) } } 94 | } 95 | } 96 | 97 | private fun FunctionDescriptor.paramType() = valueParameters.first().type 98 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/provider/utils.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer.provider 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.CodeBlock 5 | import com.squareup.kotlinpoet.FunSpec 6 | import com.squareup.kotlinpoet.MemberName 7 | import com.squareup.kotlinpoet.ParameterizedTypeName 8 | import com.squareup.kotlinpoet.PropertySpec 9 | import com.squareup.kotlinpoet.TypeName 10 | import com.squareup.kotlinpoet.TypeSpec 11 | import me.shika.di.dagger.renderer.asString 12 | import me.shika.di.dagger.renderer.dsl.function 13 | import me.shika.di.dagger.renderer.dsl.markOverride 14 | import me.shika.di.dagger.renderer.dsl.markPrivate 15 | import me.shika.di.dagger.renderer.dsl.nestedClass 16 | import me.shika.di.dagger.renderer.dsl.property 17 | import me.shika.di.dagger.renderer.letIf 18 | import me.shika.di.dagger.renderer.provider.ProviderSpec.ProviderType 19 | import me.shika.di.dagger.renderer.toParameter 20 | import me.shika.di.model.Binding 21 | 22 | internal fun TypeSpec.Builder.initializeDeps(factories: List) { 23 | val properties = factories.map { 24 | PropertySpec.builder(it.property.name, it.property.type, *it.property.modifiers.toTypedArray()) 25 | .initializer(it.property.name) 26 | .build() 27 | } 28 | 29 | // Inner static class to generate binding 30 | addProperties(properties) 31 | primaryConstructor( 32 | FunSpec.constructorBuilder() 33 | .addParameters(properties.map { it.toParameter() }) 34 | .build() 35 | ) 36 | } 37 | 38 | internal fun ProviderSpec.getValue() = when(type) { 39 | ProviderType.Provider -> "${property.name}.get()" 40 | ProviderType.Value -> property.name 41 | } 42 | 43 | internal fun providerOf(typeName: TypeName) = 44 | with(ParameterizedTypeName.Companion) { 45 | PROVIDER_CLASS_NAME.parameterizedBy(typeName) 46 | } 47 | 48 | internal fun TypeSpec.Builder.providerImpl( 49 | providerName: String, 50 | returnType: TypeName, 51 | dependencies: List, 52 | providerBody: CodeBlock 53 | ): TypeName { 54 | val providerTypeName = providerOf(returnType) 55 | 56 | nestedClass(providerName) { 57 | markPrivate() 58 | addSuperinterface(providerTypeName) 59 | initializeDeps(dependencies.distinct()) 60 | 61 | function("get") { 62 | markOverride() 63 | returns(returnType) 64 | addCode(providerBody) 65 | } 66 | } 67 | 68 | return providerTypeName 69 | } 70 | 71 | internal fun TypeSpec.Builder.providerProperty( 72 | propertyName: String, 73 | deps: List, 74 | constructorType: TypeName, 75 | propertyType: TypeName, 76 | doubleCheck: Boolean 77 | ) = ProviderSpec( 78 | property = property(propertyName, propertyType) { 79 | markPrivate() 80 | initializeProvider(constructorType, deps.distinct().map { it.property.name }, doubleCheck) 81 | }, 82 | type = ProviderType.Provider 83 | ) 84 | 85 | internal fun PropertySpec.Builder.initializeProvider( 86 | providerType: TypeName, 87 | params: List, 88 | doubleCheck: Boolean 89 | ): PropertySpec.Builder = apply { 90 | val args = hashMapOf( 91 | "doubleCheck" to DOUBLE_CHECK_PROVIDER, 92 | "factoryType" to providerType 93 | ) 94 | 95 | initializer( 96 | CodeBlock.builder() 97 | .addNamed( 98 | "%factoryType:T(${params.joinToString()})".letIf(doubleCheck) { 99 | "%doubleCheck:M($it)" 100 | }, 101 | args 102 | ) 103 | .build() 104 | ) 105 | } 106 | 107 | internal fun Binding.renderedName(parentType: TypeName?): String { 108 | val qualifiers = key.qualifiers.joinToString( 109 | separator = "_", 110 | prefix = "_" 111 | ) { it.fqName?.shortName()?.asString()?.decapitalize().orEmpty() } 112 | val methodName = bindingType.source.name.asString().capitalize() 113 | return "${parentType?.asString()}_$methodName$qualifiers" 114 | } 115 | 116 | private val PROVIDER_CLASS_NAME = ClassName("javax.inject", "Provider") 117 | internal val DOUBLE_CHECK_NAME = ClassName("dagger.internal", "DoubleCheck") 118 | private val DOUBLE_CHECK_PROVIDER = MemberName(DOUBLE_CHECK_NAME, "provider") 119 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/GraphRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.CodeBlock 5 | import com.squareup.kotlinpoet.MemberName 6 | import com.squareup.kotlinpoet.TypeSpec 7 | import me.shika.di.dagger.renderer.provider.BindingRenderer 8 | import me.shika.di.dagger.renderer.provider.BoundInstanceBindingRenderer 9 | import me.shika.di.dagger.renderer.provider.ComponentBindingRenderer 10 | import me.shika.di.dagger.renderer.provider.ConstructorBindingRenderer 11 | import me.shika.di.dagger.renderer.provider.EqualityBindingRenderer 12 | import me.shika.di.dagger.renderer.provider.InstanceFunctionRenderer 13 | import me.shika.di.dagger.renderer.provider.InstancePropertyRenderer 14 | import me.shika.di.dagger.renderer.provider.LazyBindingRenderer 15 | import me.shika.di.dagger.renderer.provider.ProviderBindingRenderer 16 | import me.shika.di.dagger.renderer.provider.ProviderSpec 17 | import me.shika.di.dagger.renderer.provider.RecursiveBindingRenderer 18 | import me.shika.di.dagger.renderer.provider.StaticFunctionRenderer 19 | import me.shika.di.model.Binding 20 | import me.shika.di.model.Binding.Variation.BoundInstance 21 | import me.shika.di.model.Binding.Variation.Component 22 | import me.shika.di.model.Binding.Variation.Constructor 23 | import me.shika.di.model.Binding.Variation.Equality 24 | import me.shika.di.model.Binding.Variation.InstanceFunction 25 | import me.shika.di.model.Binding.Variation.InstanceProperty 26 | import me.shika.di.model.Binding.Variation.Lazy 27 | import me.shika.di.model.Binding.Variation.Provider 28 | import me.shika.di.model.Binding.Variation.Recursive 29 | import me.shika.di.model.Binding.Variation.StaticFunction 30 | import me.shika.di.model.GraphNode 31 | 32 | class GraphRenderer(private val componentBuilder: TypeSpec.Builder, private val componentName: ClassName) { 33 | private val bindingToProp = mutableMapOf() 34 | private val recursionResolved = mutableMapOf() 35 | 36 | fun getProvider(graphNode: GraphNode): ProviderSpec? { 37 | return getUnresolvedProvider(graphNode).also { 38 | componentBuilder.resolveRecursiveBindings() 39 | } 40 | } 41 | 42 | private fun getUnresolvedProvider(graphNode: GraphNode): ProviderSpec? { 43 | return bindingToProp.getOrPut(graphNode.value) { 44 | componentBuilder.addFactory(graphNode) 45 | } 46 | } 47 | 48 | private fun TypeSpec.Builder.addFactory(graphNode: GraphNode): ProviderSpec { 49 | val deps by lazy { graphNode.dependencies.mapNotNull { getUnresolvedProvider(it) } } 50 | return when (graphNode.value.bindingType) { 51 | is Constructor -> render(ConstructorBindingRenderer(componentName, deps), graphNode) 52 | is InstanceFunction -> render(InstanceFunctionRenderer(componentName, deps), graphNode) 53 | is InstanceProperty -> render(InstancePropertyRenderer(componentName), graphNode) 54 | is StaticFunction -> render(StaticFunctionRenderer(componentName, deps), graphNode) 55 | is Equality -> render(EqualityBindingRenderer(deps), graphNode) 56 | is BoundInstance -> render(BoundInstanceBindingRenderer(), graphNode) 57 | is Component -> render(ComponentBindingRenderer(componentName), graphNode) 58 | is Provider -> render(ProviderBindingRenderer(componentName, deps), graphNode) 59 | is Lazy -> render(LazyBindingRenderer(componentName, deps), graphNode) 60 | is Recursive -> render(RecursiveBindingRenderer(), graphNode) 61 | } 62 | } 63 | 64 | private fun TypeSpec.Builder.resolveRecursiveBindings() { 65 | val recursiveBindings = bindingToProp.keys.filter { it.bindingType is Recursive && recursionResolved[it] == null } 66 | recursiveBindings.forEach { 67 | val providerSpec = bindingToProp[it] 68 | val delegateSpec = bindingToProp[(it.bindingType as Recursive).delegate] 69 | addInitializerBlock( 70 | CodeBlock.of("%M(%N, %N)", DELEGATE_METHOD, providerSpec?.property, delegateSpec?.property) 71 | ) 72 | recursionResolved[it] = true 73 | } 74 | } 75 | 76 | private fun TypeSpec.Builder.render(renderer: BindingRenderer, graphNode: GraphNode) = 77 | renderer.run { render(graphNode.value, graphNode.value.bindingType as T) } 78 | 79 | companion object { 80 | private val DELEGATE_METHOD = MemberName(ClassName("dagger.internal", "DelegateFactory"), "setDelegate") 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/bindings/ModuleBindingResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.bindings 2 | 3 | import me.shika.di.BINDS_METHOD_NOT_ABSTRACT 4 | import me.shika.di.BINDS_METHOD_NOT_ONE_PARAMETER 5 | import me.shika.di.BINDS_TYPE_IS_NOT_ASSIGNABLE 6 | import me.shika.di.MODULE_WITHOUT_ANNOTATION 7 | import me.shika.di.PROVIDES_METHOD_ABSTRACT 8 | import me.shika.di.dagger.resolver.ComponentAnnotationDescriptor 9 | import me.shika.di.dagger.resolver.ResolverContext 10 | import me.shika.di.dagger.resolver.allDescriptors 11 | import me.shika.di.dagger.resolver.qualifiers 12 | import me.shika.di.dagger.resolver.report 13 | import me.shika.di.dagger.resolver.scopeAnnotations 14 | import me.shika.di.model.Binding 15 | import me.shika.di.model.Key 16 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 17 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 18 | import org.jetbrains.kotlin.descriptors.Modality 19 | import org.jetbrains.kotlin.name.FqName 20 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter 21 | import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf 22 | 23 | class ModuleBindingResolver( 24 | private val componentAnnotation: ComponentAnnotationDescriptor, 25 | private val definition: ClassDescriptor, 26 | private val context: ResolverContext 27 | ): BindingResolver { 28 | override fun invoke(): List = 29 | componentAnnotation.modules 30 | .flatMap { resolveBindings(it).toList() } 31 | 32 | private fun resolveBindings(module: ClassDescriptor): Sequence { 33 | module.validateModuleAnnotation() 34 | 35 | val isInstance = module in componentAnnotation.moduleInstances 36 | 37 | val companionFunctions = module.companionObjectDescriptor 38 | ?.allDescriptors(DescriptorKindFilter.FUNCTIONS) 39 | ?: emptyList() 40 | 41 | val instanceFunctions = module.allDescriptors(DescriptorKindFilter.FUNCTIONS) 42 | 43 | val moduleFunctions = (instanceFunctions + companionFunctions).asSequence() 44 | .filterIsInstance() 45 | 46 | return moduleFunctions.provisionBindings(isInstance) + moduleFunctions.typeEqualityBindings() 47 | } 48 | 49 | private fun ClassDescriptor.validateModuleAnnotation() { 50 | if (!annotations.hasAnnotation(DAGGER_MODULE_FQ_NAME)) { 51 | report(context.trace) { MODULE_WITHOUT_ANNOTATION.on(it, definition) } 52 | } 53 | } 54 | 55 | private fun Sequence.provisionBindings(isInstance: Boolean) = 56 | filter { it.annotations.hasAnnotation(PROVIDES_FQ_NAME) } 57 | .map { 58 | it.validateProvisionBinding() 59 | Binding( 60 | Key(it.returnType!!, it.qualifiers()), 61 | it.scopeAnnotations(), 62 | when { 63 | isInstance -> Binding.Variation.InstanceFunction(it) 64 | else -> Binding.Variation.StaticFunction(it) 65 | } 66 | ) 67 | } 68 | 69 | private fun FunctionDescriptor.validateProvisionBinding() { 70 | if (modality == Modality.ABSTRACT) { 71 | report(context.trace) { PROVIDES_METHOD_ABSTRACT.on(it) } 72 | } 73 | } 74 | 75 | private fun Sequence.typeEqualityBindings() = 76 | filter { it.annotations.hasAnnotation(BINDS_FQ_NAME) } 77 | .map { 78 | it.validateTypeEqualityBinding() 79 | Binding( 80 | Key(it.returnType!!, it.qualifiers()), 81 | it.scopeAnnotations(), 82 | Binding.Variation.Equality(it) 83 | ) 84 | } 85 | 86 | private fun FunctionDescriptor.validateTypeEqualityBinding() { 87 | if (modality != Modality.ABSTRACT) { 88 | report(context.trace) { BINDS_METHOD_NOT_ABSTRACT.on(it) } 89 | } 90 | 91 | if (valueParameters.size != 1) { 92 | report(context.trace) { BINDS_METHOD_NOT_ONE_PARAMETER.on(it) } 93 | return 94 | } 95 | 96 | val paramType = valueParameters.first().type 97 | if (!paramType.isSubtypeOf(returnType!!)) { 98 | report(context.trace) { BINDS_TYPE_IS_NOT_ASSIGNABLE.on(it) } 99 | return 100 | } 101 | } 102 | } 103 | 104 | private val PROVIDES_FQ_NAME = FqName("dagger.Provides") 105 | private val BINDS_FQ_NAME = FqName("dagger.Binds") 106 | private val DAGGER_MODULE_FQ_NAME = FqName("dagger.Module") 107 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/bindings/ModuleBindingResolver.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver.bindings 2 | 3 | import me.shika.di.BINDS_METHOD_NOT_ABSTRACT 4 | import me.shika.di.BINDS_METHOD_NOT_ONE_PARAMETER 5 | import me.shika.di.BINDS_TYPE_IS_NOT_ASSIGNABLE 6 | import me.shika.di.MODULE_WITHOUT_ANNOTATION 7 | import me.shika.di.PROVIDES_METHOD_ABSTRACT 8 | import me.shika.di.dagger.resolver.ComponentAnnotationDescriptor 9 | import me.shika.di.dagger.resolver.ResolverContext 10 | import me.shika.di.dagger.resolver.allDescriptors 11 | import me.shika.di.dagger.resolver.qualifiers 12 | import me.shika.di.dagger.resolver.report 13 | import me.shika.di.dagger.resolver.scopeAnnotations 14 | import me.shika.di.model.Binding 15 | import me.shika.di.model.Key 16 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 17 | import org.jetbrains.kotlin.descriptors.FunctionDescriptor 18 | import org.jetbrains.kotlin.descriptors.Modality 19 | import org.jetbrains.kotlin.name.FqName 20 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter 21 | import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf 22 | 23 | class ModuleBindingResolver( 24 | private val componentAnnotation: ComponentAnnotationDescriptor, 25 | private val definition: ClassDescriptor, 26 | private val context: ResolverContext 27 | ): BindingResolver { 28 | override fun invoke(): List = 29 | componentAnnotation.modules 30 | .flatMap { resolveBindings(it).toList() } 31 | 32 | private fun resolveBindings(module: ClassDescriptor): Sequence { 33 | module.validateModuleAnnotation() 34 | 35 | val isInstance = module in componentAnnotation.moduleInstances 36 | 37 | val companionFunctions = module.companionObjectDescriptor 38 | ?.allDescriptors(DescriptorKindFilter.FUNCTIONS) 39 | ?: emptyList() 40 | 41 | val instanceFunctions = module.allDescriptors(DescriptorKindFilter.FUNCTIONS) 42 | 43 | val moduleFunctions = (instanceFunctions + companionFunctions).asSequence() 44 | .filterIsInstance() 45 | 46 | return moduleFunctions.provisionBindings(isInstance) + moduleFunctions.typeEqualityBindings() 47 | } 48 | 49 | private fun ClassDescriptor.validateModuleAnnotation() { 50 | if (!annotations.hasAnnotation(DAGGER_MODULE_FQ_NAME)) { 51 | report(context.trace) { MODULE_WITHOUT_ANNOTATION.on(it, definition) } 52 | } 53 | } 54 | 55 | private fun Sequence.provisionBindings(isInstance: Boolean) = 56 | filter { it.annotations.hasAnnotation(PROVIDES_FQ_NAME) } 57 | .map { 58 | it.validateProvisionBinding() 59 | Binding( 60 | Key(it.returnType!!, it.qualifiers()), 61 | it.scopeAnnotations(), 62 | when { 63 | isInstance -> Binding.Variation.InstanceFunction(it) 64 | else -> Binding.Variation.StaticFunction(it) 65 | } 66 | ) 67 | } 68 | 69 | private fun FunctionDescriptor.validateProvisionBinding() { 70 | if (modality == Modality.ABSTRACT) { 71 | report(context.trace) { PROVIDES_METHOD_ABSTRACT.on(it) } 72 | } 73 | } 74 | 75 | private fun Sequence.typeEqualityBindings() = 76 | filter { it.annotations.hasAnnotation(BINDS_FQ_NAME) } 77 | .map { 78 | it.validateTypeEqualityBinding() 79 | Binding( 80 | Key(it.returnType!!, it.qualifiers()), 81 | it.scopeAnnotations(), 82 | Binding.Variation.Equality(it) 83 | ) 84 | } 85 | 86 | private fun FunctionDescriptor.validateTypeEqualityBinding() { 87 | if (modality != Modality.ABSTRACT) { 88 | report(context.trace) { BINDS_METHOD_NOT_ABSTRACT.on(it) } 89 | } 90 | 91 | if (valueParameters.size != 1) { 92 | report(context.trace) { BINDS_METHOD_NOT_ONE_PARAMETER.on(it) } 93 | return 94 | } 95 | 96 | val paramType = valueParameters.first().type 97 | if (!paramType.isSubtypeOf(returnType!!)) { 98 | report(context.trace) { BINDS_TYPE_IS_NOT_ASSIGNABLE.on(it) } 99 | return 100 | } 101 | } 102 | } 103 | 104 | private val PROVIDES_FQ_NAME = FqName("dagger.Provides") 105 | private val BINDS_FQ_NAME = FqName("dagger.Binds") 106 | private val DAGGER_MODULE_FQ_NAME = FqName("dagger.Module") 107 | -------------------------------------------------------------------------------- /codegen-reflect/kotlin-plugin/src/main/java/me/shika/di/dagger/renderer/ComponentRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer 2 | 3 | import com.squareup.kotlinpoet.AnnotationSpec 4 | import com.squareup.kotlinpoet.ClassName 5 | import com.squareup.kotlinpoet.FileSpec 6 | import com.squareup.kotlinpoet.KModifier.INTERNAL 7 | import com.squareup.kotlinpoet.KModifier.PRIVATE 8 | import com.squareup.kotlinpoet.TypeSpec 9 | import me.shika.di.dagger.renderer.creator.BuilderRenderer 10 | import me.shika.di.dagger.renderer.creator.DefaultBuilderRenderer 11 | import me.shika.di.dagger.renderer.creator.FactoryRenderer 12 | import me.shika.di.dagger.renderer.dsl.markPrivate 13 | import me.shika.di.dagger.renderer.dsl.primaryConstructor 14 | import me.shika.di.dagger.renderer.dsl.property 15 | import me.shika.di.dagger.resolver.DaggerComponentDescriptor 16 | import me.shika.di.dagger.resolver.creator.BuilderDescriptor 17 | import me.shika.di.dagger.resolver.creator.DefaultBuilderDescriptor 18 | import me.shika.di.dagger.resolver.creator.FactoryDescriptor 19 | import me.shika.di.dagger.resolver.scopeAnnotations 20 | import org.jetbrains.kotlin.descriptors.ClassKind 21 | import org.jetbrains.kotlin.descriptors.Visibilities 22 | import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi 23 | import org.jetbrains.kotlin.psi.KtFile 24 | import org.jetbrains.kotlin.renderer.render 25 | import java.io.File 26 | 27 | class DaggerComponentRenderer( 28 | private val componentDescriptor: DaggerComponentDescriptor 29 | ) { 30 | private val definition = componentDescriptor.definition 31 | private val componentClassName = ClassName( 32 | (definition.findPsi()?.containingFile as KtFile).packageFqName.render(), 33 | "Dagger${definition.name}" 34 | ) 35 | 36 | fun render(sourcesDir: File) { 37 | val fileSpec = FileSpec.builder(componentClassName.packageName, componentClassName.simpleName) 38 | .addType(renderComponent(/*componentDescriptor.graph*/)) 39 | .build() 40 | 41 | fileSpec.writeTo(sourcesDir) 42 | } 43 | 44 | private fun renderComponent(/*results: List*/): TypeSpec = 45 | TypeSpec.interfaceBuilder(componentClassName) 46 | .apply { 47 | if (definition.visibility == Visibilities.INTERNAL) { 48 | addModifiers(INTERNAL) 49 | } 50 | extendComponent() 51 | if (componentDescriptor.creatorDescriptor is DefaultBuilderDescriptor) { 52 | val modules = componentDescriptor.annotation.modules.mapNotNull { it.typeName() } 53 | val dependencies = componentDescriptor.annotation.dependencies.mapNotNull { it.typeName() } 54 | val scopes = componentDescriptor.definition.scopeAnnotations().mapNotNull { it.type.typeName() } 55 | addAnnotation( 56 | AnnotationSpec.builder(DAGGER_COMPONENT) 57 | .addMember("modules = [${modules.joinToString { "%T::class" }}]", *modules.toTypedArray()) 58 | .addMember("dependencies = [${dependencies.joinToString { "%T::class" }}]", *dependencies.toTypedArray()) 59 | .build() 60 | ) 61 | 62 | scopes.forEach { 63 | addAnnotation(it as ClassName) 64 | } 65 | } 66 | creator() 67 | } 68 | .build() 69 | 70 | private fun TypeSpec.Builder.extendComponent() = apply { 71 | if (definition.kind == ClassKind.INTERFACE) { 72 | addSuperinterface(definition.defaultType.typeName()!!) 73 | } else { 74 | superclass(definition.defaultType.typeName()!!) 75 | } 76 | } 77 | 78 | private fun TypeSpec.Builder.creator() = apply { 79 | val originalComponentClassName = definition.defaultType.typeName()!! as ClassName 80 | when (val descriptor = componentDescriptor.creatorDescriptor) { 81 | is FactoryDescriptor -> FactoryRenderer( 82 | originalComponentClassName, 83 | componentDescriptor.parameters, 84 | this 85 | ).render(descriptor) 86 | is BuilderDescriptor -> BuilderRenderer( 87 | originalComponentClassName, 88 | componentDescriptor.parameters, 89 | this 90 | ).render(descriptor) 91 | is DefaultBuilderDescriptor -> DefaultBuilderRenderer( 92 | originalComponentClassName, 93 | componentClassName, 94 | componentDescriptor.parameters, 95 | this 96 | ).render() 97 | } 98 | } 99 | 100 | private fun TypeSpec.Builder.addDependencyInstances() { 101 | val properties = componentDescriptor.parameters.map { 102 | val name = it.parameterName() 103 | property(name, it.type.typeName()!!) { 104 | markPrivate() 105 | initializer(name) 106 | } 107 | } 108 | 109 | primaryConstructor { 110 | addModifiers(PRIVATE) 111 | addParameters(properties.map { it.toParameter() }) 112 | } 113 | } 114 | 115 | companion object { 116 | private val DAGGER_COMPONENT = ClassName("dagger", "Component") 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/renderer/ComponentRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.renderer 2 | 3 | import com.squareup.kotlinpoet.ClassName 4 | import com.squareup.kotlinpoet.FileSpec 5 | import com.squareup.kotlinpoet.KModifier.INTERNAL 6 | import com.squareup.kotlinpoet.KModifier.PRIVATE 7 | import com.squareup.kotlinpoet.TypeSpec 8 | import me.shika.di.dagger.renderer.creator.BuilderRenderer 9 | import me.shika.di.dagger.renderer.creator.DefaultBuilderRenderer 10 | import me.shika.di.dagger.renderer.creator.FactoryRenderer 11 | import me.shika.di.dagger.renderer.dsl.markPrivate 12 | import me.shika.di.dagger.renderer.dsl.overrideFunction 13 | import me.shika.di.dagger.renderer.dsl.primaryConstructor 14 | import me.shika.di.dagger.renderer.dsl.property 15 | import me.shika.di.dagger.renderer.provider.getValue 16 | import me.shika.di.dagger.resolver.DaggerComponentDescriptor 17 | import me.shika.di.dagger.resolver.creator.BuilderDescriptor 18 | import me.shika.di.dagger.resolver.creator.DefaultBuilderDescriptor 19 | import me.shika.di.dagger.resolver.creator.FactoryDescriptor 20 | import me.shika.di.model.Endpoint 21 | import me.shika.di.model.ResolveResult 22 | import org.jetbrains.kotlin.descriptors.ClassKind 23 | import org.jetbrains.kotlin.descriptors.Visibilities 24 | import org.jetbrains.kotlin.renderer.render 25 | import java.io.File 26 | 27 | class DaggerComponentRenderer( 28 | private val componentDescriptor: DaggerComponentDescriptor 29 | ) { 30 | private val definition = componentDescriptor.definition 31 | private val componentClassName = ClassName( 32 | componentDescriptor.file.packageFqName.render(), 33 | "Dagger${definition.name}" 34 | ) 35 | 36 | fun render(sourcesDir: File) { 37 | val fileSpec = FileSpec.builder(componentClassName.packageName, componentClassName.simpleName) 38 | .addType(renderComponent(componentDescriptor.graph)) 39 | .build() 40 | 41 | fileSpec.writeTo(sourcesDir) 42 | } 43 | 44 | private fun renderComponent(results: List): TypeSpec = 45 | TypeSpec.classBuilder(componentClassName) 46 | .apply { 47 | if (definition.visibility == Visibilities.INTERNAL) { 48 | addModifiers(INTERNAL) 49 | } 50 | extendComponent() 51 | creator() 52 | addDependencyInstances() 53 | addBindings(results) 54 | } 55 | .build() 56 | 57 | private fun TypeSpec.Builder.extendComponent() = apply { 58 | if (definition.kind == ClassKind.INTERFACE) { 59 | addSuperinterface(definition.defaultType.typeName()!!) 60 | } else { 61 | superclass(definition.defaultType.typeName()!!) 62 | } 63 | } 64 | 65 | private fun TypeSpec.Builder.creator() = apply { 66 | when (val descriptor = componentDescriptor.creatorDescriptor) { 67 | is FactoryDescriptor -> FactoryRenderer( 68 | componentClassName, 69 | componentDescriptor.parameters, 70 | this 71 | ).render(descriptor) 72 | is BuilderDescriptor -> BuilderRenderer( 73 | componentClassName, 74 | componentDescriptor.parameters, 75 | this 76 | ).render(descriptor) 77 | is DefaultBuilderDescriptor -> DefaultBuilderRenderer( 78 | componentClassName, 79 | componentDescriptor.parameters, 80 | this 81 | ).render() 82 | } 83 | } 84 | 85 | private fun TypeSpec.Builder.addDependencyInstances() { 86 | val properties = componentDescriptor.parameters.map { 87 | val name = it.parameterName() 88 | property(name, it.type.typeName()!!) { 89 | markPrivate() 90 | initializer(name) 91 | } 92 | } 93 | 94 | primaryConstructor { 95 | addModifiers(PRIVATE) 96 | addParameters(properties.map { it.toParameter() }) 97 | } 98 | } 99 | 100 | private fun TypeSpec.Builder.addBindings(results: List) = apply { 101 | val factoryRenderer = 102 | GraphRenderer(this, componentClassName) 103 | val membersInjectorRenderer = 104 | MembersInjectorRenderer(this, componentClassName, factoryRenderer) 105 | results.groupBy { it.endpoint.source } 106 | .map { (_, results) -> 107 | val result = results.first() 108 | when (val endpoint = result.endpoint) { 109 | is Endpoint.Provided -> { 110 | val provider = factoryRenderer.getProvider(result.graph.single()) 111 | overrideFunction(endpoint.source) { 112 | addCode("return ${provider!!.getValue()}") 113 | } 114 | } 115 | is Endpoint.Injected -> { 116 | overrideFunction(endpoint.source) { 117 | val injectedValue = parameters.first() 118 | val membersInjector = membersInjectorRenderer.getMembersInjector(injectedValue.type, results) 119 | addCode("%N.injectMembers(${injectedValue.name})", membersInjector) 120 | } 121 | } 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /common/resolver/src/main/kotlin/me/shika/di/dagger/resolver/ComponentDescriptor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver 2 | 3 | import me.shika.di.COMPONENT_NOT_ABSTRACT 4 | import me.shika.di.COMPONENT_TYPE_PARAMETER 5 | import me.shika.di.COMPONENT_WITH_FACTORY_AND_BUILDER 6 | import me.shika.di.COMPONENT_WITH_MULTIPLE_BUILDERS 7 | import me.shika.di.COMPONENT_WITH_MULTIPLE_FACTORIES 8 | import me.shika.di.dagger.resolver.creator.BuilderDescriptor 9 | import me.shika.di.dagger.resolver.creator.CreatorDescriptor 10 | import me.shika.di.dagger.resolver.creator.DefaultBuilderDescriptor 11 | import me.shika.di.dagger.resolver.creator.FactoryDescriptor 12 | import me.shika.di.model.Binding 13 | import me.shika.di.model.Key 14 | import me.shika.di.model.ResolveResult 15 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 16 | import org.jetbrains.kotlin.descriptors.ClassKind 17 | import org.jetbrains.kotlin.descriptors.Modality 18 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 19 | import org.jetbrains.kotlin.name.FqName 20 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter 21 | import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered 22 | 23 | class DaggerComponentDescriptor( 24 | val definition: ClassDescriptor, 25 | 26 | val context: ResolverContext 27 | ) { 28 | var creatorDescriptor: CreatorDescriptor? = null 29 | private set 30 | 31 | lateinit var annotation: ComponentAnnotationDescriptor 32 | private set 33 | 34 | var parameters: List = emptyList() 35 | private set 36 | 37 | lateinit var graph: List 38 | private set 39 | 40 | init { 41 | parseDefinition() 42 | } 43 | 44 | private fun parseDefinition() { 45 | val componentAnnotation = definition.componentAnnotation()?.let { 46 | ComponentAnnotationDescriptor(context, it) 47 | } ?: return 48 | 49 | annotation = componentAnnotation 50 | val scopes = definition.scopeAnnotations() 51 | 52 | creatorDescriptor = definition.findCreator(componentAnnotation) ?: DefaultBuilderDescriptor() 53 | 54 | // val bindingResolvers = listOfNotNull( 55 | // ModuleBindingResolver(componentAnnotation, definition, context), 56 | // DependencyBindingResolver(componentAnnotation, definition, context), 57 | // creatorDescriptor?.let { CreatorInstanceBindingResolver(it) }, 58 | // { listOf(componentBinding()) } 59 | // ) 60 | 61 | parameters = listOfNotNull( 62 | componentAnnotation.moduleInstances.map { Key(it.defaultType) }, 63 | componentAnnotation.dependencies.map { Key(it.defaultType) }, 64 | creatorDescriptor?.instances?.map { it.key } 65 | ).flatten() 66 | // 67 | // val endpointResolvers = listOf( 68 | // ProvisionEndpointResolver(componentAnnotation, definition, context), 69 | // InjectionEndpointResolver(componentAnnotation, definition, context) 70 | // ) 71 | // 72 | // graph = GraphBuilder( 73 | // context, 74 | // scopes, 75 | // endpointResolvers.flatMap { it() }, 76 | // bindingResolvers.flatMap { it() } 77 | // ).build() 78 | } 79 | 80 | private fun ClassDescriptor.componentAnnotation() = 81 | when { 82 | modality != Modality.ABSTRACT || this is AnnotationDescriptor -> { 83 | report(context.trace) { COMPONENT_NOT_ABSTRACT.on(it) } 84 | null 85 | } 86 | declaredTypeParameters.isNotEmpty() -> { 87 | report(context.trace) { COMPONENT_TYPE_PARAMETER.on(it) } 88 | null 89 | } 90 | else -> annotations.findAnnotation(DAGGER_COMPONENT_ANNOTATION) 91 | } 92 | 93 | private fun ClassDescriptor.findCreator(componentAnnotation: ComponentAnnotationDescriptor): CreatorDescriptor? { 94 | val innerClasses = innerClasses() 95 | val factories = innerClasses.filter { it.annotations.hasAnnotation(DAGGER_FACTORY_ANNOTATION) } 96 | val builders = innerClasses.filter { it.annotations.hasAnnotation(DAGGER_BUILDER_ANNOTATION) } 97 | 98 | if (factories.size > 1) { 99 | report(context.trace) { COMPONENT_WITH_MULTIPLE_FACTORIES.on(it, factories) } 100 | } 101 | 102 | if (builders.size > 1) { 103 | report(context.trace) { COMPONENT_WITH_MULTIPLE_BUILDERS.on(it, builders) } 104 | } 105 | 106 | if (factories.isNotEmpty() && builders.isNotEmpty()) { 107 | report(context.trace) { COMPONENT_WITH_FACTORY_AND_BUILDER.on(it, factories, builders) } 108 | } 109 | 110 | return factories.firstOrNull()?.let { FactoryDescriptor(definition, it, componentAnnotation, context) } 111 | ?: builders.firstOrNull()?.let { BuilderDescriptor(definition, it, componentAnnotation, context) } 112 | } 113 | 114 | private fun componentBinding() = Binding( 115 | Key(definition.defaultType, emptyList()), 116 | emptyList(), 117 | Binding.Variation.Component(definition) 118 | ) 119 | } 120 | 121 | private val DAGGER_COMPONENT_ANNOTATION = FqName("dagger.Component") 122 | private val DAGGER_FACTORY_ANNOTATION = FqName("dagger.Component.Factory") 123 | private val DAGGER_BUILDER_ANNOTATION = FqName("dagger.Component.Builder") 124 | 125 | private fun ClassDescriptor.innerClasses() = 126 | unsubstitutedMemberScope.getDescriptorsFiltered(DescriptorKindFilter.CLASSIFIERS) { true } 127 | .filterIsInstance() 128 | .filter { it.kind == ClassKind.INTERFACE || (it.kind == ClassKind.CLASS && it.modality == Modality.ABSTRACT) } 129 | 130 | fun ClassDescriptor.isComponent() = 131 | annotations.hasAnnotation(DAGGER_COMPONENT_ANNOTATION) 132 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, onChange options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /codegen/kotlin-plugin/src/main/kotlin/me/shika/di/dagger/resolver/ComponentDescriptor.kt: -------------------------------------------------------------------------------- 1 | package me.shika.di.dagger.resolver 2 | 3 | import me.shika.di.COMPONENT_NOT_ABSTRACT 4 | import me.shika.di.COMPONENT_TYPE_PARAMETER 5 | import me.shika.di.COMPONENT_WITH_FACTORY_AND_BUILDER 6 | import me.shika.di.COMPONENT_WITH_MULTIPLE_BUILDERS 7 | import me.shika.di.COMPONENT_WITH_MULTIPLE_FACTORIES 8 | import me.shika.di.dagger.resolver.bindings.CreatorInstanceBindingResolver 9 | import me.shika.di.dagger.resolver.bindings.DependencyBindingResolver 10 | import me.shika.di.dagger.resolver.bindings.ModuleBindingResolver 11 | import me.shika.di.dagger.resolver.creator.BuilderDescriptor 12 | import me.shika.di.dagger.resolver.creator.CreatorDescriptor 13 | import me.shika.di.dagger.resolver.creator.DefaultBuilderDescriptor 14 | import me.shika.di.dagger.resolver.creator.FactoryDescriptor 15 | import me.shika.di.dagger.resolver.endpoints.InjectionEndpointResolver 16 | import me.shika.di.dagger.resolver.endpoints.ProvisionEndpointResolver 17 | import me.shika.di.model.Binding 18 | import me.shika.di.model.Key 19 | import me.shika.di.model.ResolveResult 20 | import org.jetbrains.kotlin.descriptors.ClassDescriptor 21 | import org.jetbrains.kotlin.descriptors.ClassKind 22 | import org.jetbrains.kotlin.descriptors.Modality 23 | import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 24 | import org.jetbrains.kotlin.name.FqName 25 | import org.jetbrains.kotlin.psi.KtFile 26 | import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter 27 | import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered 28 | 29 | class DaggerComponentDescriptor( 30 | val definition: ClassDescriptor, 31 | val file: KtFile, 32 | val context: ResolverContext 33 | ) { 34 | var creatorDescriptor: CreatorDescriptor? = null 35 | private set 36 | 37 | var parameters: List = emptyList() 38 | private set 39 | 40 | lateinit var graph: List 41 | private set 42 | 43 | init { 44 | parseDefinition() 45 | } 46 | 47 | private fun parseDefinition() { 48 | val componentAnnotation = definition.componentAnnotation()?.let { 49 | ComponentAnnotationDescriptor(context, it) 50 | } ?: return 51 | val scopes = definition.scopeAnnotations() 52 | 53 | creatorDescriptor = definition.findCreator(componentAnnotation) ?: DefaultBuilderDescriptor() 54 | 55 | val bindingResolvers = listOfNotNull( 56 | ModuleBindingResolver(componentAnnotation, definition, context), 57 | DependencyBindingResolver(componentAnnotation, definition, context), 58 | creatorDescriptor?.let { CreatorInstanceBindingResolver(it) }, 59 | { listOf(componentBinding()) } 60 | ) 61 | 62 | parameters = listOfNotNull( 63 | componentAnnotation.moduleInstances.map { Key(it.defaultType) }, 64 | componentAnnotation.dependencies.map { Key(it.defaultType) }, 65 | creatorDescriptor?.instances?.map { it.key } 66 | ).flatten() 67 | 68 | val endpointResolvers = listOf( 69 | ProvisionEndpointResolver(componentAnnotation, definition, context), 70 | InjectionEndpointResolver(componentAnnotation, definition, context) 71 | ) 72 | 73 | graph = GraphBuilder( 74 | context, 75 | scopes, 76 | endpointResolvers.flatMap { it() }, 77 | bindingResolvers.flatMap { it() } 78 | ).build() 79 | } 80 | 81 | private fun ClassDescriptor.componentAnnotation() = 82 | when { 83 | modality != Modality.ABSTRACT || this is AnnotationDescriptor -> { 84 | report(context.trace) { COMPONENT_NOT_ABSTRACT.on(it) } 85 | null 86 | } 87 | declaredTypeParameters.isNotEmpty() -> { 88 | report(context.trace) { COMPONENT_TYPE_PARAMETER.on(it) } 89 | null 90 | } 91 | else -> annotations.findAnnotation(DAGGER_COMPONENT_ANNOTATION) 92 | } 93 | 94 | private fun ClassDescriptor.findCreator(componentAnnotation: ComponentAnnotationDescriptor): CreatorDescriptor? { 95 | val innerClasses = innerClasses() 96 | val factories = innerClasses.filter { it.annotations.hasAnnotation(DAGGER_FACTORY_ANNOTATION) } 97 | val builders = innerClasses.filter { it.annotations.hasAnnotation(DAGGER_BUILDER_ANNOTATION) } 98 | 99 | if (factories.size > 1) { 100 | report(context.trace) { COMPONENT_WITH_MULTIPLE_FACTORIES.on(it, factories) } 101 | } 102 | 103 | if (builders.size > 1) { 104 | report(context.trace) { COMPONENT_WITH_MULTIPLE_BUILDERS.on(it, builders) } 105 | } 106 | 107 | if (factories.isNotEmpty() && builders.isNotEmpty()) { 108 | report(context.trace) { COMPONENT_WITH_FACTORY_AND_BUILDER.on(it, factories, builders) } 109 | } 110 | 111 | return factories.firstOrNull()?.let { FactoryDescriptor(definition, it, componentAnnotation, context) } 112 | ?: builders.firstOrNull()?.let { BuilderDescriptor(definition, it, componentAnnotation, context) } 113 | } 114 | 115 | private fun componentBinding() = Binding( 116 | Key(definition.defaultType, emptyList()), 117 | emptyList(), 118 | Binding.Variation.Component(definition) 119 | ) 120 | } 121 | 122 | private val DAGGER_COMPONENT_ANNOTATION = FqName("dagger.Component") 123 | private val DAGGER_FACTORY_ANNOTATION = FqName("dagger.Component.Factory") 124 | private val DAGGER_BUILDER_ANNOTATION = FqName("dagger.Component.Builder") 125 | 126 | private fun ClassDescriptor.innerClasses() = 127 | unsubstitutedMemberScope.getDescriptorsFiltered(DescriptorKindFilter.CLASSIFIERS) { true } 128 | .filterIsInstance() 129 | .filter { it.kind == ClassKind.INTERFACE || (it.kind == ClassKind.CLASS && it.modality == Modality.ABSTRACT) } 130 | 131 | fun ClassDescriptor.isComponent() = 132 | annotations.hasAnnotation(DAGGER_COMPONENT_ANNOTATION) 133 | -------------------------------------------------------------------------------- /testing/integration/src/test/kotlin/me/shika/di/factory/FactoryImplicitModulesTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Dagger Authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package me.shika.di.factory 17 | 18 | import com.google.common.truth.Truth.assertThat 19 | import dagger.Component 20 | import org.junit.Assert.fail 21 | import org.junit.Test 22 | import org.junit.runner.RunWith 23 | import org.junit.runners.JUnit4 24 | 25 | /** 26 | * Tests for factories for components with modules that do not require an instance to be passed to 27 | * the factory. Includes both tests where the module does not have a corresponding parameter in the 28 | * factory method and where it does have a parameter, for cases where that's allowed. 29 | */ 30 | @RunWith(JUnit4::class) 31 | class FactoryImplicitModulesTest { 32 | @Component(modules = [AbstractModule::class]) 33 | internal interface AbstractModuleComponent { 34 | fun string(): String? 35 | @Component.Factory 36 | interface Factory { 37 | fun create(): AbstractModuleComponent? 38 | } 39 | } 40 | 41 | @Test 42 | fun abstractModule() { 43 | val component: AbstractModuleComponent = 44 | DaggerFactoryImplicitModulesTest_AbstractModuleComponent.factory().create() 45 | assertThat(component.string()).isEqualTo("foo") 46 | } 47 | 48 | @Component(modules = [InstantiableConcreteModule::class]) 49 | internal interface InstantiableConcreteModuleComponent { 50 | val int: Int 51 | 52 | @Component.Factory 53 | interface Factory { 54 | fun create(): InstantiableConcreteModuleComponent? 55 | } 56 | } 57 | 58 | @Test 59 | fun instantiableConcreteModule() { 60 | val component: InstantiableConcreteModuleComponent = 61 | DaggerFactoryImplicitModulesTest_InstantiableConcreteModuleComponent.factory().create() 62 | assertThat(component.int).isEqualTo(42) 63 | } 64 | 65 | @Component(modules = [InstantiableConcreteModule::class]) 66 | internal interface InstantiableConcreteModuleWithFactoryParameterComponent { 67 | val int: Int 68 | 69 | @Component.Factory 70 | interface Factory { 71 | fun create( 72 | module: InstantiableConcreteModule? 73 | ): InstantiableConcreteModuleWithFactoryParameterComponent? 74 | } 75 | } 76 | 77 | @Test 78 | fun instantiableConcreteModule_withFactoryParameter() { 79 | val component: InstantiableConcreteModuleWithFactoryParameterComponent = 80 | DaggerFactoryImplicitModulesTest_InstantiableConcreteModuleWithFactoryParameterComponent 81 | .factory() 82 | .create(InstantiableConcreteModule()) 83 | assertThat(component.int).isEqualTo(42) 84 | } 85 | 86 | @Test 87 | fun instantiableConcreteModule_withFactoryParameter_failsOnNull() { 88 | try { 89 | DaggerFactoryImplicitModulesTest_InstantiableConcreteModuleWithFactoryParameterComponent 90 | .factory() 91 | .create(null) 92 | fail() 93 | } catch (expected: NullPointerException) { 94 | } 95 | } 96 | 97 | @Component(modules = [ConcreteModuleThatCouldBeAbstract::class]) 98 | internal interface ConcreteModuleThatCouldBeAbstractComponent { 99 | val double: Double 100 | 101 | @Component.Factory 102 | interface Factory { 103 | fun create(): ConcreteModuleThatCouldBeAbstractComponent? 104 | } 105 | } 106 | 107 | @Test 108 | fun concreteModuleThatCouldBeAbstract() { 109 | val component: ConcreteModuleThatCouldBeAbstractComponent = 110 | DaggerFactoryImplicitModulesTest_ConcreteModuleThatCouldBeAbstractComponent.factory() 111 | .create() 112 | assertThat(component.double).isEqualTo(42.0) 113 | } 114 | 115 | @Component(modules = [ConcreteModuleThatCouldBeAbstract::class]) 116 | internal interface ConcreteModuleThatCouldBeAbstractWithFactoryParameterComponent { 117 | val double: Double 118 | 119 | @Component.Factory 120 | interface Factory { 121 | fun create( 122 | module: ConcreteModuleThatCouldBeAbstract? 123 | ): ConcreteModuleThatCouldBeAbstractWithFactoryParameterComponent? 124 | } 125 | } 126 | 127 | @Test 128 | fun concreteModuleThatCouldBeAbstract_withFactoryParameter() { 129 | val component: ConcreteModuleThatCouldBeAbstractWithFactoryParameterComponent = 130 | DaggerFactoryImplicitModulesTest_ConcreteModuleThatCouldBeAbstractWithFactoryParameterComponent 131 | .factory() 132 | .create(ConcreteModuleThatCouldBeAbstract()) 133 | assertThat(component.double).isEqualTo(42.0) 134 | } 135 | 136 | @Test 137 | fun concreteModuleThatCouldBeAbstract_withFactoryParameter_failsOnNull() { // This matches what builders do when there's a setter for such a module; the setter checks that 138 | // the argument is not null but otherwise ignores it. 139 | // It's possible that we shouldn't even allow such a parameter for a factory, since unlike a 140 | // builder, where the setter can just not be called, a factory doesn't give the option of not 141 | // passing *something* for the unused parameter. 142 | try { 143 | val component: ConcreteModuleThatCouldBeAbstractWithFactoryParameterComponent = 144 | DaggerFactoryImplicitModulesTest_ConcreteModuleThatCouldBeAbstractWithFactoryParameterComponent 145 | .factory() 146 | .create(null) 147 | fail() 148 | } catch (expected: NullPointerException) { 149 | } 150 | } 151 | } 152 | --------------------------------------------------------------------------------