Initial support for class usages replacement with ReplaceWith

This commit is contained in:
Valentin Kipyatkov
2015-09-01 20:32:20 +03:00
parent e7e2a9b1e2
commit de13e23a0a
23 changed files with 394 additions and 98 deletions
+4 -4
View File
@@ -16,9 +16,9 @@
package kotlin package kotlin
import kotlin.annotation.* import kotlin.annotation.AnnotationRetention.BINARY
import kotlin.annotation.AnnotationRetention.SOURCE
import kotlin.annotation.AnnotationTarget.* import kotlin.annotation.AnnotationTarget.*
import kotlin.annotation.AnnotationRetention.*
/** /**
* Marks the annotated class as a data class. The compiler automatically generates * Marks the annotated class as a data class. The compiler automatically generates
@@ -40,11 +40,11 @@ target(CLASSIFIER, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_S
public annotation(mustBeDocumented = true) class deprecated(val value: String, val replaceWith: ReplaceWith = ReplaceWith("")) public annotation(mustBeDocumented = true) class deprecated(val value: String, val replaceWith: ReplaceWith = ReplaceWith(""))
/** /**
* Specifies a code fragment that can be used to replace a deprecated function or property. Tools such * Specifies a code fragment that can be used to replace a deprecated function, property or class. Tools such
* as IDEs can automatically apply the replacements specified through this annotation. * as IDEs can automatically apply the replacements specified through this annotation.
* *
* @property expression the replacement expression. The replacement expression is interpreted in the context * @property expression the replacement expression. The replacement expression is interpreted in the context
* of the function or property being called, and can reference members of enclosing classes etc. * of the symbol being used, and can reference members of enclosing classes etc.
* For function calls, the replacement expression may contain argument names of the deprecated function, * For function calls, the replacement expression may contain argument names of the deprecated function,
* which will be substituted with actual parameters used in the call being updated. The imports used in the file * which will be substituted with actual parameters used in the call being updated. The imports used in the file
* containing the deprecated function or property are NOT accessible; if the replacement expression refers * containing the deprecated function or property are NOT accessible; if the replacement expression refers
@@ -50,7 +50,22 @@ import org.jetbrains.kotlin.types.JetType
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.* import java.util.*
fun performCallReplacement( class CallableUsageReplacementStrategy(
private val replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression
) : UsageReplacementStrategy {
override fun createReplacer(usage: JetSimpleNameExpression): (() -> JetElement)? {
val bindingContext = usage.analyze(BodyResolveMode.PARTIAL)
val resolvedCall = usage.getResolvedCall(bindingContext) ?: return null
if (!resolvedCall.status.isSuccess) return null
return {
// copy replacement expression because it is modified by performCallReplacement
performCallReplacement(usage, bindingContext, resolvedCall, replacement.copy())
}
}
}
private fun performCallReplacement(
element: JetSimpleNameExpression, element: JetSimpleNameExpression,
bindingContext: BindingContext, bindingContext: BindingContext,
resolvedCall: ResolvedCall<out CallableDescriptor>, resolvedCall: ResolvedCall<out CallableDescriptor>,
@@ -0,0 +1,45 @@
/*
* 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.idea.core.replaced
import org.jetbrains.kotlin.idea.util.ShortenReferences
import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.psi.JetNameReferenceExpression
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.psi.JetUserType
class ClassUsageReplacementStrategy(
private val replacement: JetUserType
) : UsageReplacementStrategy {
override fun createReplacer(usage: JetSimpleNameExpression): (() -> JetElement)? {
if (usage !is JetNameReferenceExpression) return null
val parent = usage.parent
when (parent) {
is JetUserType -> {
return {
val replaced = parent.replaced(replacement)
ShortenReferences.DEFAULT.process(replaced)
} //TODO: type arguments and type arguments of outer class are lost
}
else -> return null //TODO
}
}
}
@@ -21,7 +21,6 @@ import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.idea.core.targetDescriptors
@@ -29,9 +28,7 @@ import org.jetbrains.kotlin.idea.quickfix.CleanupFix
import org.jetbrains.kotlin.idea.quickfix.JetSingleIntentionActionFactory import org.jetbrains.kotlin.idea.quickfix.JetSingleIntentionActionFactory
import org.jetbrains.kotlin.idea.quickfix.moveCaret import org.jetbrains.kotlin.idea.quickfix.moveCaret
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
public class DeprecatedSymbolUsageFix( public class DeprecatedSymbolUsageFix(
element: JetSimpleNameExpression/*TODO?*/, element: JetSimpleNameExpression/*TODO?*/,
@@ -40,17 +37,10 @@ public class DeprecatedSymbolUsageFix(
override fun getFamilyName() = "Replace deprecated symbol usage" override fun getFamilyName() = "Replace deprecated symbol usage"
override fun getText() = "Replace with '${replaceWith.expression}'" //TODO: substitute? override fun getText() = "Replace with '${replaceWith.pattern}'" //TODO: substitute?
override fun invoke(
resolvedCall: ResolvedCall<out CallableDescriptor>,
bindingContext: BindingContext,
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression,
project: Project,
editor: Editor?
) {
val result = performCallReplacement(element, bindingContext, resolvedCall, replacement)
override fun invoke(replacementStrategy: UsageReplacementStrategy, project: Project, editor: Editor?) {
val result = replacementStrategy.createReplacer(element)!!.invoke()
val offset = (result.getCalleeExpressionIfAny() ?: result).textOffset val offset = (result.getCalleeExpressionIfAny() ?: result).textOffset
editor?.moveCaret(offset) editor?.moveCaret(offset)
} }
@@ -22,61 +22,36 @@ import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.OptionalParametersHelper import org.jetbrains.kotlin.idea.core.OptionalParametersHelper
import org.jetbrains.kotlin.idea.quickfix.JetIntentionAction import org.jetbrains.kotlin.idea.quickfix.JetIntentionAction
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.JetSimpleNameExpression import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.annotations.argumentValue import org.jetbrains.kotlin.resolve.annotations.argumentValue
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.constants.StringValue
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue
//TODO: replacement of class usages
//TODO: different replacements for property accessors //TODO: different replacements for property accessors
public abstract class DeprecatedSymbolUsageFixBase( public abstract class DeprecatedSymbolUsageFixBase(
element: JetSimpleNameExpression/*TODO?*/, element: JetSimpleNameExpression,
val replaceWith: ReplaceWith val replaceWith: ReplaceWith
) : JetIntentionAction<JetSimpleNameExpression>(element) { ) : JetIntentionAction<JetSimpleNameExpression>(element) {
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean { override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
if (!super.isAvailable(project, editor, file)) return false if (!super.isAvailable(project, editor, file)) return false
val strategy = UsageReplacementStrategy.build(element, replaceWith)
val resolvedCall = element.getResolvedCall(element.analyze()) ?: return false return strategy != null && strategy.createReplacer(element) != null
if (!resolvedCall.status.isSuccess) return false
val descriptor = resolvedCall.resultingDescriptor
if (replaceWithPattern(descriptor, project) != replaceWith) return false
try {
JetPsiFactory(project).createExpression(replaceWith.expression)
return true
}
catch(e: Exception) {
return false
}
} }
final override fun invoke(project: Project, editor: Editor?, file: JetFile) { final override fun invoke(project: Project, editor: Editor?, file: JetFile) {
val bindingContext = element.analyze() val strategy = UsageReplacementStrategy.build(element, replaceWith)!!
val resolvedCall = element.getResolvedCall(bindingContext)!! invoke(strategy, project, editor)
val descriptor = resolvedCall.resultingDescriptor
val replacement = ReplaceWithAnnotationAnalyzer.analyze(replaceWith, descriptor, element.getResolutionFacade())
invoke(resolvedCall, bindingContext, replacement, project, editor)
} }
protected abstract fun invoke( protected abstract fun invoke(
resolvedCall: ResolvedCall<out CallableDescriptor>, replacementStrategy: UsageReplacementStrategy,
bindingContext: BindingContext,
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression,
project: Project, project: Project,
editor: Editor?) editor: Editor?)
@@ -27,10 +27,8 @@ import com.intellij.psi.PsiFile
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.util.ui.UIUtil import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.targetDescriptors import org.jetbrains.kotlin.idea.core.targetDescriptors
import org.jetbrains.kotlin.idea.quickfix.JetSingleIntentionActionFactory import org.jetbrains.kotlin.idea.quickfix.JetSingleIntentionActionFactory
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
@@ -46,10 +44,6 @@ import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.NameShortness import org.jetbrains.kotlin.renderer.NameShortness
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
public class DeprecatedSymbolUsageInWholeProjectFix( public class DeprecatedSymbolUsageInWholeProjectFix(
element: JetSimpleNameExpression, element: JetSimpleNameExpression,
@@ -68,16 +62,10 @@ public class DeprecatedSymbolUsageInWholeProjectFix(
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean { override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
if (!super.isAvailable(project, editor, file)) return false if (!super.isAvailable(project, editor, file)) return false
val targetPsiElement = element.mainReference.resolve() val targetPsiElement = element.mainReference.resolve()
return targetPsiElement is JetNamedFunction || targetPsiElement is JetProperty return targetPsiElement is JetNamedFunction || targetPsiElement is JetProperty //TODO
} }
override fun invoke( override fun invoke(replacementStrategy: UsageReplacementStrategy, project: Project, editor: Editor?) {
resolvedCall: ResolvedCall<out CallableDescriptor>,
bindingContext: BindingContext,
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression,
project: Project,
editor: Editor?
) {
val psiElement = element.mainReference.resolve()!! val psiElement = element.mainReference.resolve()!!
ProgressManager.getInstance().run( ProgressManager.getInstance().run(
@@ -89,12 +77,12 @@ public class DeprecatedSymbolUsageInWholeProjectFix(
.filterIsInstance<JetSimpleNameReference>() .filterIsInstance<JetSimpleNameReference>()
.map { ref -> ref.expression } .map { ref -> ref.expression }
} }
replaceUsages(project, usages, replacement) replaceUsages(project, usages, replacementStrategy)
} }
}) })
} }
private fun replaceUsages(project: Project, usages: Collection<JetSimpleNameExpression>, replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression) { private fun replaceUsages(project: Project, usages: Collection<JetSimpleNameExpression>, replacementStrategy: UsageReplacementStrategy) {
UIUtil.invokeLaterIfNeeded { UIUtil.invokeLaterIfNeeded {
project.executeWriteCommand(text) { project.executeWriteCommand(text) {
// we should delete imports later to not affect other usages // we should delete imports later to not affect other usages
@@ -104,6 +92,7 @@ public class DeprecatedSymbolUsageInWholeProjectFix(
try { try {
if (!usage.isValid) continue // TODO: nested calls 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<JetImportDirective>() val importDirective = usage.getStrictParentOfType<JetImportDirective>()
if (importDirective != null) { if (importDirective != null) {
if (!importDirective.isAllUnder && importDirective.targetDescriptors().size() == 1) { if (!importDirective.isAllUnder && importDirective.targetDescriptors().size() == 1) {
@@ -112,11 +101,7 @@ public class DeprecatedSymbolUsageInWholeProjectFix(
continue continue
} }
val bindingContext = usage.analyze(BodyResolveMode.PARTIAL) replacementStrategy.createReplacer(usage)?.invoke()
val resolvedCall = usage.getResolvedCall(bindingContext) ?: continue
if (!resolvedCall.status.isSuccess) continue
// copy replacement expression because it is modified by performReplacement
performCallReplacement(usage, bindingContext, resolvedCall, replacement.copy())
} }
catch (e: Throwable) { catch (e: Throwable) {
LOG.error(e) LOG.error(e)
@@ -26,6 +26,8 @@ import org.jetbrains.kotlin.idea.core.copied
import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArgumentsIntention import org.jetbrains.kotlin.idea.intentions.InsertExplicitTypeArgumentsIntention
import org.jetbrains.kotlin.idea.references.JetSimpleNameReference
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.name.FqNameUnsafe
@@ -36,6 +38,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getReceiverExpression
import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.lazy.FileScopeProvider import org.jetbrains.kotlin.resolve.lazy.FileScopeProvider
@@ -50,7 +53,7 @@ import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.* import java.util.*
data class ReplaceWith(val expression: String, vararg val imports: String) data class ReplaceWith(val pattern: String, vararg val imports: String)
object ReplaceWithAnnotationAnalyzer { object ReplaceWithAnnotationAnalyzer {
public val PARAMETER_USAGE_KEY: Key<Name> = Key("PARAMETER_USAGE") public val PARAMETER_USAGE_KEY: Key<Name> = Key("PARAMETER_USAGE")
@@ -63,11 +66,11 @@ object ReplaceWithAnnotationAnalyzer {
fun copy() = ReplacementExpression(expression.copied(), fqNamesToImport) fun copy() = ReplacementExpression(expression.copied(), fqNamesToImport)
} }
public fun analyze( public fun analyzeCallableReplacement(
annotation: ReplaceWith, annotation: ReplaceWith,
symbolDescriptor: CallableDescriptor, symbolDescriptor: CallableDescriptor,
resolutionFacade: ResolutionFacade resolutionFacade: ResolutionFacade
): ReplacementExpression { ): ReplacementExpression? {
val originalDescriptor = (if (symbolDescriptor is CallableMemberDescriptor) val originalDescriptor = (if (symbolDescriptor is CallableMemberDescriptor)
DescriptorUtils.unwrapFakeOverride(symbolDescriptor) DescriptorUtils.unwrapFakeOverride(symbolDescriptor)
else else
@@ -79,23 +82,22 @@ object ReplaceWithAnnotationAnalyzer {
annotation: ReplaceWith, annotation: ReplaceWith,
symbolDescriptor: CallableDescriptor, symbolDescriptor: CallableDescriptor,
resolutionFacade: ResolutionFacade resolutionFacade: ResolutionFacade
): ReplacementExpression { ): ReplacementExpression? {
val psiFactory = JetPsiFactory(resolutionFacade.project) val psiFactory = JetPsiFactory(resolutionFacade.project)
var expression = psiFactory.createExpression(annotation.expression) var expression = try {
psiFactory.createExpression(annotation.pattern)
val importFqNames = annotation.imports }
.filter { FqNameUnsafe.isValid(it) } catch(e: Exception) {
.map { FqNameUnsafe(it) } return null
.filter { it.isSafe } }
.mapTo(LinkedHashSet<FqName>()) { it.toSafe() }
val explicitlyImportedSymbols = importFqNames.flatMap { resolutionFacade.resolveImportReference(symbolDescriptor.module, it) }
val module = symbolDescriptor.module
val explicitImportsScope = buildExplicitImportsScope(annotation, resolutionFacade, module)
val additionalScopes = resolutionFacade.getFrontendService(FileScopeProvider.AdditionalScopes::class.java) val additionalScopes = resolutionFacade.getFrontendService(FileScopeProvider.AdditionalScopes::class.java)
val scope = getResolutionScope(symbolDescriptor, symbolDescriptor, val scope = getResolutionScope(symbolDescriptor, symbolDescriptor,
listOf(ExplicitImportsScope(explicitlyImportedSymbols)) + additionalScopes.scopes) listOf(explicitImportsScope) + additionalScopes.scopes)
var bindingContext = analyzeInContext(expression, symbolDescriptor, scope, resolutionFacade) var bindingContext = analyzeInContext(expression, module, scope, resolutionFacade)
val typeArgsToAdd = ArrayList<Pair<JetCallExpression, JetTypeArgumentList>>() val typeArgsToAdd = ArrayList<Pair<JetCallExpression, JetTypeArgumentList>>()
expression.forEachDescendantOfType<JetCallExpression> { expression.forEachDescendantOfType<JetCallExpression> {
@@ -110,10 +112,11 @@ object ReplaceWithAnnotationAnalyzer {
} }
// reanalyze expression - new usages of type parameters may be added // reanalyze expression - new usages of type parameters may be added
bindingContext = analyzeInContext(expression, symbolDescriptor, scope, resolutionFacade) bindingContext = analyzeInContext(expression, module, scope, resolutionFacade)
} }
val receiversToAdd = ArrayList<Pair<JetExpression, JetExpression>>() val receiversToAdd = ArrayList<Pair<JetExpression, JetExpression>>()
val importFqNames = importFqNames(annotation).toMutableSet()
expression.forEachDescendantOfType<JetSimpleNameExpression> { expression -> expression.forEachDescendantOfType<JetSimpleNameExpression> { expression ->
val target = bindingContext[BindingContext.REFERENCE_TARGET, expression] ?: return@forEachDescendantOfType val target = bindingContext[BindingContext.REFERENCE_TARGET, expression] ?: return@forEachDescendantOfType
@@ -158,14 +161,70 @@ object ReplaceWithAnnotationAnalyzer {
return ReplacementExpression(expression, importFqNames) return ReplacementExpression(expression, importFqNames)
} }
public fun analyzeClassReplacement(
annotation: ReplaceWith,
symbolDescriptor: ClassDescriptor,
resolutionFacade: ResolutionFacade
): JetUserType? {
val psiFactory = JetPsiFactory(resolutionFacade.project)
val typeReference = try {
psiFactory.createType(annotation.pattern)
}
catch(e: Exception) {
return null
}
if (typeReference.typeElement !is JetUserType) return null
val module = symbolDescriptor.module
val explicitImportsScope = buildExplicitImportsScope(annotation, resolutionFacade, module)
val scope = getResolutionScope(symbolDescriptor, symbolDescriptor, listOf(explicitImportsScope))
val dummyExpression = psiFactory.createExpressionByPattern("x as $0", typeReference) as JetBinaryExpressionWithTypeRHS
val bindingContext = analyzeInContext(dummyExpression, module, scope, resolutionFacade)
val typesToQualify = ArrayList<Pair<JetNameReferenceExpression, FqName>>()
dummyExpression.right!!.forEachDescendantOfType<JetNameReferenceExpression> { expression ->
val parentType = expression.parent as? JetUserType ?: return@forEachDescendantOfType
if (parentType.qualifier != null) return@forEachDescendantOfType
val targetClass = bindingContext[BindingContext.REFERENCE_TARGET, expression] as? ClassDescriptor ?: return@forEachDescendantOfType
val fqName = targetClass.fqNameUnsafe
if (fqName.isSafe) {
typesToQualify.add(expression to fqName.toSafe())
}
}
for ((nameExpression, fqName) in typesToQualify) {
nameExpression.mainReference.bindToFqName(fqName, JetSimpleNameReference.ShorteningMode.NO_SHORTENING)
}
return dummyExpression.right!!.typeElement as JetUserType
}
private fun buildExplicitImportsScope(annotation: ReplaceWith, resolutionFacade: ResolutionFacade, module: ModuleDescriptor): ExplicitImportsScope {
val importedSymbols = importFqNames(annotation)
.flatMap { resolutionFacade.resolveImportReference(module, it) }
return ExplicitImportsScope(importedSymbols)
}
private fun importFqNames(annotation: ReplaceWith): List<FqName> {
return annotation.imports
.filter { FqNameUnsafe.isValid(it) }
.map { FqNameUnsafe(it) }
.filter { it.isSafe }
.map { it.toSafe() }
}
private fun analyzeInContext( private fun analyzeInContext(
expression: JetExpression, expression: JetExpression,
symbolDescriptor: CallableDescriptor, module: ModuleDescriptor,
scope: LexicalScope, scope: LexicalScope,
resolutionFacade: ResolutionFacade resolutionFacade: ResolutionFacade
): BindingContext { ): BindingContext {
val traceContext = BindingTraceContext() val traceContext = BindingTraceContext()
resolutionFacade.getFrontendService(symbolDescriptor.module, ExpressionTypingServices::class.java) resolutionFacade.getFrontendService(module, ExpressionTypingServices::class.java)
.getTypeInfo(scope, expression, TypeUtils.NO_EXPECTED_TYPE, DataFlowInfo.EMPTY, traceContext, false) .getTypeInfo(scope, expression, TypeUtils.NO_EXPECTED_TYPE, DataFlowInfo.EMPTY, traceContext, false)
return traceContext.bindingContext return traceContext.bindingContext
} }
@@ -180,22 +239,22 @@ object ReplaceWithAnnotationAnalyzer {
is PackageViewDescriptor -> is PackageViewDescriptor ->
ChainedScope(ownerDescriptor, "ReplaceWith resolution scope", descriptor.memberScope, *additionalScopes.toTypedArray()).asLexicalScope() ChainedScope(ownerDescriptor, "ReplaceWith resolution scope", descriptor.memberScope, *additionalScopes.toTypedArray()).asLexicalScope()
is ClassDescriptorWithResolutionScopes ->
descriptor.scopeForMemberDeclarationResolution
is ClassDescriptor -> { is ClassDescriptor -> {
val outerScope = getResolutionScope(descriptor.containingDeclaration, ownerDescriptor, additionalScopes) val outerScope = getResolutionScope(descriptor.containingDeclaration, ownerDescriptor, additionalScopes)
ClassResolutionScopesSupport(descriptor, LockBasedStorageManager.NO_LOCKS, { outerScope }).scopeForMemberDeclarationResolution() ClassResolutionScopesSupport(descriptor, LockBasedStorageManager.NO_LOCKS, { outerScope }).scopeForMemberDeclarationResolution()
} }
is FunctionDescriptor -> is FunctionDescriptor -> {
FunctionDescriptorUtil.getFunctionInnerScope(getResolutionScope(descriptor.containingDeclaration, ownerDescriptor, additionalScopes), val outerScope = getResolutionScope(descriptor.containingDeclaration, ownerDescriptor, additionalScopes)
descriptor, RedeclarationHandler.DO_NOTHING) FunctionDescriptorUtil.getFunctionInnerScope(outerScope, descriptor, RedeclarationHandler.DO_NOTHING)
}
is PropertyDescriptor -> is PropertyDescriptor -> {
JetScopeUtils.getPropertyDeclarationInnerScope(descriptor, val outerScope = getResolutionScope(descriptor.containingDeclaration, ownerDescriptor, additionalScopes)
getResolutionScope(descriptor.containingDeclaration, ownerDescriptor, additionalScopes), JetScopeUtils.getPropertyDeclarationInnerScope(descriptor, outerScope, RedeclarationHandler.DO_NOTHING)
RedeclarationHandler.DO_NOTHING) }
//TODO: it's not correct (it does not use additionalScopes!), drop this branch
is LocalVariableDescriptor -> { is LocalVariableDescriptor -> {
val declaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as JetDeclaration val declaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as JetDeclaration
declaration.analyze()[BindingContext.RESOLUTION_SCOPE, declaration]!!.asLexicalScope() declaration.analyze()[BindingContext.RESOLUTION_SCOPE, declaration]!!.asLexicalScope()
@@ -0,0 +1,57 @@
/*
* 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.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.JetElement
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
interface UsageReplacementStrategy {
fun createReplacer(usage: JetSimpleNameExpression): (() -> JetElement)?
companion object {
fun build(element: JetSimpleNameExpression, replaceWith: ReplaceWith): UsageReplacementStrategy? {
val resolutionFacade = element.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(element, BodyResolveMode.PARTIAL)
val target = element.mainReference.resolveToDescriptors(bindingContext).singleOrNull() ?: return null
// check that ReplaceWith hasn't changed
if (DeprecatedSymbolUsageFixBase.replaceWithPattern(target, resolutionFacade.project) != replaceWith) return null
when (target) {
is CallableDescriptor -> {
val resolvedCall = element.getResolvedCall(bindingContext) ?: return null
if (!resolvedCall.status.isSuccess) return null
val replacement = ReplaceWithAnnotationAnalyzer.analyzeCallableReplacement(replaceWith, target, resolutionFacade) ?: return null
return CallableUsageReplacementStrategy(replacement)
}
is ClassDescriptor -> {
val replacement = ReplaceWithAnnotationAnalyzer.analyzeClassReplacement(replaceWith, target, resolutionFacade) ?: return null
return ClassUsageReplacementStrategy(replacement)
}
else -> return null
}
}
}
}
@@ -0,0 +1,8 @@
// "Replace with 'NewClass'" "true"
import dependency.NewClass
import dependency.OldClass
fun foo(): NewClass? {
return null
}
@@ -0,0 +1,6 @@
package dependency
@deprecated("", ReplaceWith("NewClass"))
class OldClass
class NewClass
@@ -0,0 +1,7 @@
// "Replace with 'NewClass'" "true"
import dependency.OldClass
fun foo(): <caret>OldClass? {
return null
}
@@ -0,0 +1,8 @@
// "Replace with 'File'" "true"
@deprecated("", ReplaceWith("File", "java.io.File"))
class OldClass
fun foo(): OldClass<caret>? {
return null
}
@@ -0,0 +1,10 @@
import java.io.File
// "Replace with 'File'" "true"
@deprecated("", ReplaceWith("File", "java.io.File"))
class OldClass
fun foo(): File? {
return null
}
@@ -0,0 +1,12 @@
// "Replace with 'NewClass'" "true"
class Outer {
@deprecated("", ReplaceWith("NewClass"))
class OldClass
class NewClass
}
fun foo(): Outer.OldClass<caret>? {
return null
}
@@ -0,0 +1,12 @@
// "Replace with 'NewClass'" "true"
class Outer {
@deprecated("", ReplaceWith("NewClass"))
class OldClass
class NewClass
}
fun foo(): Outer.NewClass? {
return null
}
@@ -0,0 +1,12 @@
// "Replace with 'NewClass'" "true"
package ppp
@deprecated("", ReplaceWith("NewClass"))
class OldClass
class NewClass
fun foo(): ppp.OldClass<caret>? {
return null
}
@@ -0,0 +1,12 @@
// "Replace with 'NewClass'" "true"
package ppp
@deprecated("", ReplaceWith("NewClass"))
class OldClass
class NewClass
fun foo(): NewClass? {
return null
}
@@ -0,0 +1,8 @@
// "Replace with 'java.io.File'" "true"
@deprecated("", ReplaceWith("java.io.File"))
class OldClass
fun foo(): OldClass<caret>? {
return null
}
@@ -0,0 +1,10 @@
import java.io.File
// "Replace with 'java.io.File'" "true"
@deprecated("", ReplaceWith("java.io.File"))
class OldClass
fun foo(): File? {
return null
}
@@ -0,0 +1,10 @@
// "Replace with 'NewClass'" "true"
@deprecated("", ReplaceWith("NewClass"))
class OldClass
class NewClass
fun foo(): OldClass<caret>? {
return null
}
@@ -0,0 +1,10 @@
// "Replace with 'NewClass'" "true"
@deprecated("", ReplaceWith("NewClass"))
class OldClass
class NewClass
fun foo(): NewClass? {
return null
}
@@ -906,6 +906,21 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
doTestWithExtraFile(fileName); doTestWithExtraFile(fileName);
} }
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ClassUsages extends AbstractQuickFixMultiFileTest {
@TestMetadata("addImportFromSamePackage.before.Main.kt")
public void testAddImportFromSamePackage() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/addImportFromSamePackage.before.Main.kt");
doTestWithExtraFile(fileName);
}
public void testAllFilesPresentInClassUsages() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/classUsages"), Pattern.compile("^(\\w+)\\.before\\.Main\\.kt$"), true);
}
}
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/imports") @TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/imports")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -3312,11 +3312,41 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/classUsages"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/classUsages"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
} }
@TestMetadata("imports.kt")
public void testImports() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/imports.kt");
doTest(fileName);
}
@TestMetadata("nestedClassToNestedClass.kt")
public void testNestedClassToNestedClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/nestedClassToNestedClass.kt");
doTest(fileName);
}
@TestMetadata("noAnnotationConstructorUsage.kt") @TestMetadata("noAnnotationConstructorUsage.kt")
public void testNoAnnotationConstructorUsage() throws Exception { public void testNoAnnotationConstructorUsage() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/noAnnotationConstructorUsage.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/noAnnotationConstructorUsage.kt");
doTest(fileName); doTest(fileName);
} }
@TestMetadata("qualifiedClassName.kt")
public void testQualifiedClassName() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassName.kt");
doTest(fileName);
}
@TestMetadata("qualifiedClassNameInPattern.kt")
public void testQualifiedClassNameInPattern() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassNameInPattern.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/simple.kt");
doTest(fileName);
}
} }
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/functionLiteralArguments") @TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/functionLiteralArguments")