Unused symbol: remove forced "used" from actual declarations
Now we really check whether actual declaration has usages (in expect or actual code) or not Related to KT-17512 Related to KT-15666
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -63,6 +63,14 @@
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Unused symbol</problem_class>
|
||||
<description>Class 'Some' is never used</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>headerImpl.kt</file>
|
||||
<line>5</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/headerImpl.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Unused symbol</problem_class>
|
||||
<description>Class 'My' is never used</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>headerImpl.kt</file>
|
||||
<line>8</line>
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
expect class My constructor() {
|
||||
fun foo()
|
||||
}
|
||||
|
||||
fun test() {
|
||||
My().foo()
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
expect class My constructor() {
|
||||
fun foo()
|
||||
}
|
||||
|
||||
fun test() {
|
||||
My().foo()
|
||||
}
|
||||
@@ -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 <caret>foo() {}
|
||||
}
|
||||
+8
@@ -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() {}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
expect class My constructor() {
|
||||
// TODO: also should be deleted (KT-15666)
|
||||
fun foo()
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
expect class My constructor() {
|
||||
// TODO: also should be deleted (KT-15666)
|
||||
fun foo()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Safe delete 'foo'" "true"
|
||||
// TOOL: org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection
|
||||
|
||||
actual class My {
|
||||
actual fun <caret>foo() {}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Safe delete 'foo'" "true"
|
||||
// TOOL: org.jetbrains.kotlin.idea.inspections.UnusedSymbolInspection
|
||||
|
||||
actual class My {
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<InspectionProfileEntry> {
|
||||
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<InspectionProfileEntry>
|
||||
return InspectionTestUtil.instantiateTools(listOf<Class<out InspectionProfileEntry>>(inspectionClass))
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
@Throws(ClassNotFoundException::class)
|
||||
private fun checkForUnexpectedActions() {
|
||||
val text = myFixture.editor.document.text
|
||||
|
||||
@@ -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<InspectionProfileEntry> {
|
||||
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<InspectionProfileEntry>
|
||||
return InspectionTestUtil.instantiateTools(listOf<Class<out InspectionProfileEntry>>(inspectionClass))
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
@Throws(ClassNotFoundException::class)
|
||||
private fun checkForUnexpectedActions() {
|
||||
val text = myFixture.editor.document.text
|
||||
|
||||
+10
@@ -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/");
|
||||
|
||||
@@ -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<InspectionProfileEntry> {
|
||||
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<InspectionProfileEntry>
|
||||
return InspectionTestUtil.instantiateTools(listOf<Class<out InspectionProfileEntry>>(inspectionClass))
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user