Initial support for class usages replacement with ReplaceWith
This commit is contained in:
@@ -16,9 +16,9 @@
|
||||
|
||||
package kotlin
|
||||
|
||||
import kotlin.annotation.*
|
||||
import kotlin.annotation.AnnotationRetention.BINARY
|
||||
import kotlin.annotation.AnnotationRetention.SOURCE
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
import kotlin.annotation.AnnotationRetention.*
|
||||
|
||||
/**
|
||||
* 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(""))
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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,
|
||||
* 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
|
||||
|
||||
+16
-1
@@ -50,7 +50,22 @@ import org.jetbrains.kotlin.types.JetType
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
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,
|
||||
bindingContext: BindingContext,
|
||||
resolvedCall: ResolvedCall<out CallableDescriptor>,
|
||||
+45
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-13
@@ -21,7 +21,6 @@ import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.core.targetDescriptors
|
||||
@@ -29,9 +28,7 @@ import org.jetbrains.kotlin.idea.quickfix.CleanupFix
|
||||
import org.jetbrains.kotlin.idea.quickfix.JetSingleIntentionActionFactory
|
||||
import org.jetbrains.kotlin.idea.quickfix.moveCaret
|
||||
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.model.ResolvedCall
|
||||
|
||||
public class DeprecatedSymbolUsageFix(
|
||||
element: JetSimpleNameExpression/*TODO?*/,
|
||||
@@ -40,17 +37,10 @@ public class DeprecatedSymbolUsageFix(
|
||||
|
||||
override fun getFamilyName() = "Replace deprecated symbol usage"
|
||||
|
||||
override fun getText() = "Replace with '${replaceWith.expression}'" //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 getText() = "Replace with '${replaceWith.pattern}'" //TODO: substitute?
|
||||
|
||||
override fun invoke(replacementStrategy: UsageReplacementStrategy, project: Project, editor: Editor?) {
|
||||
val result = replacementStrategy.createReplacer(element)!!.invoke()
|
||||
val offset = (result.getCalleeExpressionIfAny() ?: result).textOffset
|
||||
editor?.moveCaret(offset)
|
||||
}
|
||||
|
||||
+6
-31
@@ -22,61 +22,36 @@ import com.intellij.psi.PsiFile
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
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.quickfix.JetIntentionAction
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import org.jetbrains.kotlin.psi.JetPsiFactory
|
||||
import org.jetbrains.kotlin.psi.JetSimpleNameExpression
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
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.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.hasDefaultValue
|
||||
|
||||
//TODO: replacement of class usages
|
||||
//TODO: different replacements for property accessors
|
||||
|
||||
public abstract class DeprecatedSymbolUsageFixBase(
|
||||
element: JetSimpleNameExpression/*TODO?*/,
|
||||
element: JetSimpleNameExpression,
|
||||
val replaceWith: ReplaceWith
|
||||
) : JetIntentionAction<JetSimpleNameExpression>(element) {
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
|
||||
if (!super.isAvailable(project, editor, file)) return false
|
||||
|
||||
val resolvedCall = element.getResolvedCall(element.analyze()) ?: return false
|
||||
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
|
||||
}
|
||||
val strategy = UsageReplacementStrategy.build(element, replaceWith)
|
||||
return strategy != null && strategy.createReplacer(element) != null
|
||||
}
|
||||
|
||||
final override fun invoke(project: Project, editor: Editor?, file: JetFile) {
|
||||
val bindingContext = element.analyze()
|
||||
val resolvedCall = element.getResolvedCall(bindingContext)!!
|
||||
val descriptor = resolvedCall.resultingDescriptor
|
||||
|
||||
val replacement = ReplaceWithAnnotationAnalyzer.analyze(replaceWith, descriptor, element.getResolutionFacade())
|
||||
|
||||
invoke(resolvedCall, bindingContext, replacement, project, editor)
|
||||
val strategy = UsageReplacementStrategy.build(element, replaceWith)!!
|
||||
invoke(strategy, project, editor)
|
||||
}
|
||||
|
||||
protected abstract fun invoke(
|
||||
resolvedCall: ResolvedCall<out CallableDescriptor>,
|
||||
bindingContext: BindingContext,
|
||||
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression,
|
||||
replacementStrategy: UsageReplacementStrategy,
|
||||
project: Project,
|
||||
editor: Editor?)
|
||||
|
||||
|
||||
+6
-21
@@ -27,10 +27,8 @@ 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.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
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.quickfix.JetSingleIntentionActionFactory
|
||||
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.NameShortness
|
||||
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(
|
||||
element: JetSimpleNameExpression,
|
||||
@@ -68,16 +62,10 @@ public class DeprecatedSymbolUsageInWholeProjectFix(
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
|
||||
if (!super.isAvailable(project, editor, file)) return false
|
||||
val targetPsiElement = element.mainReference.resolve()
|
||||
return targetPsiElement is JetNamedFunction || targetPsiElement is JetProperty
|
||||
return targetPsiElement is JetNamedFunction || targetPsiElement is JetProperty //TODO
|
||||
}
|
||||
|
||||
override fun invoke(
|
||||
resolvedCall: ResolvedCall<out CallableDescriptor>,
|
||||
bindingContext: BindingContext,
|
||||
replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression,
|
||||
project: Project,
|
||||
editor: Editor?
|
||||
) {
|
||||
override fun invoke(replacementStrategy: UsageReplacementStrategy, project: Project, editor: Editor?) {
|
||||
val psiElement = element.mainReference.resolve()!!
|
||||
|
||||
ProgressManager.getInstance().run(
|
||||
@@ -89,12 +77,12 @@ public class DeprecatedSymbolUsageInWholeProjectFix(
|
||||
.filterIsInstance<JetSimpleNameReference>()
|
||||
.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 {
|
||||
project.executeWriteCommand(text) {
|
||||
// we should delete imports later to not affect other usages
|
||||
@@ -104,6 +92,7 @@ public class DeprecatedSymbolUsageInWholeProjectFix(
|
||||
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<JetImportDirective>()
|
||||
if (importDirective != null) {
|
||||
if (!importDirective.isAllUnder && importDirective.targetDescriptors().size() == 1) {
|
||||
@@ -112,11 +101,7 @@ public class DeprecatedSymbolUsageInWholeProjectFix(
|
||||
continue
|
||||
}
|
||||
|
||||
val bindingContext = usage.analyze(BodyResolveMode.PARTIAL)
|
||||
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())
|
||||
replacementStrategy.createReplacer(usage)?.invoke()
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
LOG.error(e)
|
||||
|
||||
+87
-28
@@ -26,6 +26,8 @@ import org.jetbrains.kotlin.idea.core.copied
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||
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.name.FqName
|
||||
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.calls.callUtil.getResolvedCall
|
||||
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.module
|
||||
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 java.util.*
|
||||
|
||||
data class ReplaceWith(val expression: String, vararg val imports: String)
|
||||
data class ReplaceWith(val pattern: String, vararg val imports: String)
|
||||
|
||||
object ReplaceWithAnnotationAnalyzer {
|
||||
public val PARAMETER_USAGE_KEY: Key<Name> = Key("PARAMETER_USAGE")
|
||||
@@ -63,11 +66,11 @@ object ReplaceWithAnnotationAnalyzer {
|
||||
fun copy() = ReplacementExpression(expression.copied(), fqNamesToImport)
|
||||
}
|
||||
|
||||
public fun analyze(
|
||||
public fun analyzeCallableReplacement(
|
||||
annotation: ReplaceWith,
|
||||
symbolDescriptor: CallableDescriptor,
|
||||
resolutionFacade: ResolutionFacade
|
||||
): ReplacementExpression {
|
||||
): ReplacementExpression? {
|
||||
val originalDescriptor = (if (symbolDescriptor is CallableMemberDescriptor)
|
||||
DescriptorUtils.unwrapFakeOverride(symbolDescriptor)
|
||||
else
|
||||
@@ -79,23 +82,22 @@ object ReplaceWithAnnotationAnalyzer {
|
||||
annotation: ReplaceWith,
|
||||
symbolDescriptor: CallableDescriptor,
|
||||
resolutionFacade: ResolutionFacade
|
||||
): ReplacementExpression {
|
||||
): ReplacementExpression? {
|
||||
val psiFactory = JetPsiFactory(resolutionFacade.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 explicitlyImportedSymbols = importFqNames.flatMap { resolutionFacade.resolveImportReference(symbolDescriptor.module, it) }
|
||||
var expression = try {
|
||||
psiFactory.createExpression(annotation.pattern)
|
||||
}
|
||||
catch(e: Exception) {
|
||||
return null
|
||||
}
|
||||
|
||||
val module = symbolDescriptor.module
|
||||
val explicitImportsScope = buildExplicitImportsScope(annotation, resolutionFacade, module)
|
||||
val additionalScopes = resolutionFacade.getFrontendService(FileScopeProvider.AdditionalScopes::class.java)
|
||||
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>>()
|
||||
expression.forEachDescendantOfType<JetCallExpression> {
|
||||
@@ -110,10 +112,11 @@ object ReplaceWithAnnotationAnalyzer {
|
||||
}
|
||||
|
||||
// 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 importFqNames = importFqNames(annotation).toMutableSet()
|
||||
|
||||
expression.forEachDescendantOfType<JetSimpleNameExpression> { expression ->
|
||||
val target = bindingContext[BindingContext.REFERENCE_TARGET, expression] ?: return@forEachDescendantOfType
|
||||
@@ -158,14 +161,70 @@ object ReplaceWithAnnotationAnalyzer {
|
||||
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(
|
||||
expression: JetExpression,
|
||||
symbolDescriptor: CallableDescriptor,
|
||||
module: ModuleDescriptor,
|
||||
scope: LexicalScope,
|
||||
resolutionFacade: ResolutionFacade
|
||||
): BindingContext {
|
||||
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)
|
||||
return traceContext.bindingContext
|
||||
}
|
||||
@@ -180,22 +239,22 @@ object ReplaceWithAnnotationAnalyzer {
|
||||
is PackageViewDescriptor ->
|
||||
ChainedScope(ownerDescriptor, "ReplaceWith resolution scope", descriptor.memberScope, *additionalScopes.toTypedArray()).asLexicalScope()
|
||||
|
||||
is ClassDescriptorWithResolutionScopes ->
|
||||
descriptor.scopeForMemberDeclarationResolution
|
||||
|
||||
is ClassDescriptor -> {
|
||||
val outerScope = getResolutionScope(descriptor.containingDeclaration, ownerDescriptor, additionalScopes)
|
||||
ClassResolutionScopesSupport(descriptor, LockBasedStorageManager.NO_LOCKS, { outerScope }).scopeForMemberDeclarationResolution()
|
||||
}
|
||||
|
||||
is FunctionDescriptor ->
|
||||
FunctionDescriptorUtil.getFunctionInnerScope(getResolutionScope(descriptor.containingDeclaration, ownerDescriptor, additionalScopes),
|
||||
descriptor, RedeclarationHandler.DO_NOTHING)
|
||||
is FunctionDescriptor -> {
|
||||
val outerScope = getResolutionScope(descriptor.containingDeclaration, ownerDescriptor, additionalScopes)
|
||||
FunctionDescriptorUtil.getFunctionInnerScope(outerScope, descriptor, RedeclarationHandler.DO_NOTHING)
|
||||
}
|
||||
|
||||
is PropertyDescriptor ->
|
||||
JetScopeUtils.getPropertyDeclarationInnerScope(descriptor,
|
||||
getResolutionScope(descriptor.containingDeclaration, ownerDescriptor, additionalScopes),
|
||||
RedeclarationHandler.DO_NOTHING)
|
||||
is PropertyDescriptor -> {
|
||||
val outerScope = getResolutionScope(descriptor.containingDeclaration, ownerDescriptor, additionalScopes)
|
||||
JetScopeUtils.getPropertyDeclarationInnerScope(descriptor, outerScope, RedeclarationHandler.DO_NOTHING)
|
||||
}
|
||||
|
||||
//TODO: it's not correct (it does not use additionalScopes!), drop this branch
|
||||
is LocalVariableDescriptor -> {
|
||||
val declaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as JetDeclaration
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
|
||||
import dependency.NewClass
|
||||
import dependency.OldClass
|
||||
|
||||
fun foo(): NewClass? {
|
||||
return null
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package dependency
|
||||
|
||||
@deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass
|
||||
|
||||
class NewClass
|
||||
Vendored
+7
@@ -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
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
|
||||
class Outer {
|
||||
@deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass
|
||||
|
||||
class NewClass
|
||||
}
|
||||
|
||||
fun foo(): Outer.OldClass<caret>? {
|
||||
return null
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
|
||||
class Outer {
|
||||
@deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass
|
||||
|
||||
class NewClass
|
||||
}
|
||||
|
||||
fun foo(): Outer.NewClass? {
|
||||
return null
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
|
||||
package ppp
|
||||
|
||||
@deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass
|
||||
|
||||
class NewClass
|
||||
|
||||
fun foo(): ppp.OldClass<caret>? {
|
||||
return null
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Replace with 'NewClass'" "true"
|
||||
|
||||
package ppp
|
||||
|
||||
@deprecated("", ReplaceWith("NewClass"))
|
||||
class OldClass
|
||||
|
||||
class NewClass
|
||||
|
||||
fun foo(): NewClass? {
|
||||
return null
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// "Replace with 'java.io.File'" "true"
|
||||
|
||||
@deprecated("", ReplaceWith("java.io.File"))
|
||||
class OldClass
|
||||
|
||||
fun foo(): OldClass<caret>? {
|
||||
return null
|
||||
}
|
||||
Vendored
+10
@@ -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);
|
||||
}
|
||||
|
||||
@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")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@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);
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testNoAnnotationConstructorUsage() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/classUsages/noAnnotationConstructorUsage.kt");
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user