https://kotlinlang.org logo
#konsist
Title
# konsist
e

Eric

03/15/2024, 7:56 PM
Would be nice if most of the interfaces had a
toKClass()
function (maybe
toClass()
also) so we can go directly to the Kotlin reflection data instead of wrangling parts of names in to a FQCN and using
Class.forName("...")
i

igor.wojda

03/20/2024, 6:27 PM
Can you give an example here? Perhaps drop a code snippet.
e

Eric

03/20/2024, 6:29 PM
Copy code
@Test
    fun `test all repositories implement JpaRepository`() {
        Konsist
            .scopeFromProduction()
            .interfaces()
            .filter {
                val clazz = try {
                    Class.forName(it.fullyQualifiedName)
                } catch (e: ClassNotFoundException) {
                    return@filter false
                }
                Repository::class.java.isAssignableFrom(clazz)
            }
            .assertTrue {
                val clazz = Class.forName(it.fullyQualifiedName)
                JpaRepository::class.java.isAssignableFrom(clazz)
            }
    }
Copy code
@Test
    fun `test classes that have a Repository injected have the proper @Transactional annotations`() {
        Konsist
            .scopeFromProduction()
            .classes()
            .withAnnotationOf(Component::class, Service::class)
            .withConstructor { ctor ->
                ctor.hasParameter { param ->
                    val paramType = param.type
                    if (paramType.isKotlinCollectionType || paramType.isKotlinBasicType) return@hasParameter false
                    val paramClassName =
                        "${paramType.declaration.packagee?.fullyQualifiedName.orEmpty()}.${paramType.declaration.name}"
                    val clazz = Class.forName(paramClassName)
                    JpaRepository::class.java.isAssignableFrom(clazz)
                }
            }
            .assertTrue { clazz ->
                clazz.hasAnnotation { annotation ->
                    annotation.fullyQualifiedName == Transactional::class.java.name &&
                            annotation.hasArgument { it.name == "readOnly" && it.value == "true" }
                }
                clazz.functions()
                    .filter { it.hasPublicOrDefaultModifier }
                    .filter {
                        it.name.startsWith("create") ||
                                it.name.startsWith("update") ||
                                it.name.startsWith("patch") ||
                                it.name.startsWith("save")
                    }
                    .all { function ->
                        function.hasAnnotation { annotation ->
                            annotation.fullyQualifiedName == Transactional::class.java.name &&
                                    annotation.hasArgument { it.name == "readOnly" && it.value == "false" }
                        }
                    }
            }
    }
i

igor.wojda

03/20/2024, 7:41 PM
Interesting. I have to wrap my hear around
isAssignableFrom
, but I see valid use case for
toKClass
(and perhaps
toClass
) Perhaps we could make it even better by utilizing extension functions e.g.:
Copy code
// Extension
private fun <T> Class<T>.isAssignableFrom(it: KoInterfaceDeclaration): Boolean { ... }
// .. extensions for other KoDeclatations

// Konsist APU
Konsist
    .scopeFromProduction()
    .interfaces()
    .filter {
        JpaRepository::class.java.isAssignableFrom(it)
    }
Few tips: 1. This part
Copy code
.filter {
                    it.name.startsWith("create") ||
                            it.name.startsWith("update") ||
                            it.name.startsWith("patch") ||
                            it.name.startsWith("save")
                }
can be simplified
Copy code
.withNameStartingWith("create", "update", "patch", "save")
2. This can be also simplified
Copy code
if (paramType.isKotlinCollectionType || paramType.isKotlinBasicType)
to
Copy code
if (paramType.isKotlinType)
3. This can be also simplified
Copy code
annotation.fullyQualifiedName == Transactional::class.java.name
to
Copy code
annotation.representsTypeOf<Transactional>()
4. In 2nd test...
Copy code
clazz.hasAnnotation { annotation ->
                    annotation.fullyQualifiedName == Transactional::class.java.name &&
                            annotation.hasArgument { it.name == "readOnly" && it.value == "true" }
                }
... is ignored - only last expression inside lambda is used as value returned by lambda
e

Eric

03/20/2024, 7:42 PM
thanks, will clean that up a bit
i almost added some ext funs, but got lazy at the end
i ended up using
isAssignableFrom
because the parents (
parentInterfaces(external = true)
or something) functions didn't seem to do what i expected.
i

igor.wojda

03/20/2024, 7:45 PM
This is a new feature just introduced in
0.14.0
so there may be some valid scenarios for improvement - if you will find some time please drop some feedback on
parentInterfaces
usage (code snippet or sample project to allow us to deep dive into issue and improve Konsist)
2 Views