Automatic renaming properties, variables, parameters when renaming class.
#KT-4642 in progress
This commit is contained in:
@@ -305,6 +305,7 @@
|
||||
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameKotlinImplicitLambdaParameter"/>
|
||||
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameDynamicMemberHandler"/>
|
||||
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameOnSecondaryConstructorHandler"/>
|
||||
<automaticRenamerFactory implementation="org.jetbrains.kotlin.idea.refactoring.rename.AutomaticVariableRenamerFactory"/>
|
||||
|
||||
<spellchecker.support implementationClass="org.jetbrains.kotlin.idea.KotlinSpellcheckingStrategy" language="jet"/>
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.refactoring.rename
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.refactoring.JavaRefactoringSettings
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.rename.naming.AutomaticRenamer
|
||||
import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.psi.JetClass
|
||||
import org.jetbrains.kotlin.psi.JetNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.JetParameter
|
||||
import org.jetbrains.kotlin.psi.JetVariableDeclaration
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.types.JetType
|
||||
import java.util.ArrayList
|
||||
|
||||
public class AutomaticVariableRenamer(klass: JetClass, newClassName: String, usages: Collection<UsageInfo>) : AutomaticRenamer() {
|
||||
private val toUnpluralize = ArrayList<JetNamedDeclaration>()
|
||||
|
||||
init {
|
||||
val classType = (klass.resolveToDescriptor() as ClassDescriptor).getDefaultType()
|
||||
|
||||
for (usage in usages) {
|
||||
val usageElement = usage.getElement() ?: continue
|
||||
|
||||
val parameterOrVariable = PsiTreeUtil.getParentOfType(
|
||||
usageElement,
|
||||
javaClass<JetVariableDeclaration>(),
|
||||
javaClass<JetParameter>()
|
||||
) ?: continue
|
||||
|
||||
if (parameterOrVariable.getTypeReference()?.isAncestor(usageElement) != true) continue
|
||||
val type = (parameterOrVariable.resolveToDescriptor() as VariableDescriptor).getType()
|
||||
if (type.isCollectionLikeOf(classType)) {
|
||||
toUnpluralize.add(parameterOrVariable)
|
||||
}
|
||||
|
||||
myElements.add(parameterOrVariable)
|
||||
}
|
||||
|
||||
suggestAllNames(klass.getName(), newClassName)
|
||||
}
|
||||
|
||||
override fun getDialogTitle() = RefactoringBundle.message("rename.variables.title")
|
||||
|
||||
override fun getDialogDescription() = RefactoringBundle.message("rename.variables.with.the.following.names.to")
|
||||
|
||||
override fun entityName() = RefactoringBundle.message("entity.name.variable")
|
||||
|
||||
override fun nameToCanonicalName(name: String, element: PsiNamedElement): String? {
|
||||
if (element in toUnpluralize) {
|
||||
val singular = StringUtil.unpluralize(name)
|
||||
if (singular != null) return singular
|
||||
toUnpluralize.remove(element)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
override fun canonicalNameToName(canonicalName: String, element: PsiNamedElement): String? {
|
||||
return if (element in toUnpluralize)
|
||||
StringUtil.pluralize(canonicalName)
|
||||
else
|
||||
canonicalName
|
||||
}
|
||||
}
|
||||
|
||||
private fun JetType.isCollectionLikeOf(elementType: JetType): Boolean {
|
||||
val klass = this.getConstructor().getDeclarationDescriptor() as? ClassDescriptor ?: return false
|
||||
if (KotlinBuiltIns.isArray(this) || DescriptorUtils.isSubclass(klass, KotlinBuiltIns.getInstance().getCollection())) {
|
||||
val typeArgument = this.getArguments().singleOrNull()?.getType() ?: return false
|
||||
return elementType == typeArgument || typeArgument.isCollectionLikeOf(elementType)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
public class AutomaticVariableRenamerFactory: AutomaticRenamerFactory {
|
||||
override fun isApplicable(element: PsiElement) = element is JetClass
|
||||
|
||||
override fun createRenamer(element: PsiElement, newName: String, usages: Collection<UsageInfo>) =
|
||||
AutomaticVariableRenamer(element as JetClass, newName, usages)
|
||||
|
||||
override fun isEnabled() = JavaRefactoringSettings.getInstance().isToRenameVariables()
|
||||
override fun setEnabled(enabled: Boolean) = JavaRefactoringSettings.getInstance().setRenameVariables(enabled)
|
||||
|
||||
override fun getOptionName() = RefactoringBundle.message("rename.variables")
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
data class Pair<out A, out B>(
|
||||
public val first: A,
|
||||
public val second: B
|
||||
)
|
||||
|
||||
|
||||
public fun listOf<T>(): List<T> = throw Error()
|
||||
@@ -0,0 +1,35 @@
|
||||
class Bar : Throwable()
|
||||
|
||||
val bar: Bar = Bar()
|
||||
val bar1: Bar = Bar()
|
||||
|
||||
val bars: List<Bar> = listOf()
|
||||
val foos1: Array<Bar> = array()
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val bar: Bar = Bar()
|
||||
val someVerySpecialBar: Bar = Bar()
|
||||
val barAnother: Bar = Bar()
|
||||
|
||||
val (bar1: Bar, bars: List<Bar>) = Pair(Bar(), listOf<Bar>())
|
||||
|
||||
try {
|
||||
for (bar2: Bar in listOf<Bar>()) {
|
||||
|
||||
}
|
||||
} catch (bar: Bar) {
|
||||
|
||||
}
|
||||
|
||||
fun local(bar: Bar) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun topLevel(bar: Bar) {
|
||||
|
||||
}
|
||||
|
||||
fun collectionLikes(bars: List<Array<Bar>>, foos: List<Map<Bar, Bar>>) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
data class Pair<out A, out B>(
|
||||
public val first: A,
|
||||
public val second: B
|
||||
)
|
||||
|
||||
|
||||
public fun listOf<T>(): List<T> = throw Error()
|
||||
@@ -0,0 +1,35 @@
|
||||
class Foo : Throwable()
|
||||
|
||||
val foo: Foo = Foo()
|
||||
val foo1: Foo = Foo()
|
||||
|
||||
val foos: List<Foo> = listOf()
|
||||
val foos1: Array<Foo> = array()
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val foo: Foo = Foo()
|
||||
val someVerySpecialFoo: Foo = Foo()
|
||||
val fooAnother: Foo = Foo()
|
||||
|
||||
val (foo1: Foo, foos: List<Foo>) = Pair(Foo(), listOf<Foo>())
|
||||
|
||||
try {
|
||||
for (foo2: Foo in listOf<Foo>()) {
|
||||
|
||||
}
|
||||
} catch (foo: Foo) {
|
||||
|
||||
}
|
||||
|
||||
fun local(foo: Foo) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun topLevel(foo: Foo) {
|
||||
|
||||
}
|
||||
|
||||
fun collectionLikes(foos: List<Array<Foo>>, foos: List<Map<Foo, Foo>>) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"type": "KOTLIN_CLASS",
|
||||
"classId": "/Foo",
|
||||
"oldName": "Foo",
|
||||
"newName": "Bar",
|
||||
"mainFile": "main.kt"
|
||||
}
|
||||
@@ -16,37 +16,42 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.rename
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import java.io.File
|
||||
import org.junit.Assert
|
||||
import com.google.gson.JsonParser
|
||||
import com.google.gson.JsonObject
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.google.gson.JsonParser
|
||||
import com.intellij.openapi.extensions.Extensions
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
|
||||
import com.intellij.refactoring.rename.RenameProcessor
|
||||
import com.intellij.refactoring.rename.RenamePsiElementProcessor
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.module.Module
|
||||
import java.util.Collections
|
||||
import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil.RefactoringErrorHintException
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.getString
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.getNullableString
|
||||
import org.jetbrains.kotlin.idea.search.allScope
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil.RefactoringErrorHintException
|
||||
import org.jetbrains.kotlin.idea.test.KotlinMultiFileTestCase
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
|
||||
import org.jetbrains.kotlin.name.*
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.getNullableString
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.getString
|
||||
import org.jetbrains.kotlin.idea.search.allScope
|
||||
import org.jetbrains.kotlin.idea.test.KotlinMultiFileTestCase
|
||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.name.isSubpackageOf
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
|
||||
import org.junit.Assert
|
||||
import java.io.File
|
||||
import java.util.Collections
|
||||
|
||||
private enum class RenameType {
|
||||
JAVA_CLASS
|
||||
@@ -110,7 +115,7 @@ public abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
|
||||
val aClass = context.javaFacade.findClass(classFQN, context.project.allScope())!!
|
||||
val substitution = RenamePsiElementProcessor.forElement(aClass).substituteElementToRename(aClass, null)
|
||||
|
||||
RenameProcessor(context.project, substitution, newName, true, true).run()
|
||||
runRenameProcessor(context, newName, substitution, true, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +133,7 @@ public abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
|
||||
if (method == null) throw IllegalStateException("Method with signature '$methodSignature' wasn't found in class $classFQN")
|
||||
|
||||
val substitution = RenamePsiElementProcessor.forElement(method).substituteElementToRename(method, null)
|
||||
RenameProcessor(context.project, substitution, newName, false, false).run()
|
||||
runRenameProcessor(context, newName, substitution, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +178,7 @@ public abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
|
||||
val psiElement = segmentReference.resolve()!!
|
||||
|
||||
val substitution = RenamePsiElementProcessor.forElement(psiElement).substituteElementToRename(psiElement, null)
|
||||
RenameProcessor(context.project, substitution, newName, true, true).run()
|
||||
runRenameProcessor(context, newName, substitution, true, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,10 +201,22 @@ public abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
|
||||
|
||||
val substitution = RenamePsiElementProcessor.forElement(psiElement).substituteElementToRename(psiElement, null)
|
||||
|
||||
RenameProcessor(context.project, substitution, newName, true, true).run()
|
||||
runRenameProcessor(context, newName, substitution, true, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun runRenameProcessor(
|
||||
context: TestContext,
|
||||
newName: String,
|
||||
substitution: PsiElement?,
|
||||
isSearchInComments: Boolean,
|
||||
isSearchTextOccurrences: Boolean
|
||||
) {
|
||||
val renameProcessor = RenameProcessor(context.project, substitution, newName, isSearchInComments, isSearchTextOccurrences)
|
||||
Extensions.getExtensions(AutomaticRenamerFactory.EP_NAME).forEach { renameProcessor.addRenamerFactory(it) }
|
||||
renameProcessor.run()
|
||||
}
|
||||
|
||||
protected fun getTestDirName(lowercaseFirstLetter : Boolean) : String {
|
||||
val testName = getTestName(lowercaseFirstLetter)
|
||||
return testName.substring(0, testName.indexOf('_'))
|
||||
|
||||
@@ -35,6 +35,12 @@ public class RenameTestGenerated extends AbstractRenameTest {
|
||||
JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/refactoring/rename"), Pattern.compile("^(.+)\\.test$"));
|
||||
}
|
||||
|
||||
@TestMetadata("automaticRenamer/simple.test")
|
||||
public void testAutomaticRenamer_Simple() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/rename/automaticRenamer/simple.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("defaultObject/defaultObject.test")
|
||||
public void testDefaultObject_DefaultObject() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/rename/defaultObject/defaultObject.test");
|
||||
|
||||
Reference in New Issue
Block a user