diff --git a/core/builtins/src/kotlin/Annotations.kt b/core/builtins/src/kotlin/Annotations.kt index 9ebd70e2700..72df6e172b7 100644 --- a/core/builtins/src/kotlin/Annotations.kt +++ b/core/builtins/src/kotlin/Annotations.kt @@ -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 diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/performCallReplacement.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/CallableUsageReplacementStrategy.kt similarity index 97% rename from idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/performCallReplacement.kt rename to idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/CallableUsageReplacementStrategy.kt index 12b404277a7..6217c70ea6a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/performCallReplacement.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/CallableUsageReplacementStrategy.kt @@ -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, diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ClassUsageReplacementStrategy.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ClassUsageReplacementStrategy.kt new file mode 100644 index 00000000000..c94f3e65c77 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ClassUsageReplacementStrategy.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFix.kt index a815440ae75..ec9e39e3a5a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFix.kt @@ -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, - 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) } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt index 097578625fc..6ce0139a6b8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageFixBase.kt @@ -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(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, - bindingContext: BindingContext, - replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression, + replacementStrategy: UsageReplacementStrategy, project: Project, editor: Editor?) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageInWholeProjectFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageInWholeProjectFix.kt index ef4ec3a3188..e12fc558d55 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageInWholeProjectFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/DeprecatedSymbolUsageInWholeProjectFix.kt @@ -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, - 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() .map { ref -> ref.expression } } - replaceUsages(project, usages, replacement) + replaceUsages(project, usages, replacementStrategy) } }) } - private fun replaceUsages(project: Project, usages: Collection, replacement: ReplaceWithAnnotationAnalyzer.ReplacementExpression) { + private fun replaceUsages(project: Project, usages: Collection, 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() 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) diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ReplaceWithAnnotationAnalyzer.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ReplaceWithAnnotationAnalyzer.kt index aa22d57ff83..a75b66419b3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ReplaceWithAnnotationAnalyzer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/ReplaceWithAnnotationAnalyzer.kt @@ -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 = 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()) { 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>() expression.forEachDescendantOfType { @@ -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>() + val importFqNames = importFqNames(annotation).toMutableSet() expression.forEachDescendantOfType { 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>() + + dummyExpression.right!!.forEachDescendantOfType { 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 { + 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() diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/UsageReplacementStrategy.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/UsageReplacementStrategy.kt new file mode 100644 index 00000000000..55852cb4899 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/replaceWith/UsageReplacementStrategy.kt @@ -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 + } + } + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/addImportFromSamePackage.after.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/addImportFromSamePackage.after.kt new file mode 100644 index 00000000000..69b6e746fa4 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/addImportFromSamePackage.after.kt @@ -0,0 +1,8 @@ +// "Replace with 'NewClass'" "true" + +import dependency.NewClass +import dependency.OldClass + +fun foo(): NewClass? { + return null +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/addImportFromSamePackage.before.Declaration.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/addImportFromSamePackage.before.Declaration.kt new file mode 100644 index 00000000000..6be2dd79dfb --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/addImportFromSamePackage.before.Declaration.kt @@ -0,0 +1,6 @@ +package dependency + +@deprecated("", ReplaceWith("NewClass")) +class OldClass + +class NewClass diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/addImportFromSamePackage.before.Main.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/addImportFromSamePackage.before.Main.kt new file mode 100644 index 00000000000..92ee1d35ead --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/addImportFromSamePackage.before.Main.kt @@ -0,0 +1,7 @@ +// "Replace with 'NewClass'" "true" + +import dependency.OldClass + +fun foo(): OldClass? { + return null +} \ No newline at end of file diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/imports.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/imports.kt new file mode 100644 index 00000000000..ff8d5be3ab5 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/imports.kt @@ -0,0 +1,8 @@ +// "Replace with 'File'" "true" + +@deprecated("", ReplaceWith("File", "java.io.File")) +class OldClass + +fun foo(): OldClass? { + return null +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/imports.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/imports.kt.after new file mode 100644 index 00000000000..89b363bc527 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/imports.kt.after @@ -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 +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/nestedClassToNestedClass.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/nestedClassToNestedClass.kt new file mode 100644 index 00000000000..bd13171ff5e --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/nestedClassToNestedClass.kt @@ -0,0 +1,12 @@ +// "Replace with 'NewClass'" "true" + +class Outer { + @deprecated("", ReplaceWith("NewClass")) + class OldClass + + class NewClass +} + +fun foo(): Outer.OldClass? { + return null +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/nestedClassToNestedClass.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/nestedClassToNestedClass.kt.after new file mode 100644 index 00000000000..003c4be6705 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/nestedClassToNestedClass.kt.after @@ -0,0 +1,12 @@ +// "Replace with 'NewClass'" "true" + +class Outer { + @deprecated("", ReplaceWith("NewClass")) + class OldClass + + class NewClass +} + +fun foo(): Outer.NewClass? { + return null +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassName.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassName.kt new file mode 100644 index 00000000000..67161ed1186 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassName.kt @@ -0,0 +1,12 @@ +// "Replace with 'NewClass'" "true" + +package ppp + +@deprecated("", ReplaceWith("NewClass")) +class OldClass + +class NewClass + +fun foo(): ppp.OldClass? { + return null +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassName.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassName.kt.after new file mode 100644 index 00000000000..b26f78a5fac --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassName.kt.after @@ -0,0 +1,12 @@ +// "Replace with 'NewClass'" "true" + +package ppp + +@deprecated("", ReplaceWith("NewClass")) +class OldClass + +class NewClass + +fun foo(): NewClass? { + return null +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassNameInPattern.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassNameInPattern.kt new file mode 100644 index 00000000000..0bc8da960c5 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassNameInPattern.kt @@ -0,0 +1,8 @@ +// "Replace with 'java.io.File'" "true" + +@deprecated("", ReplaceWith("java.io.File")) +class OldClass + +fun foo(): OldClass? { + return null +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassNameInPattern.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassNameInPattern.kt.after new file mode 100644 index 00000000000..e041f0aa26f --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/qualifiedClassNameInPattern.kt.after @@ -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 +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/simple.kt b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/simple.kt new file mode 100644 index 00000000000..615d8a5eef6 --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/simple.kt @@ -0,0 +1,10 @@ +// "Replace with 'NewClass'" "true" + +@deprecated("", ReplaceWith("NewClass")) +class OldClass + +class NewClass + +fun foo(): OldClass? { + return null +} diff --git a/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/simple.kt.after b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/simple.kt.after new file mode 100644 index 00000000000..178d8101efc --- /dev/null +++ b/idea/testData/quickfix/deprecatedSymbolUsage/classUsages/simple.kt.after @@ -0,0 +1,10 @@ +// "Replace with 'NewClass'" "true" + +@deprecated("", ReplaceWith("NewClass")) +class OldClass + +class NewClass + +fun foo(): NewClass? { + return null +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index 23fe2ee112e..c17eb8550a7 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -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) diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index e4b56a383d5..b0dd69af55a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -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")