diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt index 8dad5c64acd..8e1b16fa291 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt @@ -52,6 +52,12 @@ import org.jetbrains.kotlin.idea.search.usagesSearch.dataClassComponentFunctionN import org.jetbrains.kotlin.psi.JetTypeParameter import org.jetbrains.kotlin.idea.search.usagesSearch.DefaultSearchHelper import com.intellij.util.Processor +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.openapi.project.Project +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInsight.daemon.QuickFixBundle +import com.intellij.codeInsight.FileModificationService +import com.intellij.refactoring.safeDelete.SafeDeleteHandler public class UnusedSymbolInspection : AbstractKotlinInspection() { private val javaInspection = UnusedDeclarationInspection() @@ -60,6 +66,19 @@ public class UnusedSymbolInspection : AbstractKotlinInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { return object : JetVisitorVoid() { + private fun createQuickFix(declaration: JetNamedDeclaration): LocalQuickFix { + return object : LocalQuickFix { + override fun getName() = QuickFixBundle.message("safe.delete.text", declaration.getName()) + + override fun getFamilyName() = "whatever" + + override fun applyFix(project: Project, descriptor: ProblemDescriptor?) { + if (!FileModificationService.getInstance().prepareFileForWrite(declaration.getContainingFile())) return + SafeDeleteHandler.invoke(project, array(declaration), false) + } + } + } + override fun visitClass(klass: JetClass) { if (klass.getName() == null) return @@ -72,8 +91,9 @@ public class UnusedSymbolInspection : AbstractKotlinInspection() { holder.registerProblem( klass.getNameIdentifier(), JetBundle.message("unused.class", klass.getName()), - ProblemHighlightType.LIKE_UNUSED_SYMBOL - ) // TODO add quick fix to delete it + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + createQuickFix(klass) + ) } override fun visitNamedFunction(function: JetNamedFunction) { @@ -87,8 +107,9 @@ public class UnusedSymbolInspection : AbstractKotlinInspection() { holder.registerProblem( function.getNameIdentifier(), JetBundle.message("unused.function", function.getName()), - ProblemHighlightType.LIKE_UNUSED_SYMBOL - ) // TODO add quick fix to delete it + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + createQuickFix(function) + ) } override fun visitProperty(property: JetProperty) { @@ -102,8 +123,9 @@ public class UnusedSymbolInspection : AbstractKotlinInspection() { holder.registerProblem( property.getNameIdentifier(), JetBundle.message("unused.property", property.getName()), - ProblemHighlightType.LIKE_UNUSED_SYMBOL - ) // TODO add quick fix to delete it + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + createQuickFix(property) + ) } override fun visitParameter(parameter: JetParameter) { @@ -120,8 +142,9 @@ public class UnusedSymbolInspection : AbstractKotlinInspection() { holder.registerProblem( parameter.getNameIdentifier(), JetBundle.message("unused.property", parameter.getName()), - ProblemHighlightType.LIKE_UNUSED_SYMBOL - ) // TODO add quick fix to delete it + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + createQuickFix(parameter) + ) } override fun visitTypeParameter(parameter: JetTypeParameter) { @@ -132,8 +155,9 @@ public class UnusedSymbolInspection : AbstractKotlinInspection() { holder.registerProblem( parameter.getNameIdentifier(), JetBundle.message("unused.type.parameter", parameter.getName()), - ProblemHighlightType.LIKE_UNUSED_SYMBOL - ) // TODO add quick fix to delete it + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + createQuickFix(parameter) + ) } } } @@ -200,4 +224,4 @@ public class UnusedSymbolInspection : AbstractKotlinInspection() { return !query.forEach(Processor { declaration.isAncestor(it.getElement()) }) } -} \ No newline at end of file +} diff --git a/idea/testData/quickfix/removeUnused/.inspection b/idea/testData/quickfix/removeUnused/.inspection new file mode 100644 index 00000000000..92d81ff196a --- /dev/null +++ b/idea/testData/quickfix/removeUnused/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection \ No newline at end of file diff --git a/idea/testData/quickfix/removeUnused/afterUnusedClass.kt b/idea/testData/quickfix/removeUnused/afterUnusedClass.kt new file mode 100644 index 00000000000..88b655b4048 --- /dev/null +++ b/idea/testData/quickfix/removeUnused/afterUnusedClass.kt @@ -0,0 +1,3 @@ +// "Safe delete 'UnusedClass'" "true" +class DummyClass + diff --git a/idea/testData/quickfix/removeUnused/afterUnusedFunction.kt b/idea/testData/quickfix/removeUnused/afterUnusedFunction.kt new file mode 100644 index 00000000000..972b055f57c --- /dev/null +++ b/idea/testData/quickfix/removeUnused/afterUnusedFunction.kt @@ -0,0 +1,4 @@ +// "Safe delete 'unusedFun'" "true" +fun dummy() { +} + diff --git a/idea/testData/quickfix/removeUnused/afterUnusedProperty.kt b/idea/testData/quickfix/removeUnused/afterUnusedProperty.kt new file mode 100644 index 00000000000..8489c295e4f --- /dev/null +++ b/idea/testData/quickfix/removeUnused/afterUnusedProperty.kt @@ -0,0 +1,3 @@ +// "Safe delete 'property'" "true" +class UnusedProperty() { +} diff --git a/idea/testData/quickfix/removeUnused/afterUnusedPropertyAsConstructorParameter.kt b/idea/testData/quickfix/removeUnused/afterUnusedPropertyAsConstructorParameter.kt new file mode 100644 index 00000000000..642ef76bdd0 --- /dev/null +++ b/idea/testData/quickfix/removeUnused/afterUnusedPropertyAsConstructorParameter.kt @@ -0,0 +1,2 @@ +// "Safe delete 'property'" "true" +class UnusedPropertyAsConstructorParameter() diff --git a/idea/testData/quickfix/removeUnused/afterUnusedTypeParameter.kt b/idea/testData/quickfix/removeUnused/afterUnusedTypeParameter.kt new file mode 100644 index 00000000000..75b2e2cf04b --- /dev/null +++ b/idea/testData/quickfix/removeUnused/afterUnusedTypeParameter.kt @@ -0,0 +1,2 @@ +// "Safe delete 'T'" "true" +class UnusedTypeParameter

(val p: P) diff --git a/idea/testData/quickfix/removeUnused/beforeUnusedClass.kt b/idea/testData/quickfix/removeUnused/beforeUnusedClass.kt new file mode 100644 index 00000000000..ae78f7de9ed --- /dev/null +++ b/idea/testData/quickfix/removeUnused/beforeUnusedClass.kt @@ -0,0 +1,4 @@ +// "Safe delete 'UnusedClass'" "true" +class DummyClass + +class UnusedClass diff --git a/idea/testData/quickfix/removeUnused/beforeUnusedFunction.kt b/idea/testData/quickfix/removeUnused/beforeUnusedFunction.kt new file mode 100644 index 00000000000..b35f9b9b61c --- /dev/null +++ b/idea/testData/quickfix/removeUnused/beforeUnusedFunction.kt @@ -0,0 +1,7 @@ +// "Safe delete 'unusedFun'" "true" +fun dummy() { +} + +fun unusedFun() { + +} diff --git a/idea/testData/quickfix/removeUnused/beforeUnusedProperty.kt b/idea/testData/quickfix/removeUnused/beforeUnusedProperty.kt new file mode 100644 index 00000000000..beb08549d00 --- /dev/null +++ b/idea/testData/quickfix/removeUnused/beforeUnusedProperty.kt @@ -0,0 +1,4 @@ +// "Safe delete 'property'" "true" +class UnusedProperty() { + val property: String = ":)" +} diff --git a/idea/testData/quickfix/removeUnused/beforeUnusedPropertyAsConstructorParameter.kt b/idea/testData/quickfix/removeUnused/beforeUnusedPropertyAsConstructorParameter.kt new file mode 100644 index 00000000000..887caff8028 --- /dev/null +++ b/idea/testData/quickfix/removeUnused/beforeUnusedPropertyAsConstructorParameter.kt @@ -0,0 +1,2 @@ +// "Safe delete 'property'" "true" +class UnusedPropertyAsConstructorParameter(val property: String) diff --git a/idea/testData/quickfix/removeUnused/beforeUnusedTypeParameter.kt b/idea/testData/quickfix/removeUnused/beforeUnusedTypeParameter.kt new file mode 100644 index 00000000000..27a969798fa --- /dev/null +++ b/idea/testData/quickfix/removeUnused/beforeUnusedTypeParameter.kt @@ -0,0 +1,2 @@ +// "Safe delete 'T'" "true" +class UnusedTypeParameter<T, P>(val p: P) diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java index 3d7faa943bf..a5482d59775 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java @@ -26,9 +26,11 @@ import com.intellij.openapi.projectRoots.JavaSdk; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.psi.PsiElement; import org.apache.commons.lang.SystemUtils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.idea.DirectiveBasedActionUtils; import org.jetbrains.kotlin.idea.KotlinLightQuickFixTestCase; import org.jetbrains.kotlin.idea.PluginTestCaseBase; @@ -39,6 +41,7 @@ import org.jetbrains.kotlin.test.TestMetadata; import org.junit.Assert; import java.io.File; +import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -50,6 +53,19 @@ public abstract class AbstractQuickFixTest extends KotlinLightQuickFixTestCase { ((StartupManagerImpl) StartupManager.getInstance(getProject())).runPostStartupActivities(); } + @Nullable + private static File findInspectionFile(@NotNull File startDir) { + File currentDir = startDir; + while (currentDir != null) { + File inspectionFile = new File(currentDir, ".inspection"); + if (inspectionFile.exists()) { + return inspectionFile; + } + currentDir = currentDir.getParentFile(); + } + return null; + } + protected void doTest(@NotNull String beforeFileName) throws Exception { boolean isWithRuntime = beforeFileName.endsWith("Runtime.kt"); @@ -58,6 +74,8 @@ public abstract class AbstractQuickFixTest extends KotlinLightQuickFixTestCase { ConfigLibraryUtil.configureKotlinRuntime(getModule(), getFullJavaJDK()); } + enableInspections(beforeFileName); + doSingleTest(getTestName(false) + ".kt"); checkForUnexpectedActions(); checkForUnexpectedErrors(); @@ -69,6 +87,15 @@ public abstract class AbstractQuickFixTest extends KotlinLightQuickFixTestCase { } } + private void enableInspections(String beforeFileName) throws IOException, ClassNotFoundException { + File inspectionFile = findInspectionFile(new File(beforeFileName).getParentFile()); + if (inspectionFile != null) { + String className = FileUtil.loadFile(inspectionFile).trim(); + Class inspectionClass = Class.forName(className); + enableInspectionTools(inspectionClass); + } + } + private void checkForUnexpectedActions() throws ClassNotFoundException { String text = getEditor().getDocument().getText(); Pair pair = parseActionHintImpl(getFile(), text); diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index 910da86bd69..a024a93e502 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -30,7 +30,7 @@ import java.util.regex.Pattern; @SuppressWarnings("all") @TestMetadata("idea/testData/quickfix") @TestDataPath("$PROJECT_ROOT") -@InnerTestClasses({QuickFixTestGenerated.Abstract.class, QuickFixTestGenerated.AddStarProjections.class, QuickFixTestGenerated.AutoImports.class, QuickFixTestGenerated.ChangeSignature.class, QuickFixTestGenerated.CheckArguments.class, QuickFixTestGenerated.ConflictingImports.class, QuickFixTestGenerated.CreateFromUsage.class, QuickFixTestGenerated.Expressions.class, QuickFixTestGenerated.Migration.class, QuickFixTestGenerated.Modifiers.class, QuickFixTestGenerated.Nullables.class, QuickFixTestGenerated.Override.class, QuickFixTestGenerated.PlatformClasses.class, QuickFixTestGenerated.Supercalls.class, QuickFixTestGenerated.SupertypeInitialization.class, QuickFixTestGenerated.Suppress.class, QuickFixTestGenerated.TypeAddition.class, QuickFixTestGenerated.TypeImports.class, QuickFixTestGenerated.TypeMismatch.class, QuickFixTestGenerated.TypeProjection.class, QuickFixTestGenerated.Variables.class, QuickFixTestGenerated.When.class}) +@InnerTestClasses({QuickFixTestGenerated.Abstract.class, QuickFixTestGenerated.AddStarProjections.class, QuickFixTestGenerated.AutoImports.class, QuickFixTestGenerated.ChangeSignature.class, QuickFixTestGenerated.CheckArguments.class, QuickFixTestGenerated.ConflictingImports.class, QuickFixTestGenerated.CreateFromUsage.class, QuickFixTestGenerated.Expressions.class, QuickFixTestGenerated.Migration.class, QuickFixTestGenerated.Modifiers.class, QuickFixTestGenerated.Nullables.class, QuickFixTestGenerated.Override.class, QuickFixTestGenerated.PlatformClasses.class, QuickFixTestGenerated.RemoveUnused.class, QuickFixTestGenerated.Supercalls.class, QuickFixTestGenerated.SupertypeInitialization.class, QuickFixTestGenerated.Suppress.class, QuickFixTestGenerated.TypeAddition.class, QuickFixTestGenerated.TypeImports.class, QuickFixTestGenerated.TypeMismatch.class, QuickFixTestGenerated.TypeProjection.class, QuickFixTestGenerated.Variables.class, QuickFixTestGenerated.When.class}) @RunWith(JUnit3RunnerWithInners.class) public class QuickFixTestGenerated extends AbstractQuickFixTest { public void testAllFilesPresentInQuickfix() throws Exception { @@ -3423,6 +3423,45 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { } } + @TestMetadata("idea/testData/quickfix/removeUnused") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RemoveUnused extends AbstractQuickFixTest { + public void testAllFilesPresentInRemoveUnused() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/removeUnused"), Pattern.compile("^before(\\w+)\\.kt$"), true); + } + + @TestMetadata("beforeUnusedClass.kt") + public void testUnusedClass() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/beforeUnusedClass.kt"); + doTest(fileName); + } + + @TestMetadata("beforeUnusedFunction.kt") + public void testUnusedFunction() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/beforeUnusedFunction.kt"); + doTest(fileName); + } + + @TestMetadata("beforeUnusedProperty.kt") + public void testUnusedProperty() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/beforeUnusedProperty.kt"); + doTest(fileName); + } + + @TestMetadata("beforeUnusedPropertyAsConstructorParameter.kt") + public void testUnusedPropertyAsConstructorParameter() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/beforeUnusedPropertyAsConstructorParameter.kt"); + doTest(fileName); + } + + @TestMetadata("beforeUnusedTypeParameter.kt") + public void testUnusedTypeParameter() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/removeUnused/beforeUnusedTypeParameter.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/quickfix/supercalls") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)