![JAR search and dependency download from the Maven repository](/logo.png)
io.gitlab.arturbosch.detekt.rules.style.UnnecessaryInnerClass.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of detekt-rules-style Show documentation
Show all versions of detekt-rules-style Show documentation
Static code analysis for Kotlin
The newest version!
package io.gitlab.arturbosch.detekt.rules.style
import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution
import org.jetbrains.kotlin.backend.common.peek
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.psi.KtThisExpression
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
/**
* This rule reports unnecessary inner classes. Nested classes that do not access members from the outer class do
* not require the `inner` qualifier.
*
*
* class A {
* val foo = "BAR"
*
* inner class B {
* val fizz = "BUZZ"
*
* fun printFizz() {
* println(fizz)
* }
* }
* }
*
*/
@Suppress("TooManyFunctions")
@RequiresTypeResolution
class UnnecessaryInnerClass(config: Config = Config.empty) : Rule(config) {
private val candidateClassToParentClasses = mutableMapOf>()
private val classChain = ArrayDeque()
override val issue: Issue = Issue(
javaClass.simpleName,
Severity.Style,
"The 'inner' qualifier is unnecessary.",
Debt.FIVE_MINS
)
override fun visitClass(klass: KtClass) {
classChain.add(klass)
if (klass.isInner()) {
candidateClassToParentClasses[klass] = findParentClasses(klass)
}
// Visit the class to determine whether it contains any references
// to outer class members.
super.visitClass(klass)
if (klass.isInner() && candidateClassToParentClasses.contains(klass)) {
report(
CodeSmell(
issue,
Entity.Companion.from(klass),
"Class '${klass.name}' does not require `inner` keyword."
)
)
candidateClassToParentClasses.remove(klass)
}
classChain.pop()
}
override fun visitReferenceExpression(expression: KtReferenceExpression) {
super.visitReferenceExpression(expression)
checkForOuterUsage { findResolvedContainingClassId(expression) }
}
override fun visitThisExpression(expression: KtThisExpression) {
checkForOuterUsage { expression.referenceClassId() }
}
// Replace this "constructor().apply{}" pattern with buildList() when the Kotlin
// API version is upgraded to 1.6
private fun findParentClasses(ktClass: KtClass): List = ArrayList().apply {
var containingClass = ktClass.containingClass()
while (containingClass != null) {
add(containingClass)
containingClass = containingClass.containingClass()
}
}
private fun checkForOuterUsage(getTargetClassId: () -> ClassId?) {
val currentClass = classChain.peek() ?: return
val parentClasses = candidateClassToParentClasses[currentClass] ?: return
val targetClassId = getTargetClassId() ?: return
/*
* If class A -> inner class B -> inner class C, and class C has outer usage of A,
* then both B and C should stay as inner classes.
*/
val index = parentClasses.indexOfFirst { it.getClassId() == targetClassId }
if (index >= 0) {
candidateClassToParentClasses.remove(currentClass)
parentClasses.subList(0, index).forEach { candidateClassToParentClasses.remove(it) }
}
}
private fun findResolvedContainingClassId(expression: KtReferenceExpression): ClassId? =
(bindingContext[BindingContext.REFERENCE_TARGET, expression]?.containingDeclaration as? ClassifierDescriptor)
?.classId
private fun KtThisExpression.referenceClassId(): ClassId? {
return getResolvedCall(bindingContext)
?.resultingDescriptor
?.returnType
?.constructor
?.declarationDescriptor
?.classId
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy