diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/CallableUsageReplacementStrategy.kt b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/CallableUsageReplacementStrategy.kt similarity index 94% rename from idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/CallableUsageReplacementStrategy.kt rename to idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/CallableUsageReplacementStrategy.kt index 98a87d78259..da184b6ccf9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/CallableUsageReplacementStrategy.kt +++ b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/CallableUsageReplacementStrategy.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,10 @@ * limitations under the License. */ -package org.jetbrains.kotlin.idea.quickfix.replaceWith +package org.jetbrains.kotlin.idea.replacement import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention -import org.jetbrains.kotlin.idea.replacement.* import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ClassUsageReplacementStrategy.kt b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ClassUsageReplacementStrategy.kt similarity index 95% rename from idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ClassUsageReplacementStrategy.kt rename to idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ClassUsageReplacementStrategy.kt index d8de59ea5cc..38f2246d1a5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ClassUsageReplacementStrategy.kt +++ b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/ClassUsageReplacementStrategy.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,11 @@ * limitations under the License. */ -package org.jetbrains.kotlin.idea.quickfix.replaceWith +package org.jetbrains.kotlin.idea.replacement import com.intellij.openapi.project.Project import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.core.replaced -import org.jetbrains.kotlin.idea.replacement.ReplacementExpression import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis import org.jetbrains.kotlin.utils.addToStdlib.check diff --git a/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/UsageReplacementStrategy.kt b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/UsageReplacementStrategy.kt new file mode 100644 index 00000000000..a8e04f71fcb --- /dev/null +++ b/idea/idea-replacement-engine/src/org/jetbrains/kotlin/idea/replacement/UsageReplacementStrategy.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.replacement + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.util.ui.UIUtil +import org.jetbrains.kotlin.idea.core.targetDescriptors +import org.jetbrains.kotlin.idea.references.KtSimpleNameReference +import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtImportDirective +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType + +interface UsageReplacementStrategy { + fun createReplacer(usage: KtSimpleNameExpression): (() -> KtElement)? +} + +private val LOG = Logger.getInstance(UsageReplacementStrategy::class.java) + +fun UsageReplacementStrategy.replaceUsagesInWholeProject( + targetPsiElement: PsiElement, + progressTitle: String, + commandName: String +) { + val project = targetPsiElement.project + ProgressManager.getInstance().run( + object : Task.Modal(project, progressTitle, true) { + override fun run(indicator: ProgressIndicator) { + val usages = runReadAction { + val searchScope = KotlinSourceFilterScope.projectSources(GlobalSearchScope.projectScope(project), project) + ReferencesSearch.search(targetPsiElement, searchScope) + .filterIsInstance() + .map { ref -> ref.expression } + } + this@replaceUsagesInWholeProject.replaceUsages(usages, project, commandName) + } + }) +} + +private fun UsageReplacementStrategy.replaceUsages( + usages: Collection, + project: Project, + commandName: String +) { + UIUtil.invokeLaterIfNeeded { + project.executeWriteCommand(commandName) { + // we should delete imports later to not affect other usages + val importsToDelete = arrayListOf() + + for (usage in usages) { + try { + if (!usage.isValid) continue // TODO: nested calls + + //TODO: keep the import if we don't know how to replace some of the usages + val importDirective = usage.getStrictParentOfType() + if (importDirective != null) { + if (!importDirective.isAllUnder && importDirective.targetDescriptors().size == 1) { + importsToDelete.add(importDirective) + } + continue + } + + createReplacer(usage)?.invoke() + } + catch (e: Throwable) { + LOG.error(e) + } + } + + importsToDelete.forEach { it.delete() } + } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFix.kt index 86f5c1f8e42..bca46bf8f16 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFix.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.idea.quickfix.CleanupFix import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory import org.jetbrains.kotlin.idea.core.moveCaret +import org.jetbrains.kotlin.idea.replacement.UsageReplacementStrategy import org.jetbrains.kotlin.psi.KtImportDirective import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt index b154afd2cc5..b911e540fd2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt @@ -20,21 +20,31 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.DiagnosticFactory import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.core.OptionalParametersHelper import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.replacement.CallableUsageReplacementStrategy +import org.jetbrains.kotlin.idea.replacement.ClassUsageReplacementStrategy +import org.jetbrains.kotlin.idea.replacement.UsageReplacementStrategy import org.jetbrains.kotlin.psi.KtConstructorCalleeExpression import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.annotations.argumentValue +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode //TODO: different replacements for property accessors @@ -46,13 +56,13 @@ abstract class DeprecatedSymbolUsageFixBase( override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean { val element = element ?: return false if (!super.isAvailable(project, editor, file)) return false - val strategy = UsageReplacementStrategy.build(element, replaceWith, recheckAnnotation = true) + val strategy = buildUsageReplacementStrategy(element, replaceWith, recheckAnnotation = true) return strategy != null && strategy.createReplacer(element) != null } final override fun invoke(project: Project, editor: Editor?, file: KtFile) { val element = element ?: return - val strategy = UsageReplacementStrategy.build(element, replaceWith, recheckAnnotation = false)!! + val strategy = buildUsageReplacementStrategy(element, replaceWith, recheckAnnotation = false)!! invoke(strategy, project, editor) } @@ -106,5 +116,43 @@ abstract class DeprecatedSymbolUsageFixBase( val replacement = DeprecatedSymbolUsageFixBase.fetchReplaceWithPattern(descriptor, nameExpression.project) ?: return null return Data(nameExpression, replacement, descriptor) } + + private fun buildUsageReplacementStrategy(element: KtSimpleNameExpression, replaceWith: ReplaceWith, recheckAnnotation: Boolean): UsageReplacementStrategy? { + val resolutionFacade = element.getResolutionFacade() + val bindingContext = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL) + var target = element.mainReference.resolveToDescriptors(bindingContext).singleOrNull() ?: return null + + var replacePatternFromSymbol = DeprecatedSymbolUsageFixBase.fetchReplaceWithPattern(target, resolutionFacade.project) + if (replacePatternFromSymbol == null && target is ConstructorDescriptor) { + target = target.containingDeclaration + replacePatternFromSymbol = DeprecatedSymbolUsageFixBase.fetchReplaceWithPattern(target, resolutionFacade.project) + } + + // check that ReplaceWith hasn't changed + if (recheckAnnotation && replacePatternFromSymbol != replaceWith) return null + + when (target) { + is CallableDescriptor -> { + val resolvedCall = element.getResolvedCall(bindingContext) ?: return null + if (!resolvedCall.isReallySuccess()) return null + val replacement = ReplaceWithAnnotationAnalyzer.analyzeCallableReplacement(replaceWith, target, resolutionFacade) ?: return null + return CallableUsageReplacementStrategy(replacement) + } + + is ClassDescriptor -> { + val replacementType = ReplaceWithAnnotationAnalyzer.analyzeClassReplacement(replaceWith, target, resolutionFacade) + if (replacementType != null) { //TODO: check that it's really resolved and is not an object otherwise it can be expression as well + return ClassUsageReplacementStrategy(replacementType, null, element.project) + } + else { + val constructor = target.unsubstitutedPrimaryConstructor ?: return null + val replacementExpression = ReplaceWithAnnotationAnalyzer.analyzeCallableReplacement(replaceWith, constructor, resolutionFacade) ?: return null + return ClassUsageReplacementStrategy(null, replacementExpression, element.project) + } + } + + else -> return null + } + } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageInWholeProjectFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageInWholeProjectFix.kt index 75048a7ed14..095b2b00d6a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageInWholeProjectFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageInWholeProjectFix.kt @@ -17,28 +17,17 @@ package org.jetbrains.kotlin.idea.quickfix.replaceWith import com.intellij.codeInsight.intention.IntentionAction -import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Editor -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile -import com.intellij.psi.search.GlobalSearchScope -import com.intellij.psi.search.searches.ReferencesSearch -import com.intellij.util.ui.UIUtil import org.jetbrains.kotlin.diagnostics.Diagnostic -import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory -import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.references.mainReference -import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope -import org.jetbrains.kotlin.idea.util.application.executeWriteCommand -import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.idea.replacement.UsageReplacementStrategy +import org.jetbrains.kotlin.idea.replacement.replaceUsagesInWholeProject import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType -import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.ClassifierNamePolicy +import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy class DeprecatedSymbolUsageInWholeProjectFix( @@ -47,8 +36,6 @@ class DeprecatedSymbolUsageInWholeProjectFix( private val text: String ) : DeprecatedSymbolUsageFixBase(element, replaceWith) { - private val LOG = Logger.getInstance(DeprecatedSymbolUsageInWholeProjectFix::class.java) - override fun getFamilyName() = "Replace deprecated symbol usage in whole project" override fun getText() = text @@ -62,19 +49,7 @@ class DeprecatedSymbolUsageInWholeProjectFix( override fun invoke(replacementStrategy: UsageReplacementStrategy, project: Project, editor: Editor?) { val psiElement = targetPsiElement()!! - - ProgressManager.getInstance().run( - object : Task.Modal(project, "Applying '$text'", true) { - override fun run(indicator: ProgressIndicator) { - val usages = runReadAction { - val searchScope = KotlinSourceFilterScope.projectSources(GlobalSearchScope.projectScope(project), project) - ReferencesSearch.search(psiElement, searchScope) - .filterIsInstance() - .map { ref -> ref.expression } - } - replaceUsages(project, usages, replacementStrategy) - } - }) + replacementStrategy.replaceUsagesInWholeProject(psiElement, progressTitle = "Applying '$text'", commandName = text) } private fun targetPsiElement(): KtDeclaration? { @@ -87,37 +62,6 @@ class DeprecatedSymbolUsageInWholeProjectFix( } } - private fun replaceUsages(project: Project, usages: Collection, replacementStrategy: UsageReplacementStrategy) { - UIUtil.invokeLaterIfNeeded { - project.executeWriteCommand(text) { - // we should delete imports later to not affect other usages - val importsToDelete = arrayListOf() - - for (usage in usages) { - try { - if (!usage.isValid) continue // TODO: nested calls - - //TODO: keep the import if we don't know how to replace some of the usages - val importDirective = usage.getStrictParentOfType() - if (importDirective != null) { - if (!importDirective.isAllUnder && importDirective.targetDescriptors().size == 1) { - importsToDelete.add(importDirective) - } - continue - } - - replacementStrategy.createReplacer(usage)?.invoke() - } - catch (e: Throwable) { - LOG.error(e) - } - } - - importsToDelete.forEach { it.delete() } - } - } - } - companion object : KotlinSingleIntentionActionFactory() { //TODO: better rendering needed private val RENDERER = DescriptorRenderer.withOptions { @@ -136,6 +80,5 @@ class DeprecatedSymbolUsageInWholeProjectFix( val descriptorName = RENDERER.render(descriptor) return DeprecatedSymbolUsageInWholeProjectFix(nameExpression, replacement, "Replace usages of '$descriptorName' in whole project") } - } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/UsageReplacementStrategy.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/UsageReplacementStrategy.kt deleted file mode 100644 index 9b43b2c76a4..00000000000 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/UsageReplacementStrategy.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.idea.quickfix.replaceWith - -import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.ConstructorDescriptor -import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade -import org.jetbrains.kotlin.idea.references.mainReference -import org.jetbrains.kotlin.psi.KtElement -import org.jetbrains.kotlin.psi.KtSimpleNameExpression -import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall -import org.jetbrains.kotlin.resolve.calls.model.isReallySuccess -import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode - -interface UsageReplacementStrategy { - fun createReplacer(usage: KtSimpleNameExpression): (() -> KtElement)? - - companion object { - fun build(element: KtSimpleNameExpression, replaceWith: ReplaceWith, recheckAnnotation: Boolean): UsageReplacementStrategy? { - val resolutionFacade = element.getResolutionFacade() - val bindingContext = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL) - var target = element.mainReference.resolveToDescriptors(bindingContext).singleOrNull() ?: return null - - var replacePatternFromSymbol = DeprecatedSymbolUsageFixBase.fetchReplaceWithPattern(target, resolutionFacade.project) - if (replacePatternFromSymbol == null && target is ConstructorDescriptor) { - target = target.containingDeclaration - replacePatternFromSymbol = DeprecatedSymbolUsageFixBase.fetchReplaceWithPattern(target, resolutionFacade.project) - } - - // check that ReplaceWith hasn't changed - if (recheckAnnotation && replacePatternFromSymbol != replaceWith) return null - - when (target) { - is CallableDescriptor -> { - val resolvedCall = element.getResolvedCall(bindingContext) ?: return null - if (!resolvedCall.isReallySuccess()) return null - val replacement = ReplaceWithAnnotationAnalyzer.analyzeCallableReplacement(replaceWith, target, resolutionFacade) ?: return null - return CallableUsageReplacementStrategy(replacement) - } - - is ClassDescriptor -> { - val replacementType = ReplaceWithAnnotationAnalyzer.analyzeClassReplacement(replaceWith, target, resolutionFacade) - if (replacementType != null) { //TODO: check that it's really resolved and is not an object otherwise it can be expression as well - return ClassUsageReplacementStrategy(replacementType, null, element.project) - } - else { - val constructor = target.unsubstitutedPrimaryConstructor ?: return null - val replacementExpression = ReplaceWithAnnotationAnalyzer.analyzeCallableReplacement(replaceWith, constructor, resolutionFacade) ?: return null - return ClassUsageReplacementStrategy(null, replacementExpression, element.project) - } - } - - else -> return null - } - } - } -} \ No newline at end of file