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