From b781a09cafaed5727c391c7ffb592b15a52c0de5 Mon Sep 17 00:00:00 2001 From: Evgeny Gerashchenko Date: Wed, 3 Dec 2014 21:16:49 +0300 Subject: [PATCH] Implemented simplest version of unused symbol inspection. --- .../kotlin/generators/tests/GenerateTests.kt | 1 + .../kotlin/idea/JetBundle.properties | 2 + .../inspectionDescriptions/UnusedSymbol.html | 5 ++ idea/src/META-INF/plugin.xml | 7 ++ .../inspections/UnusedSymbolInspection.kt | 67 +++++++++++++++ .../class/inspectionData/expected.xml | 26 ++++++ .../class/inspectionData/inspections.test | 1 + .../unusedSymbol/class/localUnused.kt | 4 + .../unusedSymbol/class/localUsed.kt | 7 ++ .../inspections/unusedSymbol/class/unused.kt | 1 + .../inspections/unusedSymbol/class/used.kt | 4 + .../unusedSymbol/class/usedOnlyInside.kt | 4 + .../JetInspectionTestGenerated.java | 81 ++++++++++++------- 13 files changed, 179 insertions(+), 31 deletions(-) create mode 100644 idea/resources/inspectionDescriptions/UnusedSymbol.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt create mode 100644 idea/testData/inspections/unusedSymbol/class/inspectionData/expected.xml create mode 100644 idea/testData/inspections/unusedSymbol/class/inspectionData/inspections.test create mode 100644 idea/testData/inspections/unusedSymbol/class/localUnused.kt create mode 100644 idea/testData/inspections/unusedSymbol/class/localUsed.kt create mode 100644 idea/testData/inspections/unusedSymbol/class/unused.kt create mode 100644 idea/testData/inspections/unusedSymbol/class/used.kt create mode 100644 idea/testData/inspections/unusedSymbol/class/usedOnlyInside.kt diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 7deff716d44..fd4bb886ab6 100644 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -451,6 +451,7 @@ fun main(args: Array) { testClass(javaClass()) { model("intentions", pattern = "^(inspections\\.test)$", singleClass = true) + model("inspections", pattern = "^(inspections\\.test)$", singleClass = true) } testClass(javaClass()) { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties index 66141731599..7f2a4b4d0ae 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties @@ -117,6 +117,8 @@ goto.super.function.chooser.title=Choose super function goto.super.property.chooser.title=Choose super property goto.super.class.chooser.title=Choose super class or interface +unused.class=Class ''{0}'' is never used + livetemplate.description.main=main() function livetemplate.description.soutp=Prints function parameter names and values to System.out livetemplate.description.iter=Iterate over elements of iterable diff --git a/idea/resources/inspectionDescriptions/UnusedSymbol.html b/idea/resources/inspectionDescriptions/UnusedSymbol.html new file mode 100644 index 00000000000..53b56cb7a14 --- /dev/null +++ b/idea/resources/inspectionDescriptions/UnusedSymbol.html @@ -0,0 +1,5 @@ + + +This inspection reports classes, functions or properties in the specified inspection scope that are not used or not reachable from entry points. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index a32ca6992d0..e9d09d93272 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -826,6 +826,13 @@ level="WARNING" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt new file mode 100644 index 00000000000..732000c3520 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.inspections + +import org.jetbrains.kotlin.idea.findUsages.toClassHelper +import org.jetbrains.kotlin.psi.psiUtil.isAncestor +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.psi.JetVisitorVoid +import org.jetbrains.kotlin.psi.JetClass +import org.jetbrains.kotlin.idea.findUsages.KotlinClassFindUsagesOptions +import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearchTarget +import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearch +import com.intellij.util.Processor +import org.jetbrains.kotlin.idea.JetBundle +import com.intellij.codeInspection.ProblemHighlightType + +public class UnusedSymbolInspection : AbstractKotlinInspection() { + override fun runForWholeFile() = true + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { + return object : JetVisitorVoid() { + override fun visitClass(klass: JetClass) { + if (klass.getName() == null) return + + val usagesSearchHelper = KotlinClassFindUsagesOptions(holder.getProject()).toClassHelper() + val request = usagesSearchHelper.newRequest(UsagesSearchTarget(klass, klass.getUseScope())) + val query = UsagesSearch.search(request) + + var foundNonTrivialUsage = false + query.forEach(Processor { + usage -> + if (klass.isAncestor(usage.getElement())) { + true + } + else { + foundNonTrivialUsage = true + false + } + }) + + if (!foundNonTrivialUsage) { + holder.registerProblem( + klass.getNameIdentifier(), + JetBundle.message("unused.class", klass.getName()), + ProblemHighlightType.LIKE_UNUSED_SYMBOL + ) // TODO add quick fix to delete it + } + } + } + } +} \ No newline at end of file diff --git a/idea/testData/inspections/unusedSymbol/class/inspectionData/expected.xml b/idea/testData/inspections/unusedSymbol/class/inspectionData/expected.xml new file mode 100644 index 00000000000..327bb5f3ea4 --- /dev/null +++ b/idea/testData/inspections/unusedSymbol/class/inspectionData/expected.xml @@ -0,0 +1,26 @@ + + + unused.kt + 1 + light_idea_test_case + + Unused Symbol + Class 'A' is never used + + + usedOnlyInside.kt + 1 + light_idea_test_case + + Unused Symbol + Class 'A' is never used + + + localUnused.kt + 2 + light_idea_test_case + + Unused Symbol + Class 'Local' is never used + + diff --git a/idea/testData/inspections/unusedSymbol/class/inspectionData/inspections.test b/idea/testData/inspections/unusedSymbol/class/inspectionData/inspections.test new file mode 100644 index 00000000000..02aea1c357e --- /dev/null +++ b/idea/testData/inspections/unusedSymbol/class/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection diff --git a/idea/testData/inspections/unusedSymbol/class/localUnused.kt b/idea/testData/inspections/unusedSymbol/class/localUnused.kt new file mode 100644 index 00000000000..2b0a47d01ca --- /dev/null +++ b/idea/testData/inspections/unusedSymbol/class/localUnused.kt @@ -0,0 +1,4 @@ +fun f() { + class Local { + } +} \ No newline at end of file diff --git a/idea/testData/inspections/unusedSymbol/class/localUsed.kt b/idea/testData/inspections/unusedSymbol/class/localUsed.kt new file mode 100644 index 00000000000..b56df51cf92 --- /dev/null +++ b/idea/testData/inspections/unusedSymbol/class/localUsed.kt @@ -0,0 +1,7 @@ +fun f() { + class Local { + + } + + Local() +} \ No newline at end of file diff --git a/idea/testData/inspections/unusedSymbol/class/unused.kt b/idea/testData/inspections/unusedSymbol/class/unused.kt new file mode 100644 index 00000000000..fb7337ede5b --- /dev/null +++ b/idea/testData/inspections/unusedSymbol/class/unused.kt @@ -0,0 +1 @@ +class A \ No newline at end of file diff --git a/idea/testData/inspections/unusedSymbol/class/used.kt b/idea/testData/inspections/unusedSymbol/class/used.kt new file mode 100644 index 00000000000..f6cbea3ef5f --- /dev/null +++ b/idea/testData/inspections/unusedSymbol/class/used.kt @@ -0,0 +1,4 @@ +class A + +fun f(): A { +} \ No newline at end of file diff --git a/idea/testData/inspections/unusedSymbol/class/usedOnlyInside.kt b/idea/testData/inspections/unusedSymbol/class/usedOnlyInside.kt new file mode 100644 index 00000000000..ef575ae9d85 --- /dev/null +++ b/idea/testData/inspections/unusedSymbol/class/usedOnlyInside.kt @@ -0,0 +1,4 @@ +class A { + fun f(other: A) { + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java index f0cd7f599f6..3e1cbeba355 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/JetInspectionTestGenerated.java @@ -28,41 +28,60 @@ import java.util.regex.Pattern; /** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") -@TestMetadata("idea/testData/intentions") -@TestDataPath("$PROJECT_ROOT") +@InnerTestClasses({JetInspectionTestGenerated.Intentions.class, JetInspectionTestGenerated.Inspections.class}) @RunWith(JUnit3RunnerWithInners.class) public class JetInspectionTestGenerated extends AbstractJetInspectionTest { - public void testAllFilesPresentInIntentions() throws Exception { - JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/intentions"), Pattern.compile("^(inspections\\.test)$")); + @TestMetadata("idea/testData/intentions") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Intentions extends AbstractJetInspectionTest { + public void testAllFilesPresentInIntentions() throws Exception { + JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/intentions"), Pattern.compile("^(inspections\\.test)$")); + } + + @TestMetadata("attributeCallReplacements/replaceGetIntention/inspectionData/inspections.test") + public void testAttributeCallReplacements_replaceGetIntention_inspectionData_Inspections_test() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/attributeCallReplacements/replaceGetIntention/inspectionData/inspections.test"); + doTest(fileName); + } + + @TestMetadata("branched/ifThenToElvis/inspectionData/inspections.test") + public void testBranched_ifThenToElvis_inspectionData_Inspections_test() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/inspectionData/inspections.test"); + doTest(fileName); + } + + @TestMetadata("branched/ifThenToSafeAccess/inspectionData/inspections.test") + public void testBranched_ifThenToSafeAccess_inspectionData_Inspections_test() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToSafeAccess/inspectionData/inspections.test"); + doTest(fileName); + } + + @TestMetadata("removeExplicitTypeArguments/inspectionData/inspections.test") + public void testRemoveExplicitTypeArguments_inspectionData_Inspections_test() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/inspectionData/inspections.test"); + doTest(fileName); + } + + @TestMetadata("simplifyNegatedBinaryExpressionIntention/inspectionData/inspections.test") + public void testSimplifyNegatedBinaryExpressionIntention_inspectionData_Inspections_test() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/inspectionData/inspections.test"); + doTest(fileName); + } } - @TestMetadata("attributeCallReplacements/replaceGetIntention/inspectionData/inspections.test") - public void testAttributeCallReplacements_replaceGetIntention_inspectionData_Inspections_test() throws Exception { - String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/attributeCallReplacements/replaceGetIntention/inspectionData/inspections.test"); - doTest(fileName); - } + @TestMetadata("idea/testData/inspections") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Inspections extends AbstractJetInspectionTest { + public void testAllFilesPresentInInspections() throws Exception { + JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/inspections"), Pattern.compile("^(inspections\\.test)$")); + } - @TestMetadata("branched/ifThenToElvis/inspectionData/inspections.test") - public void testBranched_ifThenToElvis_inspectionData_Inspections_test() throws Exception { - String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/inspectionData/inspections.test"); - doTest(fileName); - } - - @TestMetadata("branched/ifThenToSafeAccess/inspectionData/inspections.test") - public void testBranched_ifThenToSafeAccess_inspectionData_Inspections_test() throws Exception { - String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToSafeAccess/inspectionData/inspections.test"); - doTest(fileName); - } - - @TestMetadata("removeExplicitTypeArguments/inspectionData/inspections.test") - public void testRemoveExplicitTypeArguments_inspectionData_Inspections_test() throws Exception { - String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/inspectionData/inspections.test"); - doTest(fileName); - } - - @TestMetadata("simplifyNegatedBinaryExpressionIntention/inspectionData/inspections.test") - public void testSimplifyNegatedBinaryExpressionIntention_inspectionData_Inspections_test() throws Exception { - String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/simplifyNegatedBinaryExpressionIntention/inspectionData/inspections.test"); - doTest(fileName); + @TestMetadata("unusedSymbol/class/inspectionData/inspections.test") + public void testUnusedSymbol_class_inspectionData_Inspections_test() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/inspections/unusedSymbol/class/inspectionData/inspections.test"); + doTest(fileName); + } } }