From 77b0bb9849a175f7366fdc2b122ee4f614b346ca Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Tue, 14 Jun 2016 15:07:52 +0300 Subject: [PATCH] Rename: Implement Rename conflict analysis for properties. Qualify property references to resove rename conflicts when possible #KT-8732 Fixed #KT-12543 Fixed (cherry picked from commit e23029d) --- ChangeLog.md | 2 + .../jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt | 5 + .../kotlin/resolve/calls/resolvedCallUtil.kt | 4 +- .../kotlin/asJava/lightClassUtils.kt | 13 +- .../jetbrains/kotlin/idea/util/scopeUtils.kt | 8 + idea/src/META-INF/plugin.xml | 3 + .../idea/refactoring/jetRefactoringUtil.kt | 31 ++- .../BasicUnresolvableCollisionUsageInfo.kt | 27 ++ .../rename/KotlinResolveSnapshotProvider.kt | 81 ++++++ .../rename/KtResolvableCollisionUsageInfo.kt | 57 +++++ .../rename/RenameKotlinPropertyProcessor.kt | 113 +++++++++ .../refactoring/rename/renameConflictUtils.kt | 239 ++++++++++++++++++ .../localVarShadowingMemberProperty.kt | 10 + .../localVarShadowingMemberProperty.kt.after | 10 + .../before/test/test.kt | 6 + .../memberPropertyRedeclaration.test | 7 + .../after/test/test.kt | 10 + .../before/test/test.kt | 10 + .../memberPropertyShadowedByLocalVar.test | 6 + .../after/test/test.kt | 17 ++ .../before/test/test.kt | 17 ++ ...mberPropertyWithInnerInstanceProperty.test | 6 + .../after/test/test.kt | 17 ++ .../before/test/test.kt | 17 ++ ...mberPropertyWithOuterInstanceProperty.test | 6 + .../before/test/test.kt | 9 + .../propertyAccidentalOverrideSubclass.test | 7 + .../before/test/test.kt | 9 + .../propertyAccidentalOverrideSuperclass.test | 7 + .../before/test/test.kt | 7 + ...tyParameterAccidentalOverrideSubclass.test | 7 + .../before/test/test.kt | 7 + ...ParameterAccidentalOverrideSuperclass.test | 7 + .../before/test/test.kt | 5 + ...propertyParameterRedeclaredByProperty.test | 7 + .../before/test/test.kt | 5 + ...propertyRedeclaredByPropertyParameter.test | 7 + .../before/test/test.kt | 4 + .../topLevelPropertyRedeclaration.test | 7 + .../idea/refactoring/InplaceRenameTest.kt | 4 + .../rename/RenameTestGenerated.java | 66 +++++ 41 files changed, 878 insertions(+), 9 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/rename/BasicUnresolvableCollisionUsageInfo.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinResolveSnapshotProvider.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KtResolvableCollisionUsageInfo.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt create mode 100644 idea/testData/refactoring/rename/inplace/localVarShadowingMemberProperty.kt create mode 100644 idea/testData/refactoring/rename/inplace/localVarShadowingMemberProperty.kt.after create mode 100644 idea/testData/refactoring/rename/memberPropertyRedeclaration/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/memberPropertyRedeclaration/memberPropertyRedeclaration.test create mode 100644 idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/after/test/test.kt create mode 100644 idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/memberPropertyShadowedByLocalVar.test create mode 100644 idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/after/test/test.kt create mode 100644 idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/memberPropertyWithInnerInstanceProperty.test create mode 100644 idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/after/test/test.kt create mode 100644 idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/memberPropertyWithOuterInstanceProperty.test create mode 100644 idea/testData/refactoring/rename/propertyAccidentalOverrideSubclass/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/propertyAccidentalOverrideSubclass/propertyAccidentalOverrideSubclass.test create mode 100644 idea/testData/refactoring/rename/propertyAccidentalOverrideSuperclass/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/propertyAccidentalOverrideSuperclass/propertyAccidentalOverrideSuperclass.test create mode 100644 idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSubclass/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSubclass/propertyParameterAccidentalOverrideSubclass.test create mode 100644 idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSuperclass/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSuperclass/propertyParameterAccidentalOverrideSuperclass.test create mode 100644 idea/testData/refactoring/rename/propertyParameterRedeclaredByProperty/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/propertyParameterRedeclaredByProperty/propertyParameterRedeclaredByProperty.test create mode 100644 idea/testData/refactoring/rename/propertyRedeclaredByPropertyParameter/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/propertyRedeclaredByPropertyParameter/propertyRedeclaredByPropertyParameter.test create mode 100644 idea/testData/refactoring/rename/topLevelPropertyRedeclaration/before/test/test.kt create mode 100644 idea/testData/refactoring/rename/topLevelPropertyRedeclaration/topLevelPropertyRedeclaration.test diff --git a/ChangeLog.md b/ChangeLog.md index 4ceb9bcf185..814bd9535ad 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -226,6 +226,7 @@ - [`KT-6363`](https://youtrack.jetbrains.com/issue/KT-6363) Do not rename ambiguous references in import directives - [`KT-8541`](https://youtrack.jetbrains.com/issue/KT-8541), [`KT-8786`](https://youtrack.jetbrains.com/issue/KT-8786) Do now show 'Rename overloads' options if target function has no overloads - [`KT-8544`](https://youtrack.jetbrains.com/issue/KT-8544) Show more detailed description in Rename dialog +- [`KT-8732`](https://youtrack.jetbrains.com/issue/KT-8732) Implement Rename conflict analysis and fixes for properties/parameters - [`KT-8860`](https://youtrack.jetbrains.com/issue/KT-8860) Allow renaming class by constructor delegation call referencing primary constructor - [`KT-8892`](https://youtrack.jetbrains.com/issue/KT-8892) Suggest renaming base declarations on overriding members in object literals - [`KT-9156`](https://youtrack.jetbrains.com/issue/KT-9156) Quote non-identifier names in Kotlin references @@ -234,6 +235,7 @@ - [`KT-9444`](https://youtrack.jetbrains.com/issue/KT-9444) Rename dialog: Allow typing any identifier without backquotes - [`KT-9446`](https://youtrack.jetbrains.com/issue/KT-9446) Warn about calls with default arguments if function to be renamed inherits default values from some base function which is excluded from rename - [`KT-10713`](https://youtrack.jetbrains.com/issue/KT-10713) Skip read-only declarations when renaming parameters +- [`KT-12543`](https://youtrack.jetbrains.com/issue/KT-12543) Qualify property references with 'this' to avoid renaming conflicts #### Java to Kotlin converter diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt index d95864ea06c..c50094f88c9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt @@ -459,3 +459,8 @@ fun KtElement.nonStaticOuterClasses(): Sequence { } fun KtElement.containingClass(): KtClass? = getStrictParentOfType() + +fun KtClassOrObject.findPropertyByName(name: String): KtNamedDeclaration? { + return declarations.firstOrNull { it is KtProperty && it.name == name } as KtNamedDeclaration? + ?: getPrimaryConstructorParameters().firstOrNull { it.hasValOrVar() && it.name == name } +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/resolvedCallUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/resolvedCallUtil.kt index a0756383a72..0abdb9bb8e7 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/resolvedCallUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/resolvedCallUtil.kt @@ -69,13 +69,13 @@ fun ResolvedCall<*>.getExplicitReceiverValue(): ReceiverValue? { } } -fun ResolvedCall<*>.getImplicitReceiverValue(): ReceiverValue? { +fun ResolvedCall<*>.getImplicitReceiverValue(): ImplicitReceiver? { return when (explicitReceiverKind) { ExplicitReceiverKind.NO_EXPLICIT_RECEIVER -> extensionReceiver ?: dispatchReceiver ExplicitReceiverKind.DISPATCH_RECEIVER -> extensionReceiver ExplicitReceiverKind.EXTENSION_RECEIVER -> dispatchReceiver else -> null - } + } as? ImplicitReceiver } private fun ResolvedCall<*>.hasSafeNullableReceiver(context: CallResolutionContext<*>): Boolean { diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt index 2c2715b3b3d..d99b8982d88 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt @@ -163,8 +163,17 @@ fun propertyNameByAccessor(name: String, accessor: KtLightMethod): String? { val methodName = Name.guessByFirstCharacter(name) val propertyName = toRename.name ?: "" return when { - name.startsWith("get") -> propertyNameByGetMethodName(methodName) - name.startsWith("set") -> propertyNameBySetMethodName(methodName, propertyName.startsWith("is")) + JvmAbi.isGetterName(name) -> propertyNameByGetMethodName(methodName) + JvmAbi.isSetterName(name) -> propertyNameBySetMethodName(methodName, propertyName.startsWith("is")) else -> null }?.asString() +} + +fun accessorNameByPropertyName(name: String, accessor: KtLightMethod): String? { + val methodName = accessor.name + return when { + JvmAbi.isGetterName(methodName) -> JvmAbi.getterName(name) + JvmAbi.isSetterName(methodName) -> JvmAbi.setterName(name) + else -> null + } } \ No newline at end of file diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/scopeUtils.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/scopeUtils.kt index dba1da892f1..26025368f70 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/util/scopeUtils.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/util/scopeUtils.kt @@ -22,6 +22,7 @@ import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.resolve.frontendService import org.jetbrains.kotlin.incremental.components.NoLookupLocation @@ -31,6 +32,7 @@ import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.FileScopeProvider import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.utils.collectFunctions @@ -78,6 +80,12 @@ fun PsiElement.getResolutionScope(bindingContext: BindingContext, resolutionFaca error("Not in KtFile") } +fun KtElement.getResolutionScope(): LexicalScope { + val resolutionFacade = getResolutionFacade() + val context = resolutionFacade.analyze(this, BodyResolveMode.PARTIAL) + return getResolutionScope(context, resolutionFacade) +} + fun ResolutionFacade.getFileResolutionScope(file: KtFile): LexicalScope { return frontendService().getFileResolutionScope(file) } \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index fe8f98bf5a9..8e0e91cd2dc 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -445,6 +445,9 @@ + diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt index 00a028809de..57e80fd1ccf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt @@ -59,10 +59,8 @@ import com.intellij.util.containers.MultiMap import org.jetbrains.kotlin.asJava.KtLightMethod import org.jetbrains.kotlin.asJava.LightClassUtil import org.jetbrains.kotlin.asJava.toLightClass -import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.ClassKind -import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.caches.resolve.analyze @@ -91,6 +89,8 @@ import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getCallWithAssert import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver +import org.jetbrains.kotlin.resolve.source.getPsi import java.io.File import java.lang.annotation.Retention import java.util.* @@ -765,4 +765,25 @@ fun addTypeArgumentsIfNeeded(expression: KtExpression, typeArgumentList: KtTypeA callElement.addAfter(typeArgumentList, callElement.calleeExpression) ShortenReferences.DEFAULT.process(callElement.typeArgumentList!!) -} \ No newline at end of file +} + +internal fun DeclarationDescriptor.getThisLabelName(): String { + if (!name.isSpecial) return name.asString() + if (this is AnonymousFunctionDescriptor) { + val function = source.getPsi() as? KtFunction + val argument = function?.parent as? KtValueArgument + val callElement = argument?.getStrictParentOfType() + val callee = callElement?.calleeExpression as? KtSimpleNameExpression + if (callee != null) return callee.text + } + return "" +} + +internal fun DeclarationDescriptor.explicateAsTextForReceiver(): String { + val labelName = getThisLabelName() + return if (labelName.isEmpty()) "this" else "this@$labelName" +} + +internal fun ImplicitReceiver.explicateAsText(): String { + return declarationDescriptor.explicateAsTextForReceiver() +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/BasicUnresolvableCollisionUsageInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/BasicUnresolvableCollisionUsageInfo.kt new file mode 100644 index 00000000000..75687c149a7 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/BasicUnresolvableCollisionUsageInfo.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.rename + +import com.intellij.psi.PsiElement +import com.intellij.refactoring.rename.UnresolvableCollisionUsageInfo + +class BasicUnresolvableCollisionUsageInfo( + element: PsiElement, + referencedElement: PsiElement, + private val _description: String) : UnresolvableCollisionUsageInfo(element, referencedElement) { + override fun getDescription() = _description +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinResolveSnapshotProvider.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinResolveSnapshotProvider.kt new file mode 100644 index 00000000000..b0b78a5cfbe --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KotlinResolveSnapshotProvider.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.rename + +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.SmartPsiElementPointer +import com.intellij.refactoring.rename.ResolveSnapshotProvider +import com.intellij.util.containers.HashMap +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.imports.importableFqName +import org.jetbrains.kotlin.idea.refactoring.explicateAsTextForReceiver +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.KtTreeVisitorVoid +import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import java.util.* + +class KotlinResolveSnapshotProvider : ResolveSnapshotProvider() { + override fun createSnapshot(scope: PsiElement) = object : ResolveSnapshot() { + private val project = scope.project + private val document = PsiDocumentManager.getInstance(project).getDocument(scope.containingFile)!! + private val refExpressionToDescriptor = HashMap, PropertyDescriptor>() + + init { + scope.accept( + object: KtTreeVisitorVoid() { + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + if (expression.getQualifiedExpressionForSelector() != null) return super.visitSimpleNameExpression(expression) + val context = expression.analyze(BodyResolveMode.PARTIAL) + val targetDescriptor = expression.mainReference.resolveToDescriptors(context).singleOrNull() ?: return + if (targetDescriptor !is PropertyDescriptor) return + refExpressionToDescriptor[expression.createSmartPointer()] = targetDescriptor + } + } + ) + } + + override fun apply(name: String) { + PsiDocumentManager.getInstance(project).commitDocument(document) + + val elementsToShorten = ArrayList() + for ((refExprPointer, targetDescriptor) in refExpressionToDescriptor) { + val refExpr = refExprPointer.element ?: continue + if (refExpr.text != name) continue + val containingDescriptor = targetDescriptor.containingDeclaration + val qualifiedRefText = if (containingDescriptor is ClassDescriptor) { + "${containingDescriptor.explicateAsTextForReceiver()}.${targetDescriptor.name.asString()}" + } + else { + targetDescriptor.importableFqName?.asString() ?: continue + } + val qualifiedRefExpr = KtPsiFactory(project).createExpression(qualifiedRefText) + elementsToShorten += refExpr.replaced(qualifiedRefExpr) + } + ShortenReferences { ShortenReferences.Options.ALL_ENABLED }.process(elementsToShorten) + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KtResolvableCollisionUsageInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KtResolvableCollisionUsageInfo.kt new file mode 100644 index 00000000000..b5bb21d7499 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/KtResolvableCollisionUsageInfo.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.rename + +import com.intellij.psi.PsiElement +import com.intellij.refactoring.rename.ResolvableCollisionUsageInfo +import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtSimpleNameExpression + +abstract class KtResolvableCollisionUsageInfo( + element: PsiElement, + referencedElement: PsiElement +) : ResolvableCollisionUsageInfo(element, referencedElement) { + // To prevent simple rename via PsiReference + override fun getReference() = null + + abstract fun apply() +} + +class UsageInfoWithReplacement( + element: PsiElement, + referencedElement: PsiElement, + private val replacement: KtElement +) : KtResolvableCollisionUsageInfo(element, referencedElement) { + override fun apply() { + element?.replaced(replacement)?.addToShorteningWaitSet(ShortenReferences.Options.ALL_ENABLED) + } +} + +class UsageInfoWithFqNameReplacement( + element: KtSimpleNameExpression, + referencedElement: PsiElement, + private val newFqName: FqName +) : KtResolvableCollisionUsageInfo(element, referencedElement) { + override fun apply() { + (element as? KtSimpleNameExpression)?.mainReference?.bindToFqName(newFqName) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinPropertyProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinPropertyProcessor.kt index 31d80996653..c4766906043 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinPropertyProcessor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/RenameKotlinPropertyProcessor.kt @@ -21,14 +21,18 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.ui.Messages import com.intellij.psi.* import com.intellij.psi.search.SearchScope +import com.intellij.psi.search.searches.DirectClassInheritorsSearch import com.intellij.psi.search.searches.OverridingMethodsSearch import com.intellij.refactoring.listeners.RefactoringElementListener import com.intellij.refactoring.rename.RenameProcessor import com.intellij.refactoring.util.RefactoringUtil import com.intellij.usageView.UsageInfo +import com.intellij.usageView.UsageViewUtil import org.jetbrains.kotlin.asJava.* +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary @@ -39,11 +43,17 @@ import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.psi.psiUtil.findPropertyByName import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.OverrideResolver import org.jetbrains.kotlin.resolve.dataClassUtils.isComponentLike +import org.jetbrains.kotlin.resolve.source.getPsi +import org.jetbrains.kotlin.util.findCallableMemberBySignature +import org.jetbrains.kotlin.utils.DFS +import org.jetbrains.kotlin.utils.SmartList class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() { override fun canProcessElement(element: PsiElement): Boolean { @@ -79,6 +89,107 @@ class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() { } } + private fun checkAccidentalOverrides( + declaration: KtNamedDeclaration, + newName: String, + descriptor: VariableDescriptor, + result: MutableList + ) { + fun reportAccidentalOverride(candidate: PsiNamedElement) { + val what = UsageViewUtil.getType(declaration).capitalize() + val withWhat = candidate.renderDescription() + val where = candidate.representativeContainer()?.renderDescription() ?: return + val message = "$what after rename will clash with existing $withWhat in $where" + result += BasicUnresolvableCollisionUsageInfo(candidate, candidate, message) + } + + if (descriptor !is PropertyDescriptor) return + val initialClass = declaration.containingClassOrObject ?: return + val initialClassDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return + + val prototype = object : PropertyDescriptor by descriptor { + override fun getName() = Name.guessByFirstCharacter(newName) + } + + DFS.dfs( + listOf(initialClassDescriptor), + DFS.Neighbors { DescriptorUtils.getSuperclassDescriptors(it) }, + object : DFS.AbstractNodeHandler() { + override fun beforeChildren(current: ClassDescriptor): Boolean { + if (current == initialClassDescriptor) return true + (current.findCallableMemberBySignature(prototype))?.let { candidateDescriptor -> + val candidate = candidateDescriptor.source.getPsi() as? PsiNamedElement ?: return false + reportAccidentalOverride(candidate) + return false + } + return true + } + + override fun result() { + + } + } + ) + + if (!declaration.hasModifier(KtTokens.PRIVATE_KEYWORD)) { + val initialPsiClass = initialClass.toLightClass() ?: return + val prototypes = declaration.toLightMethods().mapNotNull { + it as KtLightMethod + val methodName = accessorNameByPropertyName(newName, it) ?: return@mapNotNull null + object : KtLightMethod by it { + override fun getName() = methodName + } + } + DFS.dfs( + listOf(initialPsiClass), + DFS.Neighbors { DirectClassInheritorsSearch.search(it) }, + object : DFS.AbstractNodeHandler() { + override fun beforeChildren(current: PsiClass): Boolean { + if (current == initialPsiClass) return true + + if (current is KtLightClass) { + val property = current.kotlinOrigin?.findPropertyByName(newName) ?: return true + reportAccidentalOverride(property) + return false + } + + for (psiMethod in prototypes) { + current.findMethodBySignature(psiMethod, false)?.let { + val candidate = it.unwrapped as? PsiNamedElement ?: return true + reportAccidentalOverride(candidate) + return false + } + } + + return true + } + + override fun result() { + + } + } + ) + } + } + + override fun findCollisions( + element: PsiElement, + newName: String?, + allRenames: MutableMap, + result: MutableList + ) { + if (newName == null) return + val declaration = element.namedUnwrappedElement as? KtNamedDeclaration ?: return + val descriptor = declaration.resolveToDescriptor() as VariableDescriptor + + val collisions = SmartList() + checkRedeclarations(descriptor, newName, collisions) + checkAccidentalOverrides(declaration, newName, descriptor, collisions) + checkOriginalUsagesRetargeting(declaration, newName, result, collisions) + checkNewNameUsagesRetargeting(declaration, newName, collisions) + result += collisions + } + private fun chooseCallableToRename(callableDeclaration: KtCallableDeclaration): KtCallableDeclaration? { val deepestSuperDeclaration = findDeepestOverriddenDeclaration(callableDeclaration) if (deepestSuperDeclaration == null || deepestSuperDeclaration == callableDeclaration) { @@ -200,6 +311,8 @@ class RenameKotlinPropertyProcessor : RenameKotlinPsiProcessor() { refKindUsages[UsageKind.SIMPLE_PROPERTY_USAGE]?.toTypedArray() ?: arrayOf(), null) + usages.forEach { (it as? UsageInfoWithReplacement)?.apply() } + dropOverrideKeywordIfNecessary(element) listener?.elementRenamed(element) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt new file mode 100644 index 00000000000..0bf2420261f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/rename/renameConflictUtils.kt @@ -0,0 +1,239 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.rename + +import com.intellij.psi.* +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.refactoring.util.MoveRenameUsageInfo +import com.intellij.usageView.UsageInfo +import com.intellij.usageView.UsageViewUtil +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.idea.analysis.analyzeInContext +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde +import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator +import org.jetbrains.kotlin.idea.core.copied +import org.jetbrains.kotlin.idea.imports.importableFqName +import org.jetbrains.kotlin.idea.refactoring.explicateAsText +import org.jetbrains.kotlin.idea.refactoring.getThisLabelName +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.search.and +import org.jetbrains.kotlin.idea.search.restrictToKotlinSources +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.getAllAccessibleVariables +import org.jetbrains.kotlin.idea.util.getResolutionScope +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.renderer.DescriptorRenderer +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.calls.resolvedCallUtil.getExplicitReceiverValue +import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getImplicitReceiverValue +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter +import org.jetbrains.kotlin.resolve.scopes.LexicalScope +import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered +import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy +import org.jetbrains.kotlin.resolve.source.getPsi +import org.jetbrains.kotlin.utils.SmartList + +internal fun ResolvedCall<*>.noReceivers() = dispatchReceiver == null && extensionReceiver == null + +internal fun PsiNamedElement.renderDescription() = "${UsageViewUtil.getType(this)} '$name'".trim() + +internal fun PsiElement.representativeContainer(): PsiNamedElement? = + when (this) { + is KtDeclaration -> containingClassOrObject + ?: getStrictParentOfType() + ?: JavaPsiFacade.getInstance(project).findPackage(getContainingKtFile().packageFqName.asString()) + is PsiMember -> containingClass + else -> null + } + +internal fun DeclarationDescriptor.canonicalRender(): String = DescriptorRenderer.FQ_NAMES_IN_TYPES.render(this) + +internal fun checkRedeclarations( + descriptor: DeclarationDescriptor, + newName: String, + result: MutableList +) { + val containingDescriptor = descriptor.containingDeclaration + val containingScope = when (containingDescriptor) { + is ClassDescriptor -> containingDescriptor.unsubstitutedMemberScope + is PackageFragmentDescriptor -> containingDescriptor.getMemberScope() + else -> return + } + containingScope.getDescriptorsFiltered(DescriptorKindFilter.VARIABLES) { it.asString() == newName }.firstOrNull()?.let { candidateDescriptor -> + val candidate = (candidateDescriptor as? DeclarationDescriptorWithSource)?.source?.getPsi() as? KtNamedDeclaration ?: return + val what = candidate.renderDescription().capitalize() + val where = candidate.representativeContainer()?.renderDescription() ?: return + val message = "$what is already declared in $where" + result += BasicUnresolvableCollisionUsageInfo(candidate, candidate, message) + } +} + +private fun LexicalScope.getRelevantDescriptors( + declaration: PsiNamedElement, + name: String +): Collection { + val nameAsName = Name.identifier(name) + return when (declaration) { + is KtProperty, is KtParameter, is PsiField -> getAllAccessibleVariables(nameAsName) + else -> emptyList() + } +} + +fun reportShadowing( + declaration: PsiNamedElement, + elementToBindUsageInfoTo: PsiElement, + candidateDescriptor: DeclarationDescriptor, + refElement: PsiElement, + result: MutableList +) { + val candidate = DescriptorToSourceUtilsIde.getAnyDeclaration(declaration.project, candidateDescriptor) as? PsiNamedElement ?: return + val message = "${declaration.renderDescription().capitalize()} will be shadowed by ${candidate.renderDescription()}" + result += BasicUnresolvableCollisionUsageInfo(refElement, elementToBindUsageInfoTo, message) +} + +private fun checkUsagesRetargeting( + elementToBindUsageInfosTo: PsiElement, + declaration: PsiNamedElement, + name: String, + isNewName: Boolean, + accessibleDescriptors: Collection, + originalUsages: MutableList, + newUsages: MutableList +) { + val usageIterator = originalUsages.listIterator() + while (usageIterator.hasNext()) { + val usage = usageIterator.next() + val refElement = usage.element as? KtSimpleNameExpression ?: continue + val context = refElement.analyze(BodyResolveMode.PARTIAL) + val scope = refElement + .parentsWithSelf + .filterIsInstance() + .mapNotNull { context[BindingContext.LEXICAL_SCOPE, it] } + .firstOrNull() + ?: continue + + if (scope.getRelevantDescriptors(declaration, name).isEmpty()) { + if (declaration !is KtProperty && declaration !is KtParameter) continue + if (NewDeclarationNameValidator(refElement.parent, refElement, NewDeclarationNameValidator.Target.VARIABLES)(name)) continue + } + + val psiFactory = KtPsiFactory(declaration) + + val resolvedCall = refElement.getResolvedCall(context) ?: continue + val callExpression = resolvedCall.call.callElement as? KtExpression ?: continue + val fullCallExpression = callExpression.getQualifiedExpressionForSelectorOrThis() + + val qualifiedExpression = if (resolvedCall.noReceivers()) { + val resultingDescriptor = resolvedCall.resultingDescriptor + val fqName = + resultingDescriptor.importableFqName + ?: (resultingDescriptor as? ClassifierDescriptor)?.let { + FqName(IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(it)) + } + ?: continue + if (fqName.parent().isRoot) { + callExpression.copied() + } + else { + psiFactory.createExpressionByPattern("${fqName.parent().asString()}.$0", callExpression) + } + } + else { + resolvedCall.getExplicitReceiverValue()?.let { + fullCallExpression.copied() + } + ?: resolvedCall.getImplicitReceiverValue()?.let { implicitReceiver -> + val expectedLabelName = implicitReceiver.declarationDescriptor.getThisLabelName() + val implicitReceivers = scope.getImplicitReceiversHierarchy() + val receiversWithExpectedName = implicitReceivers.filter { + it.value.type.constructor.declarationDescriptor?.getThisLabelName() == expectedLabelName + } + + val canQualifyThis = receiversWithExpectedName.size <= 1 + if (canQualifyThis) { + psiFactory.createExpressionByPattern("${implicitReceiver.explicateAsText()}.$0", callExpression) + } + else { + val defaultReceiverClassText = + implicitReceivers.firstOrNull()?.value?.type?.constructor?.declarationDescriptor?.canonicalRender() + val canInsertUnqualifiedThis = accessibleDescriptors.any { it.canonicalRender() == defaultReceiverClassText } + if (canInsertUnqualifiedThis) { + psiFactory.createExpressionByPattern("this.$0", callExpression) + } + else { + callExpression.copied() + } + } + } + ?: continue + } + + val newCallee = qualifiedExpression.getQualifiedElementSelector() as? KtSimpleNameExpression ?: continue + if (isNewName) { + newCallee.getReferencedNameElement().replace(psiFactory.createNameIdentifier(name)) + } + + val newContext = qualifiedExpression.analyzeInContext(scope, refElement) + + val newResolvedCall = newCallee.getResolvedCall(newContext) + val candidateText = newResolvedCall?.candidateDescriptor?.canonicalRender() + + if (newResolvedCall != null + && !accessibleDescriptors.any { it.canonicalRender() == candidateText } + && resolvedCall.candidateDescriptor.canonicalRender() != candidateText) { + reportShadowing(declaration, elementToBindUsageInfosTo, newResolvedCall.candidateDescriptor, refElement, newUsages) + continue + } + + usageIterator.set(UsageInfoWithReplacement(fullCallExpression, declaration, qualifiedExpression)) + } +} + +internal fun checkOriginalUsagesRetargeting( + declaration: KtNamedDeclaration, + newName: String, + originalUsages: MutableList, + newUsages: MutableList +) { + val accessibleDescriptors = declaration.getResolutionScope().getRelevantDescriptors(declaration, newName) + checkUsagesRetargeting(declaration, declaration, newName, true, accessibleDescriptors, originalUsages, newUsages) +} + +internal fun checkNewNameUsagesRetargeting( + declaration: KtNamedDeclaration, + newName: String, + newUsages: MutableList +) { + val currentName = declaration.name ?: return + val descriptor = declaration.resolveToDescriptor() + for (candidateDescriptor in declaration.getResolutionScope().getRelevantDescriptors(declaration, newName)) { + val candidate = DescriptorToSourceUtilsIde.getAnyDeclaration(declaration.project, candidateDescriptor) as? PsiNamedElement ?: continue + val usages = ReferencesSearch + .search(candidate, candidate.useScope.restrictToKotlinSources() and declaration.useScope) + .mapTo(SmartList()) { MoveRenameUsageInfo(it, candidate) } + checkUsagesRetargeting(candidate, declaration, currentName, false, listOf(descriptor), usages, newUsages) + usages.filterIsInstanceTo>(newUsages) + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/inplace/localVarShadowingMemberProperty.kt b/idea/testData/refactoring/rename/inplace/localVarShadowingMemberProperty.kt new file mode 100644 index 00000000000..dd435b9515d --- /dev/null +++ b/idea/testData/refactoring/rename/inplace/localVarShadowingMemberProperty.kt @@ -0,0 +1,10 @@ +package test + +class ShadeKotlin { + val name1 = 1; + fun inner() { + val name2 = 2; + print(name1) + print(name2) + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/inplace/localVarShadowingMemberProperty.kt.after b/idea/testData/refactoring/rename/inplace/localVarShadowingMemberProperty.kt.after new file mode 100644 index 00000000000..7a1b56f17c4 --- /dev/null +++ b/idea/testData/refactoring/rename/inplace/localVarShadowingMemberProperty.kt.after @@ -0,0 +1,10 @@ +package test + +class ShadeKotlin { + val name1 = 1; + fun inner() { + val name1 = 2; + print(this.name1) + print(name1) + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/memberPropertyRedeclaration/before/test/test.kt b/idea/testData/refactoring/rename/memberPropertyRedeclaration/before/test/test.kt new file mode 100644 index 00000000000..6564151c98c --- /dev/null +++ b/idea/testData/refactoring/rename/memberPropertyRedeclaration/before/test/test.kt @@ -0,0 +1,6 @@ +package test + +class A { + val foo = 1 + val /*rename*/bar = 2 +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/memberPropertyRedeclaration/memberPropertyRedeclaration.test b/idea/testData/refactoring/rename/memberPropertyRedeclaration/memberPropertyRedeclaration.test new file mode 100644 index 00000000000..b059772e0a3 --- /dev/null +++ b/idea/testData/refactoring/rename/memberPropertyRedeclaration/memberPropertyRedeclaration.test @@ -0,0 +1,7 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "foo", + "withRuntime": "true", + "hint": "Property 'foo' is already declared in class 'A'" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/after/test/test.kt b/idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/after/test/test.kt new file mode 100644 index 00000000000..6305bfcdce5 --- /dev/null +++ b/idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/after/test/test.kt @@ -0,0 +1,10 @@ +package test + +class ShadeKotlin { + val name2 = 1; + fun inner() { + val name2 = 2; + print(this.name2) + print(name2) + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/before/test/test.kt b/idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/before/test/test.kt new file mode 100644 index 00000000000..09f46320418 --- /dev/null +++ b/idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/before/test/test.kt @@ -0,0 +1,10 @@ +package test + +class ShadeKotlin { + val /*rename*/name1 = 1; + fun inner() { + val name2 = 2; + print(name1) + print(name2) + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/memberPropertyShadowedByLocalVar.test b/idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/memberPropertyShadowedByLocalVar.test new file mode 100644 index 00000000000..5e68c14cbbd --- /dev/null +++ b/idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/memberPropertyShadowedByLocalVar.test @@ -0,0 +1,6 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "name2", + "withRuntime": "true" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/after/test/test.kt b/idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/after/test/test.kt new file mode 100644 index 00000000000..af6e9240c9a --- /dev/null +++ b/idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/after/test/test.kt @@ -0,0 +1,17 @@ +package test + +class X { + val x: Int get() = 1 + + inner class A { + val xx: Int get() = 1 + + inner class XX { + val xx: Int get() = 1 + + fun test() { + this@A.xx + } + } + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/before/test/test.kt b/idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/before/test/test.kt new file mode 100644 index 00000000000..d6ca1bde662 --- /dev/null +++ b/idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/before/test/test.kt @@ -0,0 +1,17 @@ +package test + +class X { + val x: Int get() = 1 + + inner class A { + val /*rename*/a: Int get() = 1 + + inner class XX { + val xx: Int get() = 1 + + fun test() { + a + } + } + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/memberPropertyWithInnerInstanceProperty.test b/idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/memberPropertyWithInnerInstanceProperty.test new file mode 100644 index 00000000000..283c8da0500 --- /dev/null +++ b/idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/memberPropertyWithInnerInstanceProperty.test @@ -0,0 +1,6 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "xx", + "withRuntime": "true" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/after/test/test.kt b/idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/after/test/test.kt new file mode 100644 index 00000000000..ce333361701 --- /dev/null +++ b/idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/after/test/test.kt @@ -0,0 +1,17 @@ +package test + +class X { + val x: Int get() = 1 + + inner class A { + val x: Int get() = 1 + + inner class XX { + val xx: Int get() = 1 + + fun test() { + x + } + } + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/before/test/test.kt b/idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/before/test/test.kt new file mode 100644 index 00000000000..d6ca1bde662 --- /dev/null +++ b/idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/before/test/test.kt @@ -0,0 +1,17 @@ +package test + +class X { + val x: Int get() = 1 + + inner class A { + val /*rename*/a: Int get() = 1 + + inner class XX { + val xx: Int get() = 1 + + fun test() { + a + } + } + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/memberPropertyWithOuterInstanceProperty.test b/idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/memberPropertyWithOuterInstanceProperty.test new file mode 100644 index 00000000000..a2191f0815d --- /dev/null +++ b/idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/memberPropertyWithOuterInstanceProperty.test @@ -0,0 +1,6 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "x", + "withRuntime": "true" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/propertyAccidentalOverrideSubclass/before/test/test.kt b/idea/testData/refactoring/rename/propertyAccidentalOverrideSubclass/before/test/test.kt new file mode 100644 index 00000000000..ff34daacda4 --- /dev/null +++ b/idea/testData/refactoring/rename/propertyAccidentalOverrideSubclass/before/test/test.kt @@ -0,0 +1,9 @@ +package test + +open class A { + val /*rename*/foo = 1 +} + +open class B : A() { + val bar = 2 +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/propertyAccidentalOverrideSubclass/propertyAccidentalOverrideSubclass.test b/idea/testData/refactoring/rename/propertyAccidentalOverrideSubclass/propertyAccidentalOverrideSubclass.test new file mode 100644 index 00000000000..f91170482e0 --- /dev/null +++ b/idea/testData/refactoring/rename/propertyAccidentalOverrideSubclass/propertyAccidentalOverrideSubclass.test @@ -0,0 +1,7 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "bar", + "withRuntime": "true", + "hint": "Property after rename will clash with existing property 'bar' in class 'B'" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/propertyAccidentalOverrideSuperclass/before/test/test.kt b/idea/testData/refactoring/rename/propertyAccidentalOverrideSuperclass/before/test/test.kt new file mode 100644 index 00000000000..a1529df12a2 --- /dev/null +++ b/idea/testData/refactoring/rename/propertyAccidentalOverrideSuperclass/before/test/test.kt @@ -0,0 +1,9 @@ +package test + +open class A { + val foo = 1 +} + +open class B : A() { + val /*rename*/bar = 2 +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/propertyAccidentalOverrideSuperclass/propertyAccidentalOverrideSuperclass.test b/idea/testData/refactoring/rename/propertyAccidentalOverrideSuperclass/propertyAccidentalOverrideSuperclass.test new file mode 100644 index 00000000000..e7d1b610768 --- /dev/null +++ b/idea/testData/refactoring/rename/propertyAccidentalOverrideSuperclass/propertyAccidentalOverrideSuperclass.test @@ -0,0 +1,7 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "foo", + "withRuntime": "true", + "hint": "Property after rename will clash with existing property 'foo' in class 'A'" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSubclass/before/test/test.kt b/idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSubclass/before/test/test.kt new file mode 100644 index 00000000000..7e955c9e221 --- /dev/null +++ b/idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSubclass/before/test/test.kt @@ -0,0 +1,7 @@ +package test + +open class A(val /*rename*/foo: Int) + +open class B : A() { + val bar = 2 +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSubclass/propertyParameterAccidentalOverrideSubclass.test b/idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSubclass/propertyParameterAccidentalOverrideSubclass.test new file mode 100644 index 00000000000..f4d11f2598a --- /dev/null +++ b/idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSubclass/propertyParameterAccidentalOverrideSubclass.test @@ -0,0 +1,7 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "bar", + "withRuntime": "true", + "hint": "Parameter after rename will clash with existing property 'bar' in class 'B'" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSuperclass/before/test/test.kt b/idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSuperclass/before/test/test.kt new file mode 100644 index 00000000000..638bd03da3c --- /dev/null +++ b/idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSuperclass/before/test/test.kt @@ -0,0 +1,7 @@ +package test + +open class A(val foo: Int) + +open class B : A() { + val /*rename*/bar = 2 +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSuperclass/propertyParameterAccidentalOverrideSuperclass.test b/idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSuperclass/propertyParameterAccidentalOverrideSuperclass.test new file mode 100644 index 00000000000..9a114ae8ca8 --- /dev/null +++ b/idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSuperclass/propertyParameterAccidentalOverrideSuperclass.test @@ -0,0 +1,7 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "foo", + "withRuntime": "true", + "hint": "Property after rename will clash with existing parameter 'foo' in class 'A'" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/propertyParameterRedeclaredByProperty/before/test/test.kt b/idea/testData/refactoring/rename/propertyParameterRedeclaredByProperty/before/test/test.kt new file mode 100644 index 00000000000..aa4473e6207 --- /dev/null +++ b/idea/testData/refactoring/rename/propertyParameterRedeclaredByProperty/before/test/test.kt @@ -0,0 +1,5 @@ +package test + +class Foo(val /*rename*/a: Int) { + val b = 1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/propertyParameterRedeclaredByProperty/propertyParameterRedeclaredByProperty.test b/idea/testData/refactoring/rename/propertyParameterRedeclaredByProperty/propertyParameterRedeclaredByProperty.test new file mode 100644 index 00000000000..03b04f881cf --- /dev/null +++ b/idea/testData/refactoring/rename/propertyParameterRedeclaredByProperty/propertyParameterRedeclaredByProperty.test @@ -0,0 +1,7 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "b", + "withRuntime": "true", + "hint": "Property 'b' is already declared in class 'Foo'" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/propertyRedeclaredByPropertyParameter/before/test/test.kt b/idea/testData/refactoring/rename/propertyRedeclaredByPropertyParameter/before/test/test.kt new file mode 100644 index 00000000000..0cece2b5898 --- /dev/null +++ b/idea/testData/refactoring/rename/propertyRedeclaredByPropertyParameter/before/test/test.kt @@ -0,0 +1,5 @@ +package test + +class Foo(val a: Int) { + val /*rename*/b = 1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/propertyRedeclaredByPropertyParameter/propertyRedeclaredByPropertyParameter.test b/idea/testData/refactoring/rename/propertyRedeclaredByPropertyParameter/propertyRedeclaredByPropertyParameter.test new file mode 100644 index 00000000000..ee6657662d4 --- /dev/null +++ b/idea/testData/refactoring/rename/propertyRedeclaredByPropertyParameter/propertyRedeclaredByPropertyParameter.test @@ -0,0 +1,7 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "a", + "withRuntime": "true", + "hint": "Parameter 'a' is already declared in class 'Foo'" +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/topLevelPropertyRedeclaration/before/test/test.kt b/idea/testData/refactoring/rename/topLevelPropertyRedeclaration/before/test/test.kt new file mode 100644 index 00000000000..375ceb8b5b3 --- /dev/null +++ b/idea/testData/refactoring/rename/topLevelPropertyRedeclaration/before/test/test.kt @@ -0,0 +1,4 @@ +package test + +val foo = 1 +val /*rename*/bar = 2 \ No newline at end of file diff --git a/idea/testData/refactoring/rename/topLevelPropertyRedeclaration/topLevelPropertyRedeclaration.test b/idea/testData/refactoring/rename/topLevelPropertyRedeclaration/topLevelPropertyRedeclaration.test new file mode 100644 index 00000000000..a9f9eef5fb0 --- /dev/null +++ b/idea/testData/refactoring/rename/topLevelPropertyRedeclaration/topLevelPropertyRedeclaration.test @@ -0,0 +1,7 @@ +{ + "type": "MARKED_ELEMENT", + "mainFile": "test/test.kt", + "newName": "foo", + "withRuntime": "true", + "hint": "Property 'foo' is already declared in package 'test'" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/InplaceRenameTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/InplaceRenameTest.kt index fa1f2e3193b..7d9f9e36b6c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/InplaceRenameTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/InplaceRenameTest.kt @@ -128,6 +128,10 @@ class InplaceRenameTest : LightPlatformCodeInsightTestCase() { doTestInplaceRename("foo") } + fun testLocalVarShadowingMemberProperty() { + doTestInplaceRename("name1") + } + private fun doTestInplaceRename(newName: String?) { configureByFile(getTestName(false) + ".kt") val element = TargetElementUtilBase.findTargetElement( diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java index 4f81370039d..6504528a288 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java @@ -131,6 +131,66 @@ public class RenameTestGenerated extends AbstractRenameTest { doTest(fileName); } + @TestMetadata("memberPropertyRedeclaration/memberPropertyRedeclaration.test") + public void testMemberPropertyRedeclaration_MemberPropertyRedeclaration() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/memberPropertyRedeclaration/memberPropertyRedeclaration.test"); + doTest(fileName); + } + + @TestMetadata("memberPropertyShadowedByLocalVar/memberPropertyShadowedByLocalVar.test") + public void testMemberPropertyShadowedByLocalVar_MemberPropertyShadowedByLocalVar() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/memberPropertyShadowedByLocalVar/memberPropertyShadowedByLocalVar.test"); + doTest(fileName); + } + + @TestMetadata("memberPropertyWithInnerInstanceProperty/memberPropertyWithInnerInstanceProperty.test") + public void testMemberPropertyWithInnerInstanceProperty_MemberPropertyWithInnerInstanceProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/memberPropertyWithInnerInstanceProperty/memberPropertyWithInnerInstanceProperty.test"); + doTest(fileName); + } + + @TestMetadata("memberPropertyWithOuterInstanceProperty/memberPropertyWithOuterInstanceProperty.test") + public void testMemberPropertyWithOuterInstanceProperty_MemberPropertyWithOuterInstanceProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/memberPropertyWithOuterInstanceProperty/memberPropertyWithOuterInstanceProperty.test"); + doTest(fileName); + } + + @TestMetadata("propertyAccidentalOverrideSubclass/propertyAccidentalOverrideSubclass.test") + public void testPropertyAccidentalOverrideSubclass_PropertyAccidentalOverrideSubclass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/propertyAccidentalOverrideSubclass/propertyAccidentalOverrideSubclass.test"); + doTest(fileName); + } + + @TestMetadata("propertyAccidentalOverrideSuperclass/propertyAccidentalOverrideSuperclass.test") + public void testPropertyAccidentalOverrideSuperclass_PropertyAccidentalOverrideSuperclass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/propertyAccidentalOverrideSuperclass/propertyAccidentalOverrideSuperclass.test"); + doTest(fileName); + } + + @TestMetadata("propertyParameterAccidentalOverrideSubclass/propertyParameterAccidentalOverrideSubclass.test") + public void testPropertyParameterAccidentalOverrideSubclass_PropertyParameterAccidentalOverrideSubclass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSubclass/propertyParameterAccidentalOverrideSubclass.test"); + doTest(fileName); + } + + @TestMetadata("propertyParameterAccidentalOverrideSuperclass/propertyParameterAccidentalOverrideSuperclass.test") + public void testPropertyParameterAccidentalOverrideSuperclass_PropertyParameterAccidentalOverrideSuperclass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/propertyParameterAccidentalOverrideSuperclass/propertyParameterAccidentalOverrideSuperclass.test"); + doTest(fileName); + } + + @TestMetadata("propertyParameterRedeclaredByProperty/propertyParameterRedeclaredByProperty.test") + public void testPropertyParameterRedeclaredByProperty_PropertyParameterRedeclaredByProperty() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/propertyParameterRedeclaredByProperty/propertyParameterRedeclaredByProperty.test"); + doTest(fileName); + } + + @TestMetadata("propertyRedeclaredByPropertyParameter/propertyRedeclaredByPropertyParameter.test") + public void testPropertyRedeclaredByPropertyParameter_PropertyRedeclaredByPropertyParameter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/propertyRedeclaredByPropertyParameter/propertyRedeclaredByPropertyParameter.test"); + doTest(fileName); + } + @TestMetadata("renameArgumentsWhenParameterRenamed/parameter.test") public void testRenameArgumentsWhenParameterRenamed_Parameter() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/renameArgumentsWhenParameterRenamed/parameter.test"); @@ -784,4 +844,10 @@ public class RenameTestGenerated extends AbstractRenameTest { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/syntheticPropertyUsages3/renameSetMethod.test"); doTest(fileName); } + + @TestMetadata("topLevelPropertyRedeclaration/topLevelPropertyRedeclaration.test") + public void testTopLevelPropertyRedeclaration_TopLevelPropertyRedeclaration() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/rename/topLevelPropertyRedeclaration/topLevelPropertyRedeclaration.test"); + doTest(fileName); + } }