KT-11576 Unused symbol warning: add quickfix for annotated declarations
#KT-11576 Fixed
This commit is contained in:
@@ -16,9 +16,11 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.inspections
|
package org.jetbrains.kotlin.idea.inspections
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.AnnotationUtil
|
||||||
import com.intellij.codeInsight.FileModificationService
|
import com.intellij.codeInsight.FileModificationService
|
||||||
import com.intellij.codeInsight.daemon.QuickFixBundle
|
import com.intellij.codeInsight.daemon.QuickFixBundle
|
||||||
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil
|
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil
|
||||||
|
import com.intellij.codeInsight.intention.QuickFixFactory
|
||||||
import com.intellij.codeInspection.*
|
import com.intellij.codeInspection.*
|
||||||
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection
|
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection
|
||||||
import com.intellij.codeInspection.ex.EntryPointsManager
|
import com.intellij.codeInspection.ex.EntryPointsManager
|
||||||
@@ -26,6 +28,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.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
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,9 +43,11 @@ import org.jetbrains.kotlin.asJava.LightClassUtil
|
|||||||
import org.jetbrains.kotlin.asJava.toLightClass
|
import org.jetbrains.kotlin.asJava.toLightClass
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
||||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||||
|
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.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.references.mainReference
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
import org.jetbrains.kotlin.idea.search.usagesSearch.*
|
import org.jetbrains.kotlin.idea.search.usagesSearch.*
|
||||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||||
@@ -50,11 +55,13 @@ import org.jetbrains.kotlin.lexer.KtTokens
|
|||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
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.util.OperatorNameConventions
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
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
|
||||||
import java.awt.Insets
|
import java.awt.Insets
|
||||||
|
import java.util.*
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
|
|
||||||
@@ -120,19 +127,6 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
|||||||
|
|
||||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
|
||||||
return object : KtVisitorVoid() {
|
return object : KtVisitorVoid() {
|
||||||
private fun createQuickFix(declaration: KtNamedDeclaration): LocalQuickFix {
|
|
||||||
return object : LocalQuickFix {
|
|
||||||
override fun getName() = QuickFixBundle.message("safe.delete.text", declaration.name)
|
|
||||||
|
|
||||||
override fun getFamilyName() = "Safe delete"
|
|
||||||
|
|
||||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
|
||||||
if (!FileModificationService.getInstance().prepareFileForWrite(declaration.containingFile)) return
|
|
||||||
SafeDeleteHandler.invoke(project, arrayOf(declaration), false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitNamedDeclaration(declaration: KtNamedDeclaration) {
|
override fun visitNamedDeclaration(declaration: KtNamedDeclaration) {
|
||||||
val messageKey = when (declaration) {
|
val messageKey = when (declaration) {
|
||||||
is KtClass -> "unused.class"
|
is KtClass -> "unused.class"
|
||||||
@@ -179,7 +173,7 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
|||||||
KotlinBundle.message(messageKey, declaration.name),
|
KotlinBundle.message(messageKey, declaration.name),
|
||||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
|
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
|
||||||
true,
|
true,
|
||||||
createQuickFix(declaration)
|
*createQuickFixes(declaration).toTypedArray()
|
||||||
)
|
)
|
||||||
|
|
||||||
holder.registerProblem(problemDescriptor)
|
holder.registerProblem(problemDescriptor)
|
||||||
@@ -284,4 +278,40 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
|
|||||||
)
|
)
|
||||||
return panel
|
return panel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createQuickFixes(declaration: KtNamedDeclaration): List<LocalQuickFix> {
|
||||||
|
val list = ArrayList<LocalQuickFix>()
|
||||||
|
|
||||||
|
list.add(SafeDeleteFix(declaration))
|
||||||
|
|
||||||
|
for (annotationEntry in declaration.annotationEntries) {
|
||||||
|
val typeElement = annotationEntry.typeReference?.typeElement as? KtUserType ?: continue
|
||||||
|
val bindingContext = annotationEntry.analyze(BodyResolveMode.PARTIAL)
|
||||||
|
val target = typeElement.referenceExpression?.mainReference?.resolveToDescriptors(bindingContext)?.singleOrNull() ?: continue
|
||||||
|
val fqName = target.importableFqName?.asString() ?: continue
|
||||||
|
|
||||||
|
// checks taken from com.intellij.codeInspection.util.SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes
|
||||||
|
if (fqName.startsWith("kotlin.")
|
||||||
|
|| fqName.startsWith("java.")
|
||||||
|
|| fqName.startsWith("javax.")
|
||||||
|
|| fqName.startsWith("org.jetbrains.") && AnnotationUtil.isJetbrainsAnnotation(StringUtil.getShortName(fqName)))
|
||||||
|
continue
|
||||||
|
|
||||||
|
val intentionAction = QuickFixFactory.getInstance().createAddToDependencyInjectionAnnotationsFix(declaration.project, fqName, "declarations")
|
||||||
|
list.add(IntentionWrapper(intentionAction, declaration.containingFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SafeDeleteFix(val declaration: KtDeclaration) : LocalQuickFix {
|
||||||
|
override fun getName() = QuickFixBundle.message("safe.delete.text", declaration.name)
|
||||||
|
|
||||||
|
override fun getFamilyName() = "Safe delete"
|
||||||
|
|
||||||
|
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||||
|
if (!FileModificationService.getInstance().prepareFileForWrite(declaration.containingFile)) return
|
||||||
|
SafeDeleteHandler.invoke(project, arrayOf(declaration), false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// "Suppress for declarations annotated by 'kotlin.Deprecated'" "false"
|
||||||
|
// ACTION: Safe delete 'foo'
|
||||||
|
// ACTION: Create test
|
||||||
|
@Deprecated("")
|
||||||
|
fun foo<caret>(){}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// "Suppress for declarations annotated by 'java.lang.SuppressWarnings'" "false"
|
||||||
|
// ACTION: Safe delete 'Unused'
|
||||||
|
// ACTION: Create test
|
||||||
|
@java.lang.SuppressWarnings("")
|
||||||
|
class <caret>Unused
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// "Suppress for declarations annotated by 'org.jetbrains.annotations.NonNls'" "false"
|
||||||
|
// ACTION: Safe delete 'foo'
|
||||||
|
// ACTION: Create test
|
||||||
|
import org.jetbrains.annotations.NonNls
|
||||||
|
|
||||||
|
@NonNls
|
||||||
|
fun <caret>foo() {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Suppress for declarations annotated by 'xxx.XXX'" "true"
|
||||||
|
package xxx
|
||||||
|
|
||||||
|
annotation class XXX
|
||||||
|
|
||||||
|
@XXX
|
||||||
|
class <caret>UnusedClass
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// "Suppress for declarations annotated by 'xxx.XXX'" "true"
|
||||||
|
package xxx
|
||||||
|
|
||||||
|
annotation class XXX
|
||||||
|
|
||||||
|
@XXX
|
||||||
|
class <caret>UnusedClass
|
||||||
@@ -7715,6 +7715,39 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("idea/testData/quickfix/unusedSuppressAnnotation")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class UnusedSuppressAnnotation extends AbstractQuickFixTest {
|
||||||
|
public void testAllFilesPresentInUnusedSuppressAnnotation() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/unusedSuppressAnnotation"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("notForDeprecated.kt")
|
||||||
|
public void testNotForDeprecated() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/unusedSuppressAnnotation/notForDeprecated.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("notForJava.kt")
|
||||||
|
public void testNotForJava() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/unusedSuppressAnnotation/notForJava.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("notForJetBrains.kt")
|
||||||
|
public void testNotForJetBrains() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/unusedSuppressAnnotation/notForJetBrains.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("simple.kt")
|
||||||
|
public void testSimple() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/unusedSuppressAnnotation/simple.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("idea/testData/quickfix/variables")
|
@TestMetadata("idea/testData/quickfix/variables")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user