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.codeInspection.ex.EntryPointsManagerImpl
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
|
import com.intellij.psi.PsiClass
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.PsiElementVisitor
|
import com.intellij.psi.PsiElementVisitor
|
||||||
import com.intellij.psi.PsiReference
|
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.psi.search.searches.ReferencesSearch
|
||||||
import com.intellij.refactoring.safeDelete.SafeDeleteHandler
|
import com.intellij.refactoring.safeDelete.SafeDeleteHandler
|
||||||
import org.jetbrains.kotlin.asJava.LightClassUtil
|
import org.jetbrains.kotlin.asJava.LightClassUtil
|
||||||
|
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||||
import org.jetbrains.kotlin.asJava.toLightClass
|
import org.jetbrains.kotlin.asJava.toLightClass
|
||||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
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.descriptors.annotations.Annotated
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
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.KotlinFindUsagesHandlerFactory
|
||||||
import org.jetbrains.kotlin.idea.findUsages.handlers.KotlinFindClassUsagesHandler
|
import org.jetbrains.kotlin.idea.findUsages.handlers.KotlinFindClassUsagesHandler
|
||||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
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.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
import org.jetbrains.kotlin.util.findCallableMemberBySignature
|
||||||
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
||||||
import java.awt.GridBagConstraints
|
import java.awt.GridBagConstraints
|
||||||
import java.awt.GridBagLayout
|
import java.awt.GridBagLayout
|
||||||
@@ -154,7 +161,7 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
|||||||
if (declaration is KtNamedFunction && isConventionalName(declaration)) return
|
if (declaration is KtNamedFunction && isConventionalName(declaration)) return
|
||||||
|
|
||||||
// More expensive, resolve-based checks
|
// More expensive, resolve-based checks
|
||||||
if (declaration.resolveToDescriptorIfAny() == null) return
|
declaration.resolveToDescriptorIfAny() ?: return
|
||||||
if (isEntryPoint(declaration)) return
|
if (isEntryPoint(declaration)) return
|
||||||
if (declaration is KtProperty && declaration.isSerializationImplicitlyUsedField()) return
|
if (declaration is KtProperty && declaration.isSerializationImplicitlyUsedField()) return
|
||||||
if (declaration is KtNamedFunction && declaration.isSerializationImplicitlyUsedMethod()) return
|
if (declaration is KtNamedFunction && declaration.isSerializationImplicitlyUsedMethod()) return
|
||||||
@@ -232,8 +239,8 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
|||||||
return (declaration is KtObjectDeclaration && declaration.isCompanion() &&
|
return (declaration is KtObjectDeclaration && declaration.isCompanion() &&
|
||||||
declaration.getBody()?.declarations?.isNotEmpty() == true) ||
|
declaration.getBody()?.declarations?.isNotEmpty() == true) ||
|
||||||
hasReferences(declaration, useScope) ||
|
hasReferences(declaration, useScope) ||
|
||||||
hasOverrides(declaration, useScope)
|
hasOverrides(declaration, useScope) ||
|
||||||
|
hasFakeOverrides(declaration, useScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hasReferences(declaration: KtNamedDeclaration, useScope: SearchScope): Boolean {
|
private fun hasReferences(declaration: KtNamedDeclaration, useScope: SearchScope): Boolean {
|
||||||
@@ -272,6 +279,37 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
|||||||
return DefinitionsScopedSearch.search(declaration, useScope).findFirst() != null
|
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? {
|
override fun createOptionsPanel(): JComponent? {
|
||||||
val panel = JPanel(GridBagLayout())
|
val panel = JPanel(GridBagLayout())
|
||||||
panel.add(
|
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);
|
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")
|
@TestMetadata("usedObjectAsAliasMulti.before.Main.kt")
|
||||||
public void testUsedObjectAsAliasMulti() throws Exception {
|
public void testUsedObjectAsAliasMulti() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/usedObjectAsAliasMulti.before.Main.kt");
|
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);
|
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")
|
@TestMetadata("unusedClass.kt")
|
||||||
public void testUnusedClass() throws Exception {
|
public void testUnusedClass() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/unusedClass.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/unusedClass.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user