From e98edbcbacf2bead86eb4a61bb4f338a845969f8 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Fri, 28 Aug 2015 21:37:56 +0300 Subject: [PATCH] Code cleanup to remove deprecated symbol imports that have ReplaceWith --- .../org/jetbrains/kotlin/idea/core/Utils.kt | 9 +++++ .../inspections/KotlinCleanupInspection.kt | 36 +++++++++++++++---- .../inspections/UnusedImportInspection.kt | 8 ++--- .../idea/quickfix/DeprecatedSymbolUsageFix.kt | 5 +++ .../SafeDeleteImportDirectiveUsageInfo.kt | 29 ++++----------- .../idea/util/ImportInsertHelperImpl.kt | 6 ++-- idea/testData/inspections/cleanup/cleanup.kt | 6 ++++ .../inspections/cleanup/cleanup.kt.after | 5 +++ .../inspections/cleanup/deprecatedSymbols.kt | 10 ++++++ 9 files changed, 76 insertions(+), 38 deletions(-) diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/Utils.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/Utils.kt index c505a0a2cf1..2d7a82ce68b 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/Utils.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/Utils.kt @@ -19,11 +19,15 @@ package org.jetbrains.kotlin.idea.core import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getFileTopLevelScope +import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstanceToExpression import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.scopes.JetScope @@ -102,3 +106,8 @@ public fun PsiElement.getResolutionScope(bindingContext: BindingContext, resolut } error("Not in JetFile") } + +public fun JetImportDirective.targetDescriptors(): Collection { + val nameExpression = importedReference?.getQualifiedElementSelector() as? JetSimpleNameExpression ?: return emptyList() + return nameExpression.mainReference.resolveToDescriptors(nameExpression.analyze()) +} diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt index fc7f013d245..b1799d1b526 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/KotlinCleanupInspection.kt @@ -29,16 +29,17 @@ import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult import org.jetbrains.kotlin.idea.highlighter.JetPsiChecker import org.jetbrains.kotlin.idea.quickfix.CleanupFix +import org.jetbrains.kotlin.idea.quickfix.DeprecatedSymbolUsageFix +import org.jetbrains.kotlin.idea.quickfix.JetIntentionAction import org.jetbrains.kotlin.idea.quickfix.ReplaceObsoleteLabelSyntaxFix import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.psi.JetAnnotationEntry import org.jetbrains.kotlin.psi.JetFile +import org.jetbrains.kotlin.psi.JetImportDirective import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType -import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm -import kotlin.properties.Delegates public class KotlinCleanupInspection(): LocalInspectionTool(), CleanupLocalInspectionTool { // required to simplify the inspection registration in tests @@ -61,6 +62,14 @@ public class KotlinCleanupInspection(): LocalInspectionTool(), CleanupLocalInspe } val problems = arrayListOf() + + val importsToRemove = file.importDirectives.filter { DeprecatedSymbolUsageFix.isImportToBeRemoved(it) } + for (import in importsToRemove) { + val removeImportFix = RemoveImportFix(import) + val problemDescriptor = createProblemDescriptor(import, removeImportFix.text, listOf(removeImportFix), file, manager) + problems.add(ProblemData(problemDescriptor, import)) + } + file.forEachDescendantOfType { element -> for (diagnostic in diagnostics.forElement(element)) { if (diagnostic.isCleanup()) { @@ -111,10 +120,23 @@ public class KotlinCleanupInspection(): LocalInspectionTool(), CleanupLocalInspe } private fun Diagnostic.toProblemDescriptor(fixes: Collection, file: JetFile, manager: InspectionManager): ProblemDescriptor { - return manager.createProblemDescriptor(getPsiElement(), - DefaultErrorMessages.render(this), - false, - fixes.map { Wrapper(it, file) }.toTypedArray(), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING) + return createProblemDescriptor(psiElement, DefaultErrorMessages.render(this), fixes, file, manager) + } + + private fun createProblemDescriptor(element: PsiElement, message: String, fixes: Collection, file: JetFile, manager: InspectionManager): ProblemDescriptor { + return manager.createProblemDescriptor(element, + message, + false, + fixes.map { Wrapper(it, file) }.toTypedArray(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING) + } + + private class RemoveImportFix(import: JetImportDirective) : JetIntentionAction(import), CleanupFix { + override fun getFamilyName() = "Remove deprecated symbol import" + override fun getText() = familyName + + override fun invoke(project: Project, editor: Editor?, file: JetFile) { + element.delete() + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedImportInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedImportInspection.kt index d2e1ff3b7dc..6efb5b3062e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedImportInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/UnusedImportInspection.kt @@ -41,15 +41,12 @@ import com.intellij.psi.PsiFile import com.intellij.util.DocumentUtil import com.intellij.util.Processor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.idea.imports.KotlinImportOptimizer import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.JetFile -import org.jetbrains.kotlin.psi.JetSimpleNameExpression -import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector import org.jetbrains.kotlin.resolve.ImportPath -import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets import java.util.* class UnusedImportInspection : AbstractKotlinInspection() { @@ -104,8 +101,7 @@ class UnusedImportInspection : AbstractKotlinInspection() { } if (!isUsed) { - val nameExpression = directive.importedReference?.getQualifiedElementSelector() as? JetSimpleNameExpression - if (nameExpression == null || nameExpression.getReferenceTargets(nameExpression.analyze()).isEmpty()) continue // do not highlight unresolved imports as unused + if (directive.targetDescriptors().isEmpty()) continue // do not highlight unresolved imports as unused val fixes = arrayListOf() fixes.add(OptimizeImportsQuickFix(file)) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFix.kt index 7951e13622e..61591487d7c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/DeprecatedSymbolUsageFix.kt @@ -24,6 +24,7 @@ import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny @@ -72,5 +73,9 @@ public class DeprecatedSymbolUsageFix( return DeprecatedSymbolUsageFix(nameExpression, replacement) } + public fun isImportToBeRemoved(import: JetImportDirective): Boolean { + return !import.isAllUnder + && import.targetDescriptors().all { DeprecatedSymbolUsageFixBase.replaceWithPattern(it, import.project) != null } + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/SafeDeleteImportDirectiveUsageInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/SafeDeleteImportDirectiveUsageInfo.kt index 17acf326773..c545c24c34e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/SafeDeleteImportDirectiveUsageInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/safeDelete/SafeDeleteImportDirectiveUsageInfo.kt @@ -17,31 +17,16 @@ package org.jetbrains.kotlin.idea.refactoring.safeDelete import com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteReferenceSimpleDeleteUsageInfo -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.core.targetDescriptors +import org.jetbrains.kotlin.psi.JetDeclaration +import org.jetbrains.kotlin.psi.JetImportDirective public class SafeDeleteImportDirectiveUsageInfo( importDirective: JetImportDirective, declaration: JetDeclaration ) : SafeDeleteReferenceSimpleDeleteUsageInfo(importDirective, declaration, importDirective.isSafeToDelete(declaration)) -fun JetImportDirective.isSafeToDelete(declaration: JetDeclaration): Boolean { - val importExpr = getImportedReference() - val importReference: JetReferenceExpression? = when (importExpr) { - is JetSimpleNameExpression -> - importExpr - is JetDotQualifiedExpression -> - importExpr.getSelectorExpression()?.let { selector -> - if (selector is JetSimpleNameExpression) selector else null - } - else -> null - } - - if (importReference != null) { - val bindingContext = importReference.analyze() - val referenceDescriptor = bindingContext.get(BindingContext.REFERENCE_TARGET, importReference) - val declarationDescriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) - return referenceDescriptor == declarationDescriptor - } - return false +private fun JetImportDirective.isSafeToDelete(declaration: JetDeclaration): Boolean { + val referencedDescriptor = targetDescriptors().singleOrNull() ?: return false + return referencedDescriptor == declaration.resolveToDescriptor() } diff --git a/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt b/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt index 2ef4b95b311..a0c91d8eab8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt +++ b/idea/src/org/jetbrains/kotlin/idea/util/ImportInsertHelperImpl.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.caches.resolve.getFileTopLevelScope import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.core.formatter.JetCodeStyleSettings +import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.idea.imports.getImportableTargets import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.project.ProjectStructureUtil @@ -299,9 +300,8 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert val importsToCheck = ArrayList() for (import in dropCandidates) { - val importedReference = import.getImportedReference() ?: continue - val refExpr = (importedReference as JetDotQualifiedExpression).getSelectorExpression() as JetSimpleNameExpression - val targets = refExpr.resolveTargets() + if (import.importedReference == null) continue + val targets = import.targetDescriptors() if (targets.any { it is PackageViewDescriptor }) continue // do not drop import of package val classDescriptor = targets.filterIsInstance().firstOrNull() importsToCheck.addIfNotNull(classDescriptor?.importableFqName) diff --git a/idea/testData/inspections/cleanup/cleanup.kt b/idea/testData/inspections/cleanup/cleanup.kt index 11980332fc3..14f3431b8d5 100644 --- a/idea/testData/inspections/cleanup/cleanup.kt +++ b/idea/testData/inspections/cleanup/cleanup.kt @@ -1,3 +1,7 @@ +import pack.oldFun1 +import pack.oldFun2 // should not be removed for non-deprecated overload used +import pack.oldFun3 + trait Foo { } @@ -16,6 +20,8 @@ fun foo() { } oldFun1(oldFun2(10)) + + oldFun2() } fun unnecessarySafeCall(x: String) { diff --git a/idea/testData/inspections/cleanup/cleanup.kt.after b/idea/testData/inspections/cleanup/cleanup.kt.after index 00dd7ff4a2c..dcf8a1e1d5c 100644 --- a/idea/testData/inspections/cleanup/cleanup.kt.after +++ b/idea/testData/inspections/cleanup/cleanup.kt.after @@ -1,3 +1,6 @@ +import pack.bar +import pack.oldFun2 // should not be removed for non-deprecated overload used + interface Foo { } @@ -16,6 +19,8 @@ fun foo() { } bar(bar(10 + 2) + 1) + + oldFun2() } fun unnecessarySafeCall(x: String) { diff --git a/idea/testData/inspections/cleanup/deprecatedSymbols.kt b/idea/testData/inspections/cleanup/deprecatedSymbols.kt index 4b725f61944..1cacfa6a107 100644 --- a/idea/testData/inspections/cleanup/deprecatedSymbols.kt +++ b/idea/testData/inspections/cleanup/deprecatedSymbols.kt @@ -1,7 +1,17 @@ +package pack + @deprecated("", ReplaceWith("bar(p + 1)")) public fun oldFun1(p: Int): Int = 0 +@deprecated("", ReplaceWith("bar(-1)")) +public fun oldFun1(): Int = 0 + @deprecated("", ReplaceWith("bar(p + 2)")) public fun oldFun2(p: Int): Int = 0 +public fun oldFun2(): Int = 0 + +@deprecated("", ReplaceWith("bar(p)")) +public fun oldFun3(p: Int): Int = 0 + public fun bar(p: Int): Int = p \ No newline at end of file