In our previous project, we have a class called AbstractActor to be inherited to do specific stuff, and some of our devs might hold its reference as a constructed valOrVar parameter or KTProperty, which eventually had led to a garbage collection disaster, the actor could not be collected since it was referenced and the memory usage went rocket high. So my leader asks me to create a new rule to prevent this, I have tried many ways yet I just couldn't get the KClass to determine if it is isAssignableFrom or isSubclassOf to the AbstractActor class. Here is the test example I created
class ActorRefTest {
@Test
fun noExplicitlyReferencingActorRule() {
val env = createEnvironment()
val findings = NoExplicitlyReferencingActorRule(Config.empty).compileAndLintWithContext(
env.env, """
package testRule
open class TypedActor : AbstractActor() {
override fun receive(msg: Any) {
}
}
class Typed1Actor : TypedActor() {
}
abstract class AbstractActor {
abstract fun receive(msg: Any)
}
//@Suppress("NoExplicitlyReferencingActorRule")
class ExplicitlyReferencingActor(val actor: Typed1Actor, val isEnabled: Boolean) {
// lateinit var actor2: AbstractActor
val isEnabled : Boolean = false
}
""".trimIndent()
)
findings.forEach {
println(it)
}
assert(findings.isNotEmpty())
}
}
And this is the rule I created
package testRuleSet
import io.gitlab.arturbosch.detekt.api.*
import io.gitlab.arturbosch.detekt.rules.identifierName
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.typeBinding.createTypeBindingForReturnType
class NoExplicitlyReferencingActorRule(config: Config) : Rule(config) {
override val issue: Issue = Issue(
javaClass.simpleName,
Severity.Style,
"Customized-NoExplicitlyReferencingActor",
Debt.TEN_MINS,
)
override fun visitParameter(parameter: KtParameter) {
super.visitParameter(parameter)
if (parameter.hasValOrVar()) {
if (validateDeclaration(parameter)) {
reportCodeSmell(parameter)
}
}
}
private fun isSubClassOfAbsractActor(parameter: KtCallableDeclaration): Boolean {
return parameter.createTypeBindingForReturnType(bindingContext)?.type?.constructor?.supertypes?.find {
it.getJetTypeFqName(
false
) == "testRule.AbstractActor"
} != null
}
private fun validateDeclaration(declaration: KtCallableDeclaration): Boolean {
if (bindingContext == BindingContext.EMPTY) {
return false
}
return isSubClassOfAbsractActor(declaration)
}
override fun visitProperty(property: KtProperty) {
super.visitProperty(property)
if (property.isMember) {
if (validateDeclaration(property)) {
reportCodeSmell(property)
}
}
}
private fun reportCodeSmell(declaration: KtCallableDeclaration) {
report(
CodeSmell(
issue,
Entity.from(requireNotNull(declaration.colon)),
"do not hold any subclass of AbsractActor as a explicit reference"
)
)
}
}
From what I see in the stacktrace, detekt and compiler know the exact package and identifier name but I just couldn't get it right, what am I missing here?