Refactoring

This commit is contained in:
Valentin Kipyatkov
2016-10-19 22:06:07 +03:00
parent f2b32f6d90
commit 7324ae145e
7 changed files with 156 additions and 141 deletions
@@ -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
@@ -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
@@ -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<KtSimpleNameReference>()
.map { ref -> ref.expression }
}
this@replaceUsagesInWholeProject.replaceUsages(usages, project, commandName)
}
})
}
private fun UsageReplacementStrategy.replaceUsages(
usages: Collection<KtSimpleNameExpression>,
project: Project,
commandName: String
) {
UIUtil.invokeLaterIfNeeded {
project.executeWriteCommand(commandName) {
// we should delete imports later to not affect other usages
val importsToDelete = arrayListOf<KtImportDirective>()
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<KtImportDirective>()
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() }
}
}
}
@@ -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
@@ -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
}
}
}
}
@@ -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<KtSimpleNameReference>()
.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<KtSimpleNameExpression>, replacementStrategy: UsageReplacementStrategy) {
UIUtil.invokeLaterIfNeeded {
project.executeWriteCommand(text) {
// we should delete imports later to not affect other usages
val importsToDelete = arrayListOf<KtImportDirective>()
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<KtImportDirective>()
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")
}
}
}
@@ -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
}
}
}
}