DeprecatedSymbolUsageFix - code refactoring

This commit is contained in:
Valentin Kipyatkov
2015-05-19 14:17:39 +03:00
parent a773542d47
commit 450168d6c7
3 changed files with 235 additions and 227 deletions
@@ -16,11 +16,13 @@
package org.jetbrains.kotlin.idea.core
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementVisitor
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.psi.Call
import org.jetbrains.kotlin.psi.FunctionLiteralArgument
import org.jetbrains.kotlin.psi.ValueArgument
import org.jetbrains.kotlin.psi.*
import java.util.ArrayList
import java.util.HashMap
public fun Call.mapArgumentsToParameters(targetDescriptor: CallableDescriptor): Map<ValueArgument, ValueParameterDescriptor> {
@@ -63,3 +65,21 @@ public fun Call.mapArgumentsToParameters(targetDescriptor: CallableDescriptor):
return map
}
// TODO: it can be default value for parameter but it's not supported yet by the compiler
public inline fun <reified TElement> PsiElement.collectElementsOfType(): Collection<TElement> {
return collectElementsOfType { true }
}
public inline fun <reified TElement> PsiElement.collectElementsOfType(@inlineOptions(InlineOption.ONLY_LOCAL_RETURN) predicate: (TElement) -> Boolean): Collection<TElement> {
val result = ArrayList<TElement>()
this.accept(object : PsiRecursiveElementVisitor(){
override fun visitElement(element: PsiElement) {
if (element is TElement && predicate(element)) {
result.add(element)
}
super.visitElement(element)
}
})
return result
}
@@ -24,24 +24,21 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiRecursiveElementVisitor
import org.jetbrains.kotlin.analyzer.analyzeInContext
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.collectElementsOfType
import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester
import org.jetbrains.kotlin.idea.core.refactoring.JetNameValidator
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.intentions.setType
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
@@ -49,34 +46,23 @@ import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.psiUtil.replaced
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.scopes.*
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.utils.Printer
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.ArrayList
import java.util.LinkedHashSet
//TODO: replacement of class usages
//TODO: different replacements for property accessors
//TODO: replace all in project quickfixes on usage and on deprecated annotation
public class DeprecatedSymbolUsageFix(
element: JetSimpleNameExpression/*TODO?*/,
val replaceWith: DeprecatedSymbolUsageFix.ReplaceWith
val replaceWith: ReplaceWith
) : JetIntentionAction<JetSimpleNameExpression>(element) {
//TODO: use ReplaceWith from package kotlin
private data class ReplaceWith(val expression: String, vararg val imports: String)
override fun getFamilyName() = "Replace deprecated symbol usage"
override fun getText() = "Replace with '${replaceWith.expression}'" //TODO: substitute?
@@ -116,11 +102,12 @@ public class DeprecatedSymbolUsageFix(
explicitReceiver?.putCopyableUserData(FROM_THIS_KEY, Unit)
//TODO: infix and operator calls
var (expression, imports, parameterUsages) = replaceWith.toExpression(descriptor.getOriginal(), element.getResolutionFacade(), file, project)
var (expression, imports, parameterUsages) = ReplaceWithAnnotationAnalyzer.analyze(
replaceWith, descriptor.getOriginal(), element.getResolutionFacade(), file, project)
//TODO: implicit receiver is not always "this"
//TODO: this@
for (thisExpression in expression.collectThisExpressions()) {
for (thisExpression in expression.collectElementsOfType<JetThisExpression>()) {
if (explicitReceiver != null) {
thisExpression.replace(explicitReceiver)
}
@@ -165,7 +152,7 @@ public class DeprecatedSymbolUsageFix(
}
if (expressionToReplace.isUsedAsExpression(bindingContext)) {
val thisReplaced = expression.collectExpressionsWithData(FROM_THIS_KEY, Unit)
val thisReplaced = expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_THIS_KEY) != null }
expression = expression.introduceValue(explicitReceiver!!, expressionToReplace, bindingContext, thisReplaced, safeCall = true)
}
else {
@@ -176,14 +163,14 @@ public class DeprecatedSymbolUsageFix(
}
if (explicitReceiver != null && explicitReceiver.shouldIntroduceVariableIfUsedTwice()) {
val thisReplaced = expression.collectExpressionsWithData(FROM_THIS_KEY, Unit)
val thisReplaced = expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_THIS_KEY) != null }
if (thisReplaced.size() > 1) {
expression = expression.introduceValue(explicitReceiver, expressionToReplace, bindingContext, thisReplaced)
}
}
for ((parameter, value) in introduceValuesForParameters) {
val usagesReplaced = expression.collectExpressionsWithData(FROM_PARAMETER_KEY, parameter)
val usagesReplaced = expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(FROM_PARAMETER_KEY) == parameter }
assert(usagesReplaced.size() > 1)
expression = expression.introduceValue(value, expressionToReplace, bindingContext, usagesReplaced, nameSuggestion = parameter.getName().asString())
}
@@ -225,128 +212,6 @@ public class DeprecatedSymbolUsageFix(
editor?.moveCaret(offset)
}
private data class ReplacementExpression(
val expression: JetExpression,
val imports: Collection<FqName>,
val parameterUsages: Map<ValueParameterDescriptor, Collection<JetExpression>>
)
private fun ReplaceWith.toExpression(
symbolDescriptor: CallableDescriptor,
resolutionFacade: ResolutionFacade,
file: JetFile/*TODO: drop it*/,
project: Project
): ReplacementExpression {
val psiFactory = JetPsiFactory(project)
var expression = psiFactory.createExpression(expression)
val importFqNames = imports
.filter { FqNameUnsafe.isValid(it) }
.map { FqNameUnsafe(it) }
.filter { it.isSafe() }
.mapTo(LinkedHashSet<FqName>()) { it.toSafe() }
val symbolScope = getResolutionScope(symbolDescriptor)
val explicitlyImportedSymbols = importFqNames.flatMap { resolutionFacade.resolveImportReference(file, it) }
val scope = ChainedScope(symbolDescriptor, "ReplaceWith resolution scope", ExplicitImportsScope(explicitlyImportedSymbols), symbolScope)
val bindingContext = expression.analyzeInContext(scope)
val thisType = symbolDescriptor.getExtensionReceiverParameter()?.getType()
?: (symbolDescriptor.getContainingDeclaration() as? ClassifierDescriptor)?.getDefaultType()
val receiversToAdd = ArrayList<Pair<JetExpression, String>>()
val parameterUsageKey = Key<ValueParameterDescriptor>("parameterUsageKey")
expression.accept(object : JetVisitorVoid(){
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
val target = bindingContext[BindingContext.REFERENCE_TARGET, expression] ?: return
if (target.canBeReferencedViaImport()) {
if (target.isExtension || expression.getReceiverExpression() == null) {
importFqNames.addIfNotNull(target.importableFqName)
}
}
if (expression.getReceiverExpression() == null) {
if (target is ValueParameterDescriptor && target.getContainingDeclaration() == symbolDescriptor) {
expression.putCopyableUserData(parameterUsageKey, target)
}
val resolvedCall = expression.getResolvedCall(bindingContext)
if (resolvedCall != null && resolvedCall.getStatus().isSuccess()) {
val receiver = if (resolvedCall.getResultingDescriptor().isExtension)
resolvedCall.getExtensionReceiver()
else
resolvedCall.getDispatchReceiver()
if (receiver is ThisReceiver) {
if (receiver.getType() == thisType) {
receiversToAdd.add(expression to "this")
}
else {
val descriptor = receiver.getDeclarationDescriptor()
if (descriptor is ClassDescriptor && descriptor.isCompanionObject()) {
receiversToAdd.add(expression to IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor))
}
}
}
}
}
}
override fun visitJetElement(element: JetElement) {
element.acceptChildren(this)
}
})
for ((expr, receiverText) in receiversToAdd) {
val expressionToReplace = expr.getParent() as? JetCallExpression ?: expr
val newExpr = expressionToReplace.replaced(psiFactory.createExpressionByPattern("$receiverText.$0", expressionToReplace))
if (expressionToReplace == expression) {
expression = newExpr
}
}
val parameterUsages = symbolDescriptor.getValueParameters()
.map { it to expression.collectExpressionsWithData(parameterUsageKey, it) }
.toMap()
expression.accept(object : PsiRecursiveElementVisitor() {
override fun visitElement(element: PsiElement) {
element.putCopyableUserData(parameterUsageKey, null)
}
})
return ReplacementExpression(expression, importFqNames, parameterUsages)
}
private fun getResolutionScope(descriptor: DeclarationDescriptor): JetScope {
when (descriptor) {
is PackageFragmentDescriptor -> {
val moduleDescriptor = descriptor.getContainingDeclaration()
return getResolutionScope(moduleDescriptor.getPackage(descriptor.fqName)!!)
}
is PackageViewDescriptor ->
return descriptor.getMemberScope()
is ClassDescriptorWithResolutionScopes ->
return descriptor.getScopeForMemberDeclarationResolution()
is FunctionDescriptor ->
return FunctionDescriptorUtil.getFunctionInnerScope(getResolutionScope(descriptor.getContainingDeclaration()),
descriptor, RedeclarationHandler.DO_NOTHING)
is PropertyDescriptor ->
return JetScopeUtils.getPropertyDeclarationInnerScope(descriptor,
getResolutionScope(descriptor.getContainingDeclaration()!!),
RedeclarationHandler.DO_NOTHING)
else -> throw IllegalArgumentException("Cannot find resolution scope for $descriptor")
}
}
private fun JetExpression.introduceValue(
value: JetExpression,
insertDeclarationsBefore: JetExpression,
@@ -429,56 +294,8 @@ public class DeprecatedSymbolUsageFix(
}
}
private fun <T> JetExpression.collectExpressionsWithData(key: Key<T>, value: T): Collection<JetExpression> {
val result = ArrayList<JetExpression>()
this.accept(object : JetVisitorVoid(){
override fun visitExpression(expression: JetExpression) {
if (expression.getCopyableUserData(key) == value) {
result.add(expression)
}
else {
super.visitExpression(expression)
}
}
override fun visitJetElement(element: JetElement) {
element.acceptChildren(this)
}
})
return result
}
private fun JetExpression.collectThisExpressions(): Collection<JetThisExpression> {
val result = ArrayList<JetThisExpression>()
this.accept(object : JetVisitorVoid(){
override fun visitThisExpression(expression: JetThisExpression) {
if (expression.getLabelName() == null) {
result.add(expression)
}
}
override fun visitJetElement(element: JetElement) {
element.acceptChildren(this)
}
})
return result
}
private fun collectNameUsages(scope: JetExpression, name: String): ArrayList<JetSimpleNameExpression> {
val result = ArrayList<JetSimpleNameExpression>()
scope.accept(object : JetVisitorVoid(){
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
if (expression.getReceiverExpression() == null && expression.getReferencedName() == name) {
result.add(expression)
}
}
override fun visitJetElement(element: JetElement) {
element.acceptChildren(this)
}
})
return result
}
private fun collectNameUsages(scope: JetExpression, name: String)
= scope.collectElementsOfType<JetSimpleNameExpression> { it.getReceiverExpression() == null && it.getReferencedName() == name }
private fun JetExpression?.shouldIntroduceVariableIfUsedTwice(): Boolean {
return when (this) {
@@ -516,32 +333,4 @@ public class DeprecatedSymbolUsageFix(
return ReplaceWith(pattern, *imports.toTypedArray())
}
}
private class ExplicitImportsScope(val descriptors: Collection<DeclarationDescriptor>) : JetScope {
override fun getClassifier(name: Name) = descriptors.filter { it.getName() == name }.firstIsInstanceOrNull<ClassifierDescriptor>()
override fun getPackage(name: Name)= descriptors.filter { it.getName() == name }.firstIsInstanceOrNull<PackageViewDescriptor>()
override fun getProperties(name: Name) = descriptors.filter { it.getName() == name }.filterIsInstance<VariableDescriptor>()
override fun getLocalVariable(name: Name): VariableDescriptor? = null
override fun getFunctions(name: Name) = descriptors.filter { it.getName() == name }.filterIsInstance<FunctionDescriptor>()
override fun getContainingDeclaration(): DeclarationDescriptor {
throw UnsupportedOperationException()
}
override fun getDeclarationsByLabel(labelName: Name): Collection<DeclarationDescriptor> = emptyList()
override fun getDescriptors(kindFilter: DescriptorKindFilter, nameFilter: (Name) -> Boolean) = descriptors
override fun getImplicitReceiversHierarchy(): List<ReceiverParameterDescriptor> = emptyList()
override fun getOwnDeclaredDescriptors(): Collection<DeclarationDescriptor> = emptyList()
override fun printScopeStructure(p: Printer) {
p.println(javaClass.getName())
}
}
}
}
@@ -0,0 +1,199 @@
/*
* 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
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementVisitor
import org.jetbrains.kotlin.analyzer.analyzeInContext
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.core.collectElementsOfType
import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
import org.jetbrains.kotlin.psi.psiUtil.replaced
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.scopes.*
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver
import org.jetbrains.kotlin.utils.Printer
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.*
//TODO: use ReplaceWith from package kotlin
data class ReplaceWith(val expression: String, vararg val imports: String)
object ReplaceWithAnnotationAnalyzer {
public data class ReplacementExpression(
val expression: JetExpression,
val imports: Collection<FqName>,
val parameterUsages: Map<ValueParameterDescriptor, Collection<JetExpression>>
)
public fun analyze(
annotation: ReplaceWith,
symbolDescriptor: CallableDescriptor,
resolutionFacade: ResolutionFacade,
file: JetFile/*TODO: drop it*/,
project: Project
): ReplacementExpression {
val psiFactory = JetPsiFactory(project)
var expression = psiFactory.createExpression(annotation.expression)
val importFqNames = annotation.imports
.filter { FqNameUnsafe.isValid(it) }
.map { FqNameUnsafe(it) }
.filter { it.isSafe() }
.mapTo(LinkedHashSet<FqName>()) { it.toSafe() }
val symbolScope = getResolutionScope(symbolDescriptor)
val explicitlyImportedSymbols = importFqNames.flatMap { resolutionFacade.resolveImportReference(file, it) }
val scope = ChainedScope(symbolDescriptor, "ReplaceWith resolution scope", ExplicitImportsScope(explicitlyImportedSymbols), symbolScope)
val bindingContext = expression.analyzeInContext(scope)
val thisType = symbolDescriptor.getExtensionReceiverParameter()?.getType()
?: (symbolDescriptor.getContainingDeclaration() as? ClassifierDescriptor)?.getDefaultType()
val receiversToAdd = ArrayList<Pair<JetExpression, String>>()
val parameterUsageKey = Key<ValueParameterDescriptor>("parameterUsageKey")
expression.accept(object : JetVisitorVoid(){
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
val target = bindingContext[BindingContext.REFERENCE_TARGET, expression] ?: return
if (target.canBeReferencedViaImport()) {
if (target.isExtension || expression.getReceiverExpression() == null) {
importFqNames.addIfNotNull(target.importableFqName)
}
}
if (expression.getReceiverExpression() == null) {
if (target is ValueParameterDescriptor && target.getContainingDeclaration() == symbolDescriptor) {
expression.putCopyableUserData(parameterUsageKey, target)
}
val resolvedCall = expression.getResolvedCall(bindingContext)
if (resolvedCall != null && resolvedCall.getStatus().isSuccess()) {
val receiver = if (resolvedCall.getResultingDescriptor().isExtension)
resolvedCall.getExtensionReceiver()
else
resolvedCall.getDispatchReceiver()
if (receiver is ThisReceiver) {
if (receiver.getType() == thisType) {
receiversToAdd.add(expression to "this")
}
else {
val descriptor = receiver.getDeclarationDescriptor()
if (descriptor is ClassDescriptor && descriptor.isCompanionObject()) {
receiversToAdd.add(expression to IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor))
}
}
}
}
}
}
override fun visitJetElement(element: JetElement) {
element.acceptChildren(this)
}
})
for ((expr, receiverText) in receiversToAdd) {
val expressionToReplace = expr.getParent() as? JetCallExpression ?: expr
val newExpr = expressionToReplace.replaced(psiFactory.createExpressionByPattern("$receiverText.$0", expressionToReplace))
if (expressionToReplace == expression) {
expression = newExpr
}
}
val parameterUsages = symbolDescriptor.getValueParameters()
.map { parameter -> parameter to expression.collectElementsOfType<JetExpression> { it.getCopyableUserData(parameterUsageKey) == parameter } }
.toMap()
expression.accept(object : PsiRecursiveElementVisitor() {
override fun visitElement(element: PsiElement) {
element.putCopyableUserData(parameterUsageKey, null)
}
})
return ReplacementExpression(expression, importFqNames, parameterUsages)
}
private fun getResolutionScope(descriptor: DeclarationDescriptor): JetScope {
return when (descriptor) {
is PackageFragmentDescriptor -> {
val moduleDescriptor = descriptor.getContainingDeclaration()
getResolutionScope(moduleDescriptor.getPackage(descriptor.fqName)!!)
}
is PackageViewDescriptor ->
descriptor.getMemberScope()
is ClassDescriptorWithResolutionScopes ->
descriptor.getScopeForMemberDeclarationResolution()
is FunctionDescriptor ->
FunctionDescriptorUtil.getFunctionInnerScope(getResolutionScope(descriptor.getContainingDeclaration()),
descriptor, RedeclarationHandler.DO_NOTHING)
is PropertyDescriptor ->
JetScopeUtils.getPropertyDeclarationInnerScope(descriptor,
getResolutionScope(descriptor.getContainingDeclaration()!!),
RedeclarationHandler.DO_NOTHING)
else -> throw IllegalArgumentException("Cannot find resolution scope for $descriptor")
}
}
private class ExplicitImportsScope(val descriptors: Collection<DeclarationDescriptor>) : JetScope {
override fun getClassifier(name: Name) = descriptors.filter { it.getName() == name }.firstIsInstanceOrNull<ClassifierDescriptor>()
override fun getPackage(name: Name)= descriptors.filter { it.getName() == name }.firstIsInstanceOrNull<PackageViewDescriptor>()
override fun getProperties(name: Name) = descriptors.filter { it.getName() == name }.filterIsInstance<VariableDescriptor>()
override fun getLocalVariable(name: Name): VariableDescriptor? = null
override fun getFunctions(name: Name) = descriptors.filter { it.getName() == name }.filterIsInstance<FunctionDescriptor>()
override fun getContainingDeclaration() = throw UnsupportedOperationException()
override fun getDeclarationsByLabel(labelName: Name) = emptyList<DeclarationDescriptor>()
override fun getDescriptors(kindFilter: DescriptorKindFilter, nameFilter: (Name) -> Boolean) = descriptors
override fun getImplicitReceiversHierarchy() = emptyList<ReceiverParameterDescriptor>()
override fun getOwnDeclaredDescriptors() = emptyList<DeclarationDescriptor>()
override fun printScopeStructure(p: Printer) {
p.println(javaClass.getName())
}
}
}