Do not suggest "convert to object" for sealed sub-classes with state

So #KT-24816 Fixed
This commit is contained in:
Mikhail Glukhikh
2018-06-08 14:45:45 +03:00
parent 813d7fcb6a
commit a09f6e14a5
8 changed files with 118 additions and 3 deletions
@@ -20,13 +20,18 @@ import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.asJava.classes.KtLightClassImpl
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.getModalityFromDescriptor
import org.jetbrains.kotlin.idea.quickfix.sealedSubClassToObject.ConvertSealedSubClassToObjectFix
import org.jetbrains.kotlin.idea.refactoring.isAbstract
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtVisitorVoid
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
class CanSealedSubClassBeObjectInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
@@ -35,13 +40,16 @@ class CanSealedSubClassBeObjectInspection : AbstractKotlinInspection() {
if (!klass.hasModifier(KtTokens.SEALED_KEYWORD)) return
if (klass.getModalityFromDescriptor() != KtTokens.SEALED_KEYWORD) return
klass.getSubclasses()
val candidates = klass.getSubclasses()
.withEmptyConstructors()
.thatAreFinal()
.thatHasNoTypeParameters()
.thatHasNoInnerClasses()
.thatHasNoCompanionObjects()
.forEach { reportPossibleObject(it) }
.thatHasNoState()
if (candidates.isEmpty() || !klass.hasNoState() || !klass.baseClassHasNoState()) return
candidates.forEach { reportPossibleObject(it) }
}
private fun reportPossibleObject(klass: KtClass) {
@@ -56,6 +64,14 @@ class CanSealedSubClassBeObjectInspection : AbstractKotlinInspection() {
}
}
private tailrec fun KtClass.baseClassHasNoState(): Boolean {
val descriptor = resolveToDescriptorIfAny() ?: return false
val superDescriptor = descriptor.getSuperClassNotAny() ?: return true // No super class -- no state
val superClass = DescriptorToSourceUtils.descriptorToDeclaration(superDescriptor) as? KtClass ?: return false
if (!superClass.hasNoState()) return false
return superClass.baseClassHasNoState()
}
private fun KtClass.getSubclasses(): List<KtLightClassImpl> {
return HierarchySearchRequest(this, this.useScope, false)
.searchInheritors().filterIsInstance<KtLightClassImpl>()
@@ -83,6 +99,28 @@ class CanSealedSubClassBeObjectInspection : AbstractKotlinInspection() {
return filter { klass -> klass.hasNoInnerClass() }
}
private fun List<KtClass>.thatHasNoState(): List<KtClass> {
return filter { it.hasNoState() }
}
private fun KtClass.hasNoState(): Boolean {
if (primaryConstructor?.valueParameters?.isNotEmpty() == true) return false
val body = getBody()
return body == null || run {
val properties = body.declarations.filterIsInstance<KtProperty>()
properties.none { property ->
// Simplified "backing field required"
when {
property.isAbstract() -> false
property.initializer != null -> true
property.delegate != null -> false
!property.isVar -> property.getter == null
else -> property.getter == null || property.setter == null
}
}
}
}
private fun KtClass.hasNoInnerClass(): Boolean {
val internalClasses = getBody()
?.declarations