KT-11576 Unused symbol warning: add quickfix for annotated declarations

#KT-11576 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-03-23 16:41:05 +03:00
parent 03d7c5764d
commit c1499e7cb6
8 changed files with 111 additions and 14 deletions
@@ -16,9 +16,11 @@
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInsight.AnnotationUtil
import com.intellij.codeInsight.FileModificationService
import com.intellij.codeInsight.daemon.QuickFixBundle
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil
import com.intellij.codeInsight.intention.QuickFixFactory
import com.intellij.codeInspection.*
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection
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.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
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.descriptors.annotations.Annotated
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.findUsages.KotlinFindUsagesHandlerFactory
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.search.usagesSearch.*
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.psiUtil.*
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.singletonOrEmptyList
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import java.awt.Insets
import java.util.*
import javax.swing.JComponent
import javax.swing.JPanel
@@ -120,19 +127,6 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
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) {
val messageKey = when (declaration) {
is KtClass -> "unused.class"
@@ -179,7 +173,7 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
KotlinBundle.message(messageKey, declaration.name),
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
true,
createQuickFix(declaration)
*createQuickFixes(declaration).toTypedArray()
)
holder.registerProblem(problemDescriptor)
@@ -284,4 +278,40 @@ class UnusedSymbolInspection : AbstractKotlinInspection() {
)
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")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)