Added fake override search in unused symbol inspection #KT-13288 Fixed
This commit is contained in:
@@ -29,6 +29,7 @@ import com.intellij.codeInspection.ex.EntryPointsManagerBase
|
||||
import com.intellij.codeInspection.ex.EntryPointsManagerImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiReference
|
||||
@@ -40,11 +41,16 @@ import com.intellij.psi.search.searches.DefinitionsScopedSearch
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.refactoring.safeDelete.SafeDeleteHandler
|
||||
import org.jetbrains.kotlin.asJava.LightClassUtil
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.core.toDescriptor
|
||||
import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory
|
||||
import org.jetbrains.kotlin.idea.findUsages.handlers.KotlinFindClassUsagesHandler
|
||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||
@@ -57,6 +63,7 @@ import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
import org.jetbrains.kotlin.util.findCallableMemberBySignature
|
||||
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
||||
import java.awt.GridBagConstraints
|
||||
import java.awt.GridBagLayout
|
||||
@@ -154,7 +161,7 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
||||
if (declaration is KtNamedFunction && isConventionalName(declaration)) return
|
||||
|
||||
// More expensive, resolve-based checks
|
||||
if (declaration.resolveToDescriptorIfAny() == null) return
|
||||
declaration.resolveToDescriptorIfAny() ?: return
|
||||
if (isEntryPoint(declaration)) return
|
||||
if (declaration is KtProperty && declaration.isSerializationImplicitlyUsedField()) return
|
||||
if (declaration is KtNamedFunction && declaration.isSerializationImplicitlyUsedMethod()) return
|
||||
@@ -232,8 +239,8 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
||||
return (declaration is KtObjectDeclaration && declaration.isCompanion() &&
|
||||
declaration.getBody()?.declarations?.isNotEmpty() == true) ||
|
||||
hasReferences(declaration, useScope) ||
|
||||
hasOverrides(declaration, useScope)
|
||||
|
||||
hasOverrides(declaration, useScope) ||
|
||||
hasFakeOverrides(declaration, useScope)
|
||||
}
|
||||
|
||||
private fun hasReferences(declaration: KtNamedDeclaration, useScope: SearchScope): Boolean {
|
||||
@@ -272,6 +279,37 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
||||
return DefinitionsScopedSearch.search(declaration, useScope).findFirst() != null
|
||||
}
|
||||
|
||||
private fun hasFakeOverrides(declaration: KtNamedDeclaration, useScope: SearchScope): Boolean {
|
||||
val ownerClass = declaration.containingClassOrObject as? KtClass ?: return false
|
||||
if (!ownerClass.isInheritable()) return false
|
||||
val descriptor = declaration.toDescriptor() as? CallableMemberDescriptor ?: return false
|
||||
if (descriptor.modality == Modality.ABSTRACT) return false
|
||||
val lightMethods = declaration.toLightMethods()
|
||||
return DefinitionsScopedSearch.search(ownerClass, useScope).any {
|
||||
element: PsiElement ->
|
||||
|
||||
when (element) {
|
||||
is KtLightClass -> {
|
||||
val memberBySignature =
|
||||
(element.kotlinOrigin?.toDescriptor() as? ClassDescriptor)?.findCallableMemberBySignature(descriptor)
|
||||
memberBySignature != null &&
|
||||
!memberBySignature.kind.isReal &&
|
||||
memberBySignature.overriddenDescriptors.any { it != descriptor }
|
||||
}
|
||||
is PsiClass ->
|
||||
lightMethods.any {
|
||||
lightMethod ->
|
||||
|
||||
val sameMethods = element.findMethodsBySignature(lightMethod, true)
|
||||
sameMethods.all { it.containingClass != element } &&
|
||||
sameMethods.any { it.containingClass != lightMethod.containingClass }
|
||||
}
|
||||
else ->
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun createOptionsPanel(): JComponent? {
|
||||
val panel = JPanel(GridBagLayout())
|
||||
panel.add(
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// See KT-13288
|
||||
|
||||
interface Inter {
|
||||
fun something(): String
|
||||
}
|
||||
|
||||
abstract class Abstract {
|
||||
fun something() = "hi"
|
||||
}
|
||||
|
||||
class Test: Abstract(), Inter {
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val x: Inter = Test()
|
||||
x.something()
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// "Safe delete 'something'" "false"
|
||||
// ACTION: Convert function to property
|
||||
// ACTION: Convert to block body
|
||||
// ACTION: Specify return type explicitly
|
||||
|
||||
interface Inter {
|
||||
fun something(): String
|
||||
}
|
||||
|
||||
class Impl : Inter {
|
||||
override fun <caret>something() = "hi"
|
||||
}
|
||||
|
||||
class Test: Inter by Impl() {
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
interface Inter {
|
||||
String something();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// "Safe delete 'something'" "false"
|
||||
// ACTION: Convert function to property
|
||||
// ACTION: Convert member to extension
|
||||
// ACTION: Convert to block body
|
||||
// ACTION: Move to companion object
|
||||
// ACTION: Specify return type explicitly
|
||||
|
||||
abstract class Abstract {
|
||||
fun <caret>something() = "hi"
|
||||
}
|
||||
|
||||
class Test: Abstract(), Inter {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
interface Inter {
|
||||
String something();
|
||||
}
|
||||
|
||||
class Test extends Abstract implements Inter {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// "Safe delete 'something'" "false"
|
||||
// ACTION: Convert function to property
|
||||
// ACTION: Convert member to extension
|
||||
// ACTION: Convert to block body
|
||||
// ACTION: Move to companion object
|
||||
// ACTION: Specify return type explicitly
|
||||
|
||||
abstract class Abstract {
|
||||
fun <caret>something() = "hi"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class Test extends Abstract {
|
||||
String something() {
|
||||
return "123";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// "Safe delete 'something'" "false"
|
||||
// ACTION: Convert function to property
|
||||
// ACTION: Convert member to extension
|
||||
// ACTION: Convert to block body
|
||||
// ACTION: Move to companion object
|
||||
// ACTION: Specify return type explicitly
|
||||
|
||||
|
||||
abstract class Abstract {
|
||||
open fun <caret>something() = "hi"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Safe delete 'something'" "true"
|
||||
|
||||
abstract class Abstract {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
interface Inter {
|
||||
String something();
|
||||
}
|
||||
|
||||
class Test extends Abstract implements Inter {
|
||||
String something() {
|
||||
return "123";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Safe delete 'something'" "true"
|
||||
|
||||
abstract class Abstract {
|
||||
fun <caret>something() = "hi"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Safe delete 'something'" "true"
|
||||
|
||||
abstract class Abstract {
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
class Test extends Abstract {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Safe delete 'something'" "true"
|
||||
|
||||
abstract class Abstract {
|
||||
fun <caret>something() = "hi"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Safe delete 'something'" "true"
|
||||
|
||||
abstract class Abstract {
|
||||
open fun <caret>something() = "hi"
|
||||
}
|
||||
|
||||
class Test: Abstract() {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// "Safe delete 'something'" "true"
|
||||
|
||||
abstract class Abstract {
|
||||
}
|
||||
|
||||
class Test: Abstract() {
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// "Safe delete 'something'" "false"
|
||||
// ACTION: Convert function to property
|
||||
// ACTION: Convert member to extension
|
||||
// ACTION: Convert to block body
|
||||
// ACTION: Move to companion object
|
||||
// ACTION: Specify return type explicitly
|
||||
|
||||
interface Inter {
|
||||
fun something(): String
|
||||
}
|
||||
|
||||
abstract class Abstract {
|
||||
fun <caret>something() = "hi"
|
||||
}
|
||||
|
||||
class Test: Abstract(), Inter {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// "Safe delete 'something'" "false"
|
||||
// ACTION: Convert function to property
|
||||
// ACTION: Convert member to extension
|
||||
// ACTION: Convert to block body
|
||||
// ACTION: Move to companion object
|
||||
// ACTION: Specify return type explicitly
|
||||
|
||||
interface Inter {
|
||||
fun something(): String
|
||||
}
|
||||
|
||||
abstract class Abstract {
|
||||
open fun <caret>something() = "hi"
|
||||
}
|
||||
|
||||
class Test: Abstract(), Inter {
|
||||
override fun something() = "bye"
|
||||
}
|
||||
@@ -1537,6 +1537,36 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/removeUnused"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("javaTriangle.before.Main.kt")
|
||||
public void testJavaTriangle() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/javaTriangle.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("javaTriangle2.before.Main.kt")
|
||||
public void testJavaTriangle2() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/javaTriangle2.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("javaTriangle3.before.Main.kt")
|
||||
public void testJavaTriangle3() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/javaTriangle3.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("javaTriangleUnused.before.Main.kt")
|
||||
public void testJavaTriangleUnused() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/javaTriangleUnused.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("javaTriangleUnused2.before.Main.kt")
|
||||
public void testJavaTriangleUnused2() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/javaTriangleUnused2.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("usedObjectAsAliasMulti.before.Main.kt")
|
||||
public void testUsedObjectAsAliasMulti() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/usedObjectAsAliasMulti.before.Main.kt");
|
||||
|
||||
@@ -6901,6 +6901,30 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/removeUnused"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("deledage.kt")
|
||||
public void testDeledage() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/deledage.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("notTriangle.kt")
|
||||
public void testNotTriangle() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/notTriangle.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("triangle.kt")
|
||||
public void testTriangle() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/triangle.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("triangle2.kt")
|
||||
public void testTriangle2() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/triangle2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("unusedClass.kt")
|
||||
public void testUnusedClass() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/unusedClass.kt");
|
||||
|
||||
Reference in New Issue
Block a user