Eric
03/15/2024, 7:56 PMtoKClass()
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("...")
igor.wojda
03/20/2024, 6:27 PMEric
03/20/2024, 6:29 PM@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)
}
}
Eric
03/20/2024, 6:30 PM@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" }
}
}
}
}
igor.wojda
03/20/2024, 7:41 PMisAssignableFrom
, but I see valid use case for toKClass
(and perhaps toClass
)
Perhaps we could make it even better by utilizing extension functions e.g.:
// 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
.filter {
it.name.startsWith("create") ||
it.name.startsWith("update") ||
it.name.startsWith("patch") ||
it.name.startsWith("save")
}
can be simplified
.withNameStartingWith("create", "update", "patch", "save")
2. This can be also simplified
if (paramType.isKotlinCollectionType || paramType.isKotlinBasicType)
to
if (paramType.isKotlinType)
3. This can be also simplified
annotation.fullyQualifiedName == Transactional::class.java.name
to
annotation.representsTypeOf<Transactional>()
4. In 2nd test...
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 lambdaEric
03/20/2024, 7:42 PMEric
03/20/2024, 7:42 PMEric
03/20/2024, 7:43 PMisAssignableFrom
because the parents (parentInterfaces(external = true)
or something) functions didn't seem to do what i expected.igor.wojda
03/20/2024, 7:45 PM0.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)