LeakingThisInspection: fix false positives for enum class

#KT-25905 Fixed
This commit is contained in:
Dmitry Gridin
2019-06-18 19:10:03 +07:00
parent efd004c997
commit d9808b70b6
7 changed files with 132 additions and 16 deletions
@@ -35,23 +35,31 @@ class LeakingThisInspection : AbstractKotlinInspection() {
val leakingThese = context.getSliceContents(LEAKING_THIS)
these@ for ((expression, leakingThisDescriptor) in leakingThese) {
if (leakingThisDescriptor.classOrObject != klass) continue@these
val description = when (leakingThisDescriptor) {
val description: String = when (leakingThisDescriptor) {
is NonFinalClass ->
if (expression is KtThisExpression && expression.getStrictParentOfType<KtClassLiteralExpression>() == null) {
if (klass.isEnum()) {
val enumEntries = klass.body?.getChildrenOfType<KtEnumEntry>() ?: continue@these
if (enumEntries.none { it.hasOverriddenMember() }) continue@these
"Leaking 'this' in constructor of enum class ${leakingThisDescriptor.klass.name} (with overridable members)"
} else {
"Leaking 'this' in constructor of non-final class ${leakingThisDescriptor.klass.name}"
}
val name = leakingThisDescriptor.klass.name
klass.createDescription(
"Leaking 'this' in constructor of non-final class $name",
"Leaking 'this' in constructor of enum class $name (with overridable members)"
) { it.hasOverriddenMember() } ?: continue@these
} else {
continue@these // Not supported yet
}
is NonFinalProperty ->
"Accessing non-final property ${leakingThisDescriptor.property.name} in constructor"
is NonFinalFunction ->
"Calling non-final function ${leakingThisDescriptor.function.name} in constructor"
is NonFinalProperty -> {
val name = leakingThisDescriptor.property.name.asString()
klass.createDescription("Accessing non-final property $name in constructor") {
it.hasOverriddenMember { owner -> owner.name == name }
} ?: continue@these
}
is NonFinalFunction -> {
val function = leakingThisDescriptor.function
klass.createDescription("Calling non-final function ${function.name} in constructor") {
it.hasOverriddenMember { owner ->
owner is KtNamedFunction && owner.name == function.name.asString() && owner.valueParameters.size == function.valueParameters.size
}
} ?: continue@these
}
else -> continue@these // Not supported yet
}
val memberDescriptorToFix = when (leakingThisDescriptor) {
@@ -83,10 +91,6 @@ class LeakingThisInspection : AbstractKotlinInspection() {
}
}
private fun KtEnumEntry.hasOverriddenMember(): Boolean {
return body?.getChildrenOfType<KtModifierListOwner>()?.any { it.hasModifier(KtTokens.OVERRIDE_KEYWORD) } == true
}
companion object {
private fun createMakeFinalFix(declaration: KtDeclaration?): IntentionWrapper? {
declaration ?: return null
@@ -96,4 +100,28 @@ class LeakingThisInspection : AbstractKotlinInspection() {
return IntentionWrapper(AddModifierFix(declaration, KtTokens.FINAL_KEYWORD), declaration.containingFile)
}
}
}
private fun KtClass.createDescription(
defaultText: String,
enumText: String = defaultText,
check: (KtClass) -> Boolean
): String? {
return if (isEnum()) {
if (check(this)) return null
enumText
} else {
defaultText
}
}
private fun KtEnumEntry.hasOverriddenMember(additionalCheck: (KtDeclaration) -> Boolean): Boolean = declarations.any {
it.hasModifier(KtTokens.OVERRIDE_KEYWORD) && additionalCheck(it)
}
private fun KtClass.hasOverriddenMember(filter: (KtDeclaration) -> Boolean = { true }): Boolean {
val enumEntries = body?.getChildrenOfType<KtEnumEntry>() ?: return false
return enumEntries.none {
it.hasOverriddenMember(filter)
}
}
@@ -0,0 +1,14 @@
// PROBLEM: Calling non-final function toString in constructor
// FIX: none
enum class Fo(val b: Int) {
ONE(5) {
override val x = b
override fun toString(): String {
return x.toString()
}
};
abstract val x: Int
val dos = <caret>toString()
}
@@ -0,0 +1,11 @@
// PROBLEM: none
// FIX: none
enum class Fo(val b: Int) {
ONE(5) {
override val x = b
};
abstract val x: Int
val dos = <caret>toString()
}
@@ -0,0 +1,13 @@
// PROBLEM: Accessing non-final property x in constructor
// FIX: none
enum class Foo : Bar {
ONE {
override val x = 1
};
val double = x<caret>
}
interface Bar {
val x: Int
}
@@ -0,0 +1,11 @@
// PROBLEM: none
// FIX: none
enum class Foo : Bar {
ONE;
val double = x<caret>
}
interface Bar {
val x: Int get() = 42
}
@@ -0,0 +1,14 @@
// PROBLEM: none
// FIX: none
enum class Foo : Bar {
ONE {
override fun x() = 1
};
val double = <caret>x(2)
}
interface Bar {
fun x(): Int = 42
fun x(i: Int): Int = 24
}
@@ -4530,6 +4530,31 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember2.kt");
}
@TestMetadata("enumEntryHasOverriddenMember3.kt")
public void testEnumEntryHasOverriddenMember3() throws Exception {
runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember3.kt");
}
@TestMetadata("enumEntryHasOverriddenMember4.kt")
public void testEnumEntryHasOverriddenMember4() throws Exception {
runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember4.kt");
}
@TestMetadata("enumEntryHasOverriddenMember5.kt")
public void testEnumEntryHasOverriddenMember5() throws Exception {
runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember5.kt");
}
@TestMetadata("enumEntryHasOverriddenMember6.kt")
public void testEnumEntryHasOverriddenMember6() throws Exception {
runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember6.kt");
}
@TestMetadata("enumEntryHasOverriddenMember7.kt")
public void testEnumEntryHasOverriddenMember7() throws Exception {
runTest("idea/testData/inspectionsLocal/leakingThis/enumEntryHasOverriddenMember7.kt");
}
@TestMetadata("inClassLiteral.kt")
public void testInClassLiteral() throws Exception {
runTest("idea/testData/inspectionsLocal/leakingThis/inClassLiteral.kt");