diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt index 57ba4b3a170..c0134efae17 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt @@ -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 { + val list = ArrayList() + + 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) + } } diff --git a/idea/testData/quickfix/unusedSuppressAnnotation/.inspection b/idea/testData/quickfix/unusedSuppressAnnotation/.inspection new file mode 100644 index 00000000000..92d81ff196a --- /dev/null +++ b/idea/testData/quickfix/unusedSuppressAnnotation/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection \ No newline at end of file diff --git a/idea/testData/quickfix/unusedSuppressAnnotation/notForDeprecated.kt b/idea/testData/quickfix/unusedSuppressAnnotation/notForDeprecated.kt new file mode 100644 index 00000000000..067d6f6b027 --- /dev/null +++ b/idea/testData/quickfix/unusedSuppressAnnotation/notForDeprecated.kt @@ -0,0 +1,5 @@ +// "Suppress for declarations annotated by 'kotlin.Deprecated'" "false" +// ACTION: Safe delete 'foo' +// ACTION: Create test +@Deprecated("") +fun foo(){} diff --git a/idea/testData/quickfix/unusedSuppressAnnotation/notForJava.kt b/idea/testData/quickfix/unusedSuppressAnnotation/notForJava.kt new file mode 100644 index 00000000000..543b33adf53 --- /dev/null +++ b/idea/testData/quickfix/unusedSuppressAnnotation/notForJava.kt @@ -0,0 +1,5 @@ +// "Suppress for declarations annotated by 'java.lang.SuppressWarnings'" "false" +// ACTION: Safe delete 'Unused' +// ACTION: Create test +@java.lang.SuppressWarnings("") +class Unused diff --git a/idea/testData/quickfix/unusedSuppressAnnotation/notForJetBrains.kt b/idea/testData/quickfix/unusedSuppressAnnotation/notForJetBrains.kt new file mode 100644 index 00000000000..dfcca8efce2 --- /dev/null +++ b/idea/testData/quickfix/unusedSuppressAnnotation/notForJetBrains.kt @@ -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 foo() { + +} \ No newline at end of file diff --git a/idea/testData/quickfix/unusedSuppressAnnotation/simple.kt b/idea/testData/quickfix/unusedSuppressAnnotation/simple.kt new file mode 100644 index 00000000000..70b02417c61 --- /dev/null +++ b/idea/testData/quickfix/unusedSuppressAnnotation/simple.kt @@ -0,0 +1,7 @@ +// "Suppress for declarations annotated by 'xxx.XXX'" "true" +package xxx + +annotation class XXX + +@XXX +class UnusedClass diff --git a/idea/testData/quickfix/unusedSuppressAnnotation/simple.kt.after b/idea/testData/quickfix/unusedSuppressAnnotation/simple.kt.after new file mode 100644 index 00000000000..70b02417c61 --- /dev/null +++ b/idea/testData/quickfix/unusedSuppressAnnotation/simple.kt.after @@ -0,0 +1,7 @@ +// "Suppress for declarations annotated by 'xxx.XXX'" "true" +package xxx + +annotation class XXX + +@XXX +class UnusedClass diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index e9f99e956dc..9042566ecb5 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -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)