diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt index 742f4d2be86..4df2237f3aa 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedSymbolInspection.kt @@ -50,7 +50,9 @@ import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.quickfix.RemoveUnusedFunctionParameterFix import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors +import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchParameters import org.jetbrains.kotlin.idea.search.isCheapEnoughToSearchConsideringOperators +import org.jetbrains.kotlin.idea.search.projectScope import org.jetbrains.kotlin.idea.search.usagesSearch.dataClassComponentFunction import org.jetbrains.kotlin.idea.search.usagesSearch.getAccessorNames import org.jetbrains.kotlin.idea.search.usagesSearch.getClassNameForCompanionObject @@ -190,7 +192,8 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { } private fun hasNonTrivialUsages(declaration: KtNamedDeclaration, descriptor: DeclarationDescriptor? = null): Boolean { - val psiSearchHelper = PsiSearchHelper.SERVICE.getInstance(declaration.project) + val project = declaration.project + val psiSearchHelper = PsiSearchHelper.SERVICE.getInstance(project) val useScope = declaration.useScope val restrictedScope = if (useScope is GlobalSearchScope) { @@ -206,14 +209,18 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { } } - if (zeroOccurrences) { + if (zeroOccurrences && !declaration.hasActualModifier()) { if (declaration is KtObjectDeclaration && declaration.isCompanion()) { // go on: companion object can be used only in containing class } else { return false } } - KotlinSourceFilterScope.projectSources(useScope, declaration.project) + if (declaration.hasActualModifier()) { + KotlinSourceFilterScope.projectSources(project.projectScope(), project) + } else { + KotlinSourceFilterScope.projectSources(useScope, project) + } } else useScope return (declaration is KtObjectDeclaration && declaration.isCompanion() && @@ -221,7 +228,6 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { hasReferences(declaration, descriptor, restrictedScope) || hasOverrides(declaration, restrictedScope) || hasFakeOverrides(declaration, restrictedScope) || - isPlatformImplementation(declaration) || hasPlatformImplementations(declaration, descriptor) } @@ -267,7 +273,8 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { return false } - val referenceUsed: Boolean by lazy { !ReferencesSearch.search(declaration, useScope).forEach(::checkReference) } + val searchParameters = KotlinReferencesSearchParameters(declaration, useScope) + val referenceUsed: Boolean by lazy { !ReferencesSearch.search(searchParameters).forEach(::checkReference) } if (descriptor is FunctionDescriptor && DescriptorUtils.getAnnotationByFqName(descriptor.annotations, JvmFileClassUtil.JVM_NAME) != null @@ -278,9 +285,11 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { if (declaration is KtCallableDeclaration && !declaration.hasModifier(KtTokens.INTERNAL_KEYWORD)) { val lightMethods = declaration.toLightMethods() if (lightMethods.isNotEmpty()) { - return lightMethods.any { method -> + val lightMethodsUsed = lightMethods.any { method -> !MethodReferencesSearch.search(method).forEach(::checkReference) } + if (lightMethodsUsed) return true + if (!declaration.hasActualModifier()) return false } } @@ -319,9 +328,6 @@ class UnusedSymbolInspection : AbstractKotlinInspection() { } } - private fun isPlatformImplementation(declaration: KtNamedDeclaration) = - declaration.hasActualModifier() - private fun hasPlatformImplementations(declaration: KtNamedDeclaration, descriptor: DeclarationDescriptor?): Boolean { if (!declaration.hasExpectModifier()) return false diff --git a/idea/testData/inspections/unusedSymbol/class/headerImpl.kt b/idea/testData/inspections/unusedSymbol/class/headerImpl.kt index 47b9bc7bb10..ad3f48f3860 100644 --- a/idea/testData/inspections/unusedSymbol/class/headerImpl.kt +++ b/idea/testData/inspections/unusedSymbol/class/headerImpl.kt @@ -1,9 +1,9 @@ // No "unused symbol" should be reported here - expect class My +// But this should be reported actual class My -// But this should be reported +// And this too expect val bar: String diff --git a/idea/testData/inspections/unusedSymbol/class/inspectionData/expected.xml b/idea/testData/inspections/unusedSymbol/class/inspectionData/expected.xml index 3b9869c7d6d..fdcdb1a03a4 100644 --- a/idea/testData/inspections/unusedSymbol/class/inspectionData/expected.xml +++ b/idea/testData/inspections/unusedSymbol/class/inspectionData/expected.xml @@ -63,6 +63,14 @@ Unused symbol Class 'Some' is never used + + headerImpl.kt + 5 + light_idea_test_case + + Unused symbol + Class 'My' is never used + headerImpl.kt 8 diff --git a/idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/common/common.kt b/idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/common/common.kt new file mode 100644 index 00000000000..cd5bd0646b4 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/common/common.kt @@ -0,0 +1,7 @@ +expect class My constructor() { + fun foo() +} + +fun test() { + My().foo() +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/common/common.kt.after b/idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/common/common.kt.after new file mode 100644 index 00000000000..cd5bd0646b4 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/common/common.kt.after @@ -0,0 +1,7 @@ +expect class My constructor() { + fun foo() +} + +fun test() { + My().foo() +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/jvm/jvm.kt b/idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/jvm/jvm.kt new file mode 100644 index 00000000000..6c370b925be --- /dev/null +++ b/idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/jvm/jvm.kt @@ -0,0 +1,8 @@ +// "Safe delete 'foo'" "false" +// TOOL: org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection +// ACTION: Convert member to extension +// ACTION: Move to companion object + +actual class My { + actual fun foo() {} +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/jvm/jvm.kt.after b/idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/jvm/jvm.kt.after new file mode 100644 index 00000000000..9107474115a --- /dev/null +++ b/idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/jvm/jvm.kt.after @@ -0,0 +1,8 @@ +// "Safe delete 'foo'" "false" +// TOOL: org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection +// ACTION: Convert member to extension +// ACTION: Move to companion object + +actual class My { + actual fun foo() {} +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/safeDeleteFromActual/common/common.kt b/idea/testData/multiModuleQuickFix/safeDeleteFromActual/common/common.kt new file mode 100644 index 00000000000..c301ddfdb93 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/safeDeleteFromActual/common/common.kt @@ -0,0 +1,4 @@ +expect class My constructor() { + // TODO: also should be deleted (KT-15666) + fun foo() +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/safeDeleteFromActual/common/common.kt.after b/idea/testData/multiModuleQuickFix/safeDeleteFromActual/common/common.kt.after new file mode 100644 index 00000000000..c301ddfdb93 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/safeDeleteFromActual/common/common.kt.after @@ -0,0 +1,4 @@ +expect class My constructor() { + // TODO: also should be deleted (KT-15666) + fun foo() +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/safeDeleteFromActual/jvm/jvm.kt b/idea/testData/multiModuleQuickFix/safeDeleteFromActual/jvm/jvm.kt new file mode 100644 index 00000000000..25c67d667fa --- /dev/null +++ b/idea/testData/multiModuleQuickFix/safeDeleteFromActual/jvm/jvm.kt @@ -0,0 +1,6 @@ +// "Safe delete 'foo'" "true" +// TOOL: org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection + +actual class My { + actual fun foo() {} +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/safeDeleteFromActual/jvm/jvm.kt.after b/idea/testData/multiModuleQuickFix/safeDeleteFromActual/jvm/jvm.kt.after new file mode 100644 index 00000000000..c138e09799d --- /dev/null +++ b/idea/testData/multiModuleQuickFix/safeDeleteFromActual/jvm/jvm.kt.after @@ -0,0 +1,5 @@ +// "Safe delete 'foo'" "true" +// TOOL: org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection + +actual class My { +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt index 2ad84a156fd..b2d995cd76a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixMultiModuleTest.kt @@ -23,7 +23,7 @@ import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.KotlinTestUtils import java.io.File -abstract class AbstractQuickFixMultiModuleTest : AbstractMultiModuleTest() { +abstract class AbstractQuickFixMultiModuleTest : AbstractMultiModuleTest(), QuickFixTest { override fun getTestDataPath() = PluginTestCaseBase.getTestDataPathBase() + "/multiModuleQuickFix/" @@ -34,10 +34,12 @@ abstract class AbstractQuickFixMultiModuleTest : AbstractMultiModuleTest() { private fun doQuickFixTest() { val actionFile = project.findFileWithCaret() - configureByExistingFile(actionFile.virtualFile!!) - + val virtualFile = actionFile.virtualFile!! + configureByExistingFile(virtualFile) val actionFileText = actionFile.text val actionFileName = actionFile.name + val inspections = parseInspectionsToEnable(virtualFile.path, actionFileText).toTypedArray() + enableInspectionTools(*inspections) CommandProcessor.getInstance().executeCommand(project, { try { diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.kt index 1fcf78de605..1e9025b5637 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.kt @@ -18,19 +18,18 @@ package org.jetbrains.kotlin.idea.quickfix import com.intellij.codeInsight.daemon.quickFix.ActionHint import com.intellij.codeInsight.intention.IntentionAction -import com.intellij.codeInspection.InspectionProfileEntry -import com.intellij.codeInspection.LocalInspectionTool import com.intellij.codeInspection.SuppressableProblemGroup import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.vfs.CharsetToolkit import com.intellij.rt.execution.junit.FileComparisonFailure -import com.intellij.testFramework.* +import com.intellij.testFramework.LightPlatformTestCase +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.UsefulTestCase import com.intellij.util.ui.UIUtil import junit.framework.TestCase import org.jetbrains.kotlin.idea.facet.KotlinFacet -import org.jetbrains.kotlin.idea.quickfix.utils.findInspectionFile import org.jetbrains.kotlin.idea.test.* import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.test.InTextDirectivesUtils @@ -38,13 +37,13 @@ import org.junit.Assert import java.io.File import java.io.IOException -abstract class AbstractQuickFixTest : KotlinLightCodeInsightFixtureTestCase() { +abstract class AbstractQuickFixTest : KotlinLightCodeInsightFixtureTestCase(), QuickFixTest { @Throws(Exception::class) protected fun doTest(beforeFileName: String) { val beforeFileText = FileUtil.loadFile(File(beforeFileName)) configureCompilerOptions(beforeFileText, project, module) - val inspections = parceInspectionsToEnable(beforeFileName, beforeFileText).toTypedArray() + val inspections = parseInspectionsToEnable(beforeFileName, beforeFileText).toTypedArray() try { myFixture.enableInspections(*inspections) @@ -171,31 +170,6 @@ abstract class AbstractQuickFixTest : KotlinLightCodeInsightFixtureTestCase() { } } - private fun parceInspectionsToEnable(beforeFileName: String, beforeFileText: String): List { - val toolsStrings = InTextDirectivesUtils.findListWithPrefixes(beforeFileText, "TOOL:") - if (toolsStrings.isNotEmpty()) { - val inspections = toolsStrings.map { toolFqName -> - try { - val aClass = Class.forName(toolFqName) - return@map aClass.newInstance() as LocalInspectionTool - } - catch (e: Exception) { - throw IllegalArgumentException("Failed to create inspection for key '$toolFqName'", e) - } - } - return inspections - } - - val inspectionFile = findInspectionFile(File(beforeFileName).parentFile) - if (inspectionFile != null) { - val className = FileUtil.loadFile(inspectionFile).trim { it <= ' ' } - val inspectionClass = Class.forName(className) as Class - return InspectionTestUtil.instantiateTools(listOf>(inspectionClass)) - } - - return emptyList() - } - @Throws(ClassNotFoundException::class) private fun checkForUnexpectedActions() { val text = myFixture.editor.document.text diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.kt.172 b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.kt.172 index 63a5c82231c..b01504cce5e 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.kt.172 +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.kt.172 @@ -18,8 +18,6 @@ package org.jetbrains.kotlin.idea.quickfix import com.intellij.codeInsight.daemon.quickFix.ActionHint import com.intellij.codeInsight.intention.IntentionAction -import com.intellij.codeInspection.InspectionProfileEntry -import com.intellij.codeInspection.LocalInspectionTool import com.intellij.codeInspection.SuppressableProblemGroup import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.util.io.FileUtil @@ -30,7 +28,6 @@ import com.intellij.testFramework.* import com.intellij.util.ui.UIUtil import junit.framework.TestCase import org.jetbrains.kotlin.idea.facet.KotlinFacet -import org.jetbrains.kotlin.idea.quickfix.utils.findInspectionFile import org.jetbrains.kotlin.idea.test.* import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.test.InTextDirectivesUtils @@ -38,13 +35,13 @@ import org.junit.Assert import java.io.File import java.io.IOException -abstract class AbstractQuickFixTest : KotlinLightCodeInsightFixtureTestCase() { +abstract class AbstractQuickFixTest : KotlinLightCodeInsightFixtureTestCase(), QuickFixTest { @Throws(Exception::class) protected fun doTest(beforeFileName: String) { val beforeFileText = FileUtil.loadFile(File(beforeFileName)) configureCompilerOptions(beforeFileText, project, module) - val inspections = parceInspectionsToEnable(beforeFileName, beforeFileText).toTypedArray() + val inspections = parseInspectionsToEnable(beforeFileName, beforeFileText).toTypedArray() try { myFixture.enableInspections(*inspections) @@ -156,31 +153,6 @@ abstract class AbstractQuickFixTest : KotlinLightCodeInsightFixtureTestCase() { } } - private fun parceInspectionsToEnable(beforeFileName: String, beforeFileText: String): List { - val toolsStrings = InTextDirectivesUtils.findListWithPrefixes(beforeFileText, "TOOL:") - if (toolsStrings.isNotEmpty()) { - val inspections = toolsStrings.map { toolFqName -> - try { - val aClass = Class.forName(toolFqName) - return@map aClass.newInstance() as LocalInspectionTool - } - catch (e: Exception) { - throw IllegalArgumentException("Failed to create inspection for key '$toolFqName'", e) - } - } - return inspections - } - - val inspectionFile = findInspectionFile(File(beforeFileName).parentFile) - if (inspectionFile != null) { - val className = FileUtil.loadFile(inspectionFile).trim { it <= ' ' } - val inspectionClass = Class.forName(className) as Class - return InspectionTestUtil.instantiateTools(listOf>(inspectionClass)) - } - - return emptyList() - } - @Throws(ClassNotFoundException::class) private fun checkForUnexpectedActions() { val text = myFixture.editor.document.text diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java index 1c5456f157e..f75cd6cf539 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTestGenerated.java @@ -334,6 +334,16 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul runTest("idea/testData/multiModuleQuickFix/property/"); } + @TestMetadata("safeDeleteForbiddenFromActual") + public void testSafeDeleteForbiddenFromActual() throws Exception { + runTest("idea/testData/multiModuleQuickFix/safeDeleteForbiddenFromActual/"); + } + + @TestMetadata("safeDeleteFromActual") + public void testSafeDeleteFromActual() throws Exception { + runTest("idea/testData/multiModuleQuickFix/safeDeleteFromActual/"); + } + @TestMetadata("sealed") public void testSealed() throws Exception { runTest("idea/testData/multiModuleQuickFix/sealed/"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTest.kt new file mode 100644 index 00000000000..fa82e5754f8 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTest.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.quickfix + +import com.intellij.codeInspection.InspectionProfileEntry +import com.intellij.codeInspection.LocalInspectionTool +import com.intellij.openapi.util.io.FileUtil +import com.intellij.testFramework.InspectionTestUtil +import org.jetbrains.kotlin.idea.quickfix.utils.findInspectionFile +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import java.io.File + +interface QuickFixTest { + fun parseInspectionsToEnable(beforeFileName: String, beforeFileText: String): List { + val toolsStrings = InTextDirectivesUtils.findListWithPrefixes(beforeFileText, "TOOL:") + if (toolsStrings.isNotEmpty()) { + return toolsStrings.map { toolFqName -> + try { + val aClass = Class.forName(toolFqName) + return@map aClass.newInstance() as LocalInspectionTool + } catch (e: Exception) { + throw IllegalArgumentException("Failed to create inspection for key '$toolFqName'", e) + } + } + } + + val inspectionFile = findInspectionFile(File(beforeFileName).parentFile) + if (inspectionFile != null) { + val className = FileUtil.loadFile(inspectionFile).trim { it <= ' ' } + val inspectionClass = Class.forName(className) as Class + return InspectionTestUtil.instantiateTools(listOf>(inspectionClass)) + } + + return emptyList() + } +} \ No newline at end of file