LeakingThisInspection: fix false positives for enum class
#KT-25905 Fixed
This commit is contained in:
@@ -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
|
||||
}
|
||||
+25
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user