Skip to content

Commit

Permalink
Merge pull request #75 from eygraber/subcomponent-override-properties
Browse files Browse the repository at this point in the history
Add an override modifier if a subcomponent factory parameter matches a property in the subcomponent
  • Loading branch information
vRallev authored Nov 12, 2024
2 parents 9110814 + 815fce3 commit 9ccb50d
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSValueParameter
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
Expand Down Expand Up @@ -130,6 +131,7 @@ internal class ContributesSubcomponentProcessor(
qualifier = valueParameter.annotations
.filter { it.isKotlinInjectQualifierAnnotation() }
.singleOrNull(),
isOverride = hasMatchingProperty(valueParameter, subcomponent),
)
}

Expand Down Expand Up @@ -202,6 +204,11 @@ internal class ContributesSubcomponentProcessor(
parameters.map { parameter ->
PropertySpec.builder(parameter.name, parameter.typeName)
.initializer(parameter.name)
.apply {
if (parameter.isOverride) {
addModifiers(KModifier.OVERRIDE)
}
}
.build()
},
)
Expand Down Expand Up @@ -270,5 +277,35 @@ internal class ContributesSubcomponentProcessor(
val name: String,
val typeName: TypeName,
val qualifier: KSAnnotation?,
val isOverride: Boolean,
)
}

private fun hasMatchingProperty(
parameter: KSValueParameter,
classDeclaration: KSClassDeclaration,
): Boolean {
val parameterName = parameter.name?.asString()
val parameterType = parameter.type.resolve()

// Function to check properties in a class declaration
fun checkPropertiesInClass(klass: KSClassDeclaration): Boolean {
return klass.getAllProperties().any { property ->
property.simpleName.asString() == parameterName &&
property.type.resolve() == parameterType
}
}

// Check the current class and its ancestors
var currentClass: KSClassDeclaration? = classDeclaration
while (currentClass != null) {
if (checkPropertiesInClass(currentClass)) {
return true
}
currentClass = currentClass.superTypes
.mapNotNull { it.resolve().declaration as? KSClassDeclaration }
.firstOrNull()
}

return false
}
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,61 @@ class ContributesSubcomponentProcessorTest {
}
}

@Test
fun `the factory function accepts parameters that will override a property in the component and the parameters are bound in the component`() {
compile(
"""
package software.amazon.test
import software.amazon.lastmile.kotlin.inject.anvil.AppScope
import software.amazon.lastmile.kotlin.inject.anvil.ContributesSubcomponent
import software.amazon.lastmile.kotlin.inject.anvil.ContributesTo
import software.amazon.lastmile.kotlin.inject.anvil.MergeComponent
import software.amazon.lastmile.kotlin.inject.anvil.SingleIn
import me.tatarka.inject.annotations.Component
import me.tatarka.inject.annotations.Provides
@Component
@MergeComponent(AppScope::class)
@SingleIn(AppScope::class)
interface ComponentInterface : ComponentInterfaceMerged
interface StringProvider {
val stringArg: String
}
@ContributesSubcomponent(LoggedInScope::class)
@SingleIn(LoggedInScope::class)
interface OtherComponent : StringProvider {
@ContributesSubcomponent.Factory(AppScope::class)
interface Parent {
fun otherComponent(stringArg: String, intArg: Int): OtherComponent
}
}
@ContributesTo(LoggedInScope::class)
interface ChildComponent {
val string: String
val int: Int
}
""",
scopesSource,
) {
val component = componentInterface.newComponent<Any>()
val childComponent = component::class.java.methods
.single { it.name == "otherComponent" }
.invoke(component, "some string", 5)

assertThat(childComponent).isNotNull()

// sanity check; the fact that compilation succeeded means the test passes
assertThat(
childComponent::class.java.methods
.find { it.name == "getStringArg" },
).isNotNull()
}
}

@Test
fun `abstract classes are disallowed`() {
compile(
Expand Down

0 comments on commit 9ccb50d

Please sign in to comment.