Skip to content

Commit

Permalink
Merge pull request #139 from bitPogo/feature/add-gernics-to-multi-int…
Browse files Browse the repository at this point in the history
…erfacces-common

Add support for generic mulit-interface mocks in Common
  • Loading branch information
bitPogo authored May 12, 2022
2 parents be472dc + 1f11d0b commit 6cc9672
Show file tree
Hide file tree
Showing 38 changed files with 2,901 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ import com.squareup.kotlinpoet.ksp.toTypeVariableName
import tech.antibytes.kmock.processor.ProcessorContract.GenericDeclaration
import tech.antibytes.kmock.processor.ProcessorContract.GenericResolver
import tech.antibytes.kmock.processor.ProcessorContract.TemplateSource
import tech.antibytes.kmock.processor.utils.mapArgumentType

internal object KMockGenerics : GenericResolver {
private val any = Any::class.asTypeName()
private val nullableAnys = listOf(any.copy(nullable = true))
private val nonNullableAnys = listOf(any.copy(nullable = false))
private const val TYPE_PARAMETER = "KMockTypeParameter"

private fun resolveBound(type: KSTypeParameter): List<KSTypeReference> = type.bounds.toList()

Expand Down Expand Up @@ -59,6 +61,78 @@ internal object KMockGenerics : GenericResolver {
)
}

private fun mapTypes(
generics: Map<String, List<KSTypeReference>>,
suffix: Int,
): Map<String, String> {
var counter = 0 + suffix
return generics.map { (typeName, _) ->
Pair(typeName, "$TYPE_PARAMETER$counter").also { counter++ }
}.toMap()
}

private fun mapDeclaredGenericsWithSuffix(
generics: Map<String, List<KSTypeReference>>,
suffix: Int,
typeResolver: TypeParameterResolver
): List<TypeVariableName> {
var counter = 0 + suffix
val mapping = mapTypes(generics, suffix)
return generics.map { (_, bounds) ->
TypeVariableName(
"$TYPE_PARAMETER$counter",
bounds = bounds.map { ksReference ->
ksReference.mapArgumentType(
typeParameterResolver = typeResolver,
mapping = mapping,
)
}
).also { counter++ }
}
}

private fun resolveTypeParameter(
typeParameter: Map<String, List<KSTypeReference>>?,
typeResolver: TypeParameterResolver,
suffix: Int,
): List<TypeVariableName> {
return if (typeParameter == null) {
emptyList()
} else {
mapDeclaredGenericsWithSuffix(
generics = typeParameter,
typeResolver = typeResolver,
suffix = suffix,
)
}
}

override fun remapTypes(
templates: List<KSClassDeclaration>,
generics: List<Map<String, List<KSTypeReference>>?>
): Pair<List<TypeName>, List<TypeVariableName>> {
var counter = 0
val aggregatedTypeParameter: MutableList<TypeVariableName> = mutableListOf()
val parameterizedParents = templates.mapIndexed { idx, parent ->
val typeParameter = resolveTypeParameter(
typeParameter = generics[idx],
typeResolver = parent.typeParameters.toTypeParameterResolver(),
suffix = counter
)
val raw = parent.toClassName()
counter += generics[idx]?.size ?: 0

if (typeParameter.isNotEmpty()) {
aggregatedTypeParameter.addAll(typeParameter)
raw.parameterizedBy(typeParameter)
} else {
raw
}
}

return Pair(parameterizedParents, aggregatedTypeParameter)
}

private fun isNullable(type: KSType): Boolean = type.nullability == Nullability.NULLABLE

private fun anys(rootNullability: Boolean): List<TypeName> {
Expand Down Expand Up @@ -357,7 +431,9 @@ internal object KMockGenerics : GenericResolver {
} else {
template.toClassName()
.parameterizedBy(
template.typeParameters.map { type -> type.toTypeVariableName(resolver) }
template.typeParameters.map { type ->
type.toTypeVariableName(resolver)
}
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSFile
import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview
import tech.antibytes.kmock.processor.ProcessorContract.Aggregated
import tech.antibytes.kmock.processor.ProcessorContract.KmpCodeGenerator
import tech.antibytes.kmock.processor.ProcessorContract.MockFactoryEntryPointGenerator
Expand All @@ -32,6 +31,7 @@ import tech.antibytes.kmock.processor.ProcessorContract.TemplateSource
*/
internal class KMockProcessor(
private val logger: KSPLogger,
private val isUnderCompilerTest: Boolean, // TODO - Test Concern see: https://github.com/tschuchortdev/kotlin-compile-testing/issues/263
private val isKmp: Boolean,
private val codeGenerator: KmpCodeGenerator,
private val interfaceGenerator: MultiInterfaceBinder,
Expand Down Expand Up @@ -118,6 +118,18 @@ internal class KMockProcessor(
commonMultiAggregated.extractedTemplates.isEmpty() // TODO - Test Concern see: https://github.com/tschuchortdev/kotlin-compile-testing/issues/263
}

// TODO - Test Concern see: https://github.com/tschuchortdev/kotlin-compile-testing/issues/263
private fun resolveMultiSources(
aggregated: Aggregated<TemplateMultiSource>,
stored: Aggregated<TemplateMultiSource>,
): List<TemplateMultiSource> {
return if (isUnderCompilerTest) {
aggregated.extractedTemplates
} else {
stored.extractedTemplates
}
}

private fun stubCommonSources(
resolver: Resolver,
relaxer: Relaxer?
Expand All @@ -127,7 +139,7 @@ internal class KMockProcessor(

mockGenerator.writeCommonMocks(
templateSources = singleCommonSources.extractedTemplates,
templateMultiSources = commonMultiAggregated,
templateMultiSources = resolveMultiSources(multiCommonSources, commonMultiAggregated),
relaxer = relaxer,
)

Expand Down Expand Up @@ -250,7 +262,6 @@ internal class KMockProcessor(
return relaxer
}

@OptIn(KotlinPoetKspPreview::class)
override fun process(resolver: Resolver): List<KSAnnotated> {
val relaxer = extractRelaxer(resolver)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ import tech.antibytes.kmock.processor.utils.SourceFilter
import tech.antibytes.kmock.processor.utils.SourceSetValidator
import tech.antibytes.kmock.processor.utils.SpyContainer

class KMockProcessorProvider : SymbolProcessorProvider {
class KMockProcessorProvider(
private val isUnderCompilerTest: Boolean = false
) : SymbolProcessorProvider {
private fun determineFactoryGenerator(
options: Options,
logger: KSPLogger,
Expand Down Expand Up @@ -73,8 +75,10 @@ class KMockProcessorProvider : SymbolProcessorProvider {
genericResolver = KMockGenerics,
),
multiInterfaceGenerator = KMockFactoryMultiInterfaceGenerator(
isKmp = options.isKmp,
spyContainer = spyContainer,
utils = factoryUtils,
genericResolver = KMockGenerics,
),
spyContainer = spyContainer,
spiesOnly = options.spiesOnly,
Expand Down Expand Up @@ -155,12 +159,14 @@ class KMockProcessorProvider : SymbolProcessorProvider {

return KMockProcessor(
logger = logger,
isUnderCompilerTest = isUnderCompilerTest,
isKmp = options.isKmp,
codeGenerator = codeGenerator,
interfaceGenerator = KMockMultiInterfaceBinder(
logger = logger,
rootPackage = options.rootPackage,
codeGenerator = codeGenerator
genericResolver = KMockGenerics,
codeGenerator = codeGenerator,
),
mockGenerator = KMockGenerator(
logger = logger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ internal interface ProcessorContract {
typeResolver: TypeParameterResolver
): List<TypeVariableName>

fun remapTypes(
templates: List<KSClassDeclaration>,
generics: List<Map<String, List<KSTypeReference>>?>
): Pair<List<TypeName>, List<TypeVariableName>>

fun mapProxyGenerics(
generics: Map<String, List<KSTypeReference>>,
typeResolver: TypeParameterResolver
Expand Down Expand Up @@ -323,6 +328,7 @@ internal interface ProcessorContract {
ksFunction: KSFunctionDeclaration,
typeResolver: TypeParameterResolver,
enableSpy: Boolean,
inherited: Boolean,
relaxer: Relaxer?,
): Pair<PropertySpec?, FunSpec>
}
Expand All @@ -348,16 +354,16 @@ internal interface ProcessorContract {

fun writeCommonMocks(
templateSources: List<TemplateSource>,
templateMultiSources: Aggregated<TemplateMultiSource>,
templateMultiSources: List<TemplateMultiSource>,
relaxer: Relaxer?
)
}

fun interface ParentFinder {
fun find(
templateSource: TemplateSource,
templateMultiSources: Aggregated<TemplateMultiSource>,
): List<KSClassDeclaration>
templateMultiSources: List<TemplateMultiSource>,
): TemplateMultiSource?
}

interface MultiInterfaceBinder {
Expand Down Expand Up @@ -396,8 +402,6 @@ internal interface ProcessorContract {
fun resolveGenerics(templateSource: TemplateSource): List<TypeVariableName>

fun resolveModifier(): KModifier?

fun toTypeNames(types: List<KSClassDeclaration>): List<TypeName>
}

interface MockFactoryWithoutGenerics {
Expand All @@ -417,6 +421,12 @@ internal interface ProcessorContract {
val shared: FunSpec
)

data class FactoryMultiBundle(
val kmock: FunSpec?,
val kspy: FunSpec?,
val shared: FunSpec?
)

interface MockFactoryWithGenerics {
fun buildGenericFactories(
templateSources: List<TemplateSource>,
Expand All @@ -425,10 +435,10 @@ internal interface ProcessorContract {
}

interface MockFactoryMultiInterface {
fun buildSpyFactory(
fun buildFactories(
templateMultiSources: List<TemplateMultiSource>,
relaxer: Relaxer?
): List<FunSpec>
): List<FactoryMultiBundle>
}

interface MockFactoryGenerator {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import tech.antibytes.kmock.processor.ProcessorContract.MockFactoryGeneratorUtil
import tech.antibytes.kmock.processor.ProcessorContract.SpyContainer
import tech.antibytes.kmock.processor.ProcessorContract.TemplateMultiSource
import tech.antibytes.kmock.processor.ProcessorContract.TemplateSource
import tech.antibytes.kmock.processor.multi.hasGenerics

internal class KMockFactoryEntryPointGenerator(
private val isKmp: Boolean,
Expand Down Expand Up @@ -150,21 +151,75 @@ internal class KMockFactoryEntryPointGenerator(
).build()
}

private fun buildMultiInterfaceSpyFactory(
private fun buildMultiInterfaceGenericKMockFactory(
boundaries: List<TypeName>,
generics: List<TypeVariableName>
): FunSpec {
val mockType = TypeVariableName(KMOCK_FACTORY_TYPE_NAME).copy(bounds = boundaries)

return utils.generateKmockSignature(
type = mockType,
generics = emptyList(),
hasDefault = true,
modifier = KModifier.EXPECT
).addTypeVariables(generics).build()
}

private fun buildMultiInterfaceGenericKSpyFactory(
boundaries: List<TypeName>,
generics: List<TypeVariableName>
): FunSpec {
val spyType = TypeVariableName(
KSPY_FACTORY_TYPE_NAME,
bounds = boundaries,
)

val mockType = TypeVariableName(KMOCK_FACTORY_TYPE_NAME, bounds = listOf(spyType))

return utils.generateKspySignature(
mockType = mockType,
spyType = spyType,
generics = emptyList(),
hasDefault = true,
modifier = KModifier.EXPECT
).addTypeVariables(generics).build()
}

private fun TemplateMultiSource.isSpyable(): Boolean {
return spyContainer.isSpyable(null, this.packageName, this.templateName)
}

private fun resolveGenericMultiInterfaceFactories(
templateSource: TemplateMultiSource,
boundaries: List<TypeName>,
generics: List<TypeVariableName>
): FunSpec {
return if (templateSource.isSpyable()) {
buildMultiInterfaceGenericKSpyFactory(boundaries, generics)
} else {
buildMultiInterfaceGenericKMockFactory(boundaries, generics)
}
}

private fun buildMultiInterfaceFactory(
templateSource: TemplateMultiSource
): FunSpec? {
return if (spyContainer.isSpyable(null, templateSource.packageName, templateSource.templateName)) {
buildMultiInterfaceSpyFactory(utils.toTypeNames(templateSource.templates))
} else {
null
val (types, generics) = genericResolver.remapTypes(templateSource.templates, templateSource.generics)

return when {
templateSource.isSpyable() && !templateSource.hasGenerics() -> {
buildMultiInterfaceSpyFactory(types)
}
templateSource.hasGenerics() -> resolveGenericMultiInterfaceFactories(templateSource, types, generics)
else -> null
}
}

private fun FileSpec.Builder.generateMultiInterfaceEntryPoints(
templateSources: List<TemplateMultiSource>
) {
templateSources.forEach { source ->
val factory = buildMultiInterfaceSpyFactory(source)
val factory = buildMultiInterfaceFactory(source)

if (factory != null) {
this.addFunction(factory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,23 @@ internal class KMockFactoryGenerator(
}
}

val multiInterfaceMocks = multiInterfaceGenerator.buildSpyFactory(
val multiInterfaceMocks = multiInterfaceGenerator.buildFactories(
templateMultiSources = templateMultiSources,
relaxer = relaxer
)

multiInterfaceMocks.forEach { factory ->
file.addFunction(factory)
multiInterfaceMocks.forEach { factories ->
if (factories.shared != null) {
file.addFunction(factories.shared)
}

if (factories.kmock != null && !spiesOnly) {
file.addFunction(factories.kmock)
}

if (factories.kspy != null) {
file.addFunction(factories.kspy)
}
}

file.build().writeTo(
Expand Down
Loading

0 comments on commit 6cc9672

Please sign in to comment.