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.RenameKotlinImplicitLambdaParameter"/>
|
||||||
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameDynamicMemberHandler"/>
|
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameDynamicMemberHandler"/>
|
||||||
<renameHandler implementation="org.jetbrains.kotlin.idea.refactoring.rename.RenameOnSecondaryConstructorHandler"/>
|
<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"/>
|
<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
|
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.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.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.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.RenameProcessor
|
||||||
import com.intellij.refactoring.rename.RenamePsiElementProcessor
|
import com.intellij.refactoring.rename.RenamePsiElementProcessor
|
||||||
import org.jetbrains.kotlin.psi.JetFile
|
import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.refactoring.util.CommonRefactoringUtil.RefactoringErrorHintException
|
||||||
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 org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
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.idea.caches.resolve.analyzeFullyAndGetResult
|
||||||
import org.jetbrains.kotlin.name.*
|
import org.jetbrains.kotlin.idea.refactoring.move.getNullableString
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.resolveTopLevelClass
|
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.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
|
||||||
|
import org.junit.Assert
|
||||||
|
import java.io.File
|
||||||
|
import java.util.Collections
|
||||||
|
|
||||||
private enum class RenameType {
|
private enum class RenameType {
|
||||||
JAVA_CLASS
|
JAVA_CLASS
|
||||||
@@ -110,7 +115,7 @@ public abstract class AbstractRenameTest : KotlinMultiFileTestCase() {
|
|||||||
val aClass = context.javaFacade.findClass(classFQN, context.project.allScope())!!
|
val aClass = context.javaFacade.findClass(classFQN, context.project.allScope())!!
|
||||||
val substitution = RenamePsiElementProcessor.forElement(aClass).substituteElementToRename(aClass, null)
|
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")
|
if (method == null) throw IllegalStateException("Method with signature '$methodSignature' wasn't found in class $classFQN")
|
||||||
|
|
||||||
val substitution = RenamePsiElementProcessor.forElement(method).substituteElementToRename(method, null)
|
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 psiElement = segmentReference.resolve()!!
|
||||||
|
|
||||||
val substitution = RenamePsiElementProcessor.forElement(psiElement).substituteElementToRename(psiElement, null)
|
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)
|
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 {
|
protected fun getTestDirName(lowercaseFirstLetter : Boolean) : String {
|
||||||
val testName = getTestName(lowercaseFirstLetter)
|
val testName = getTestName(lowercaseFirstLetter)
|
||||||
return testName.substring(0, testName.indexOf('_'))
|
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$"));
|
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")
|
@TestMetadata("defaultObject/defaultObject.test")
|
||||||
public void testDefaultObject_DefaultObject() throws Exception {
|
public void testDefaultObject_DefaultObject() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/rename/defaultObject/defaultObject.test");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/rename/defaultObject/defaultObject.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user