Automatic renaming properties, variables, parameters when renaming class.

#KT-4642 in progress
This commit is contained in:
Evgeny Gerashchenko
2015-04-13 20:13:14 +03:00
parent a20f1bdb6e
commit e6e5e28e1d
9 changed files with 252 additions and 26 deletions
+1
View File
@@ -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");