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:
Mikhail Glukhikh
2018-06-20 18:51:18 +03:00
parent e885e54233
commit 7e0e7dc983
16 changed files with 135 additions and 75 deletions
@@ -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
+2 -2
View File
@@ -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>
@@ -0,0 +1,7 @@
expect class My constructor() {
fun foo()
}
fun test() {
My().foo()
}
@@ -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() {}
}
@@ -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
@@ -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()
}
}