Change Signature: Process implicit usages of data class 'componentN' functions

#KT-9393 Fixed
This commit is contained in:
Alexey Sedunov
2015-12-22 15:30:05 +03:00
parent 4fba251fba
commit 2895e2e86f
15 changed files with 275 additions and 13 deletions
@@ -45,6 +45,7 @@ public open class KtClass : KtClassOrObject {
_stub?.isInterface() ?: (findChildByType<PsiElement>(KtTokens.INTERFACE_KEYWORD) != null)
public fun isEnum(): Boolean = hasModifier(KtTokens.ENUM_KEYWORD)
public fun isData(): Boolean = hasModifier(KtTokens.DATA_KEYWORD)
public fun isSealed(): Boolean = hasModifier(KtTokens.SEALED_KEYWORD)
public fun isInner(): Boolean = hasModifier(KtTokens.INNER_KEYWORD)
@@ -36,6 +36,9 @@ public fun KtPsiFactory.createExpressionByPattern(pattern: String, vararg args:
public fun <TDeclaration : KtDeclaration> KtPsiFactory.createDeclarationByPattern(pattern: String, vararg args: Any): TDeclaration
= createByPattern(pattern, *args) { createDeclaration<TDeclaration>(it) }
public fun KtPsiFactory.createDestructuringDeclarationByPattern(pattern: String, vararg args: Any): KtDestructuringDeclaration
= createByPattern(pattern, *args) { createDestructuringDeclaration(it) }
private abstract class ArgumentType<T : Any>(val klass: Class<T>)
private class PlainTextArgumentType<T : Any>(klass: Class<T>, val toPlainText: (T) -> String) : ArgumentType<T>(klass)
@@ -315,6 +318,10 @@ public fun KtPsiFactory.buildDeclaration(build: BuilderByPattern<KtDeclaration>.
return buildByPattern({ pattern, args -> this.createDeclarationByPattern(pattern, *args) }, build)
}
public fun KtPsiFactory.buildDestructuringDeclaration(build: BuilderByPattern<KtDestructuringDeclaration>.() -> Unit): KtDestructuringDeclaration {
return buildByPattern({ pattern, args -> this.createDestructuringDeclarationByPattern(pattern, *args) }, build)
}
public fun <TElement> buildByPattern(factory: (String, Array<out Any>) -> TElement, build: BuilderByPattern<TElement>.() -> Unit): TElement {
val builder = BuilderByPattern<TElement>()
builder.build()
@@ -64,10 +64,7 @@ import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.psiUtil.getValueParameters
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
@@ -77,6 +74,7 @@ import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.*
@@ -268,17 +266,28 @@ class KotlinChangeSignatureUsageProcessor : ChangeSignatureUsageProcessor {
val newReceiverInfo = changeInfo.receiverParameterInfo
for (parameterInfo in changeInfo.newParameters) {
val isDataClass = functionPsi is KtPrimaryConstructor && (functionPsi.getContainingClassOrObject() as? KtClass)?.isData() ?: false
for ((i, parameterInfo) in changeInfo.newParameters.withIndex()) {
if (parameterInfo.oldIndex >= 0 && parameterInfo.oldIndex < oldParameters.size) {
val oldParam = oldParameters[parameterInfo.oldIndex]
val oldParamName = oldParam.name
if (parameterInfo == newReceiverInfo || (oldParamName != null && oldParamName != parameterInfo.name)) {
if (parameterInfo == newReceiverInfo ||
(oldParamName != null && oldParamName != parameterInfo.name) ||
isDataClass && i != parameterInfo.oldIndex) {
for (reference in ReferencesSearch.search(oldParam, oldParam.useScope)) {
val element = reference.element
if (isDataClass &&
element is KtSimpleNameExpression &&
(element.parent as? KtCallExpression)?.calleeExpression == element &&
element.getReferencedName() != parameterInfo.name &&
OperatorNameConventions.COMPONENT_REGEX.matches(element.getReferencedName())) {
result.add(KotlinDataClassComponentUsage(element, "component${i + 1}"))
}
// Usages in named arguments of the calls usage will be changed when the function call is changed
if ((element is KtSimpleNameExpression || element is KDocName) && element.parent !is KtValueArgumentName) {
else if ((element is KtSimpleNameExpression || element is KDocName) && element.parent !is KtValueArgumentName) {
result.add(KotlinParameterUsage(element as KtElement, parameterInfo, functionUsageInfo))
}
}
@@ -286,6 +295,15 @@ class KotlinChangeSignatureUsageProcessor : ChangeSignatureUsageProcessor {
}
}
if (isDataClass) {
(functionPsi as KtPrimaryConstructor).valueParameters.firstOrNull()?.let {
ReferencesSearch.search(it).mapNotNullTo(result) {
val destructuringDeclaration = it.element as? KtDestructuringDeclaration ?: return@mapNotNullTo null
KotlinComponentUsageInDestructuring(destructuringDeclaration)
}
}
}
if (functionPsi is KtFunction && newReceiverInfo != changeInfo.methodDescriptor.receiver) {
findOriginalReceiversUsages(functionUsageInfo, result, changeInfo)
}
@@ -0,0 +1,80 @@
/*
* 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.changeSignature.usages
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.idea.core.CollectingNameValidator
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator.Target
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeInfo
import org.jetbrains.kotlin.idea.refactoring.replaceListPsiAndKeepDelimiters
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.buildDestructuringDeclaration
import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange
import org.jetbrains.kotlin.utils.ifEmpty
class KotlinComponentUsageInDestructuring(element: KtDestructuringDeclaration) : KotlinUsageInfo<KtDestructuringDeclaration>(element) {
override fun processUsage(changeInfo: KotlinChangeInfo, element: KtDestructuringDeclaration, allUsages: Array<out UsageInfo>): Boolean {
if (!changeInfo.isParameterSetOrOrderChanged) return true
val currentEntries = element.entries
val newParameterInfos = changeInfo.getNonReceiverParameters()
val newDestructuring = KtPsiFactory(element).buildDestructuringDeclaration {
val lastIndex = newParameterInfos.indexOfLast { it.oldIndex in currentEntries.indices }
val nameValidator = CollectingNameValidator(filter = NewDeclarationNameValidator(element.parent, null, Target.VARIABLES))
appendFixedText("val (")
for (i in 0..lastIndex) {
if (i > 0) {
appendFixedText(", ")
}
val paramInfo = newParameterInfos[i]
val oldIndex = paramInfo.oldIndex
if (oldIndex >= 0 && oldIndex < currentEntries.size) {
appendChildRange(PsiChildRange.singleElement(currentEntries[oldIndex]))
}
else {
appendFixedText(KotlinNameSuggester.suggestNameByName(paramInfo.name, nameValidator))
}
}
appendFixedText(")")
}
replaceListPsiAndKeepDelimiters(
element,
newDestructuring,
{
apply {
val oldEntries = entries.ifEmpty { return@apply }
val firstOldEntry = oldEntries.first()
val lastOldEntry = oldEntries.last()
val newEntries = it.entries
if (newEntries.isNotEmpty()) {
addRangeBefore(newEntries.first(), newEntries.last(), firstOldEntry)
}
deleteChildRange(firstOldEntry, lastOldEntry)
}
},
{ entries }
)
return true
}
}
@@ -0,0 +1,32 @@
/*
* 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.changeSignature.usages
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeInfo
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
class KotlinDataClassComponentUsage(
calleeExpression: KtSimpleNameExpression,
private val newName: String
) : KotlinUsageInfo<KtSimpleNameExpression>(calleeExpression) {
override fun processUsage(changeInfo: KotlinChangeInfo, element: KtSimpleNameExpression, allUsages: Array<out UsageInfo>): Boolean {
element.replace(KtPsiFactory(element).createExpression(newName))
return true
}
}
@@ -722,6 +722,7 @@ fun PsiNamedElement.isInterfaceClass(): Boolean = this is KtClass && isInterface
fun <ListType : KtElement> replaceListPsiAndKeepDelimiters(
originalList: ListType,
newList: ListType,
@Suppress("UNCHECKED_CAST") listReplacer: ListType.(ListType) -> ListType = { replace(it) as ListType },
itemsFun: ListType.() -> List<KtElement>
): ListType {
originalList.children.takeWhile { it is PsiErrorElement }.forEach { it.delete() }
@@ -736,17 +737,13 @@ fun <ListType : KtElement> replaceListPsiAndKeepDelimiters(
oldParameters[i] = oldParameters[i].replace(newParameters[i]) as KtElement
}
@Suppress("UNCHECKED_CAST")
if (commonCount == 0) return originalList.replace(newList) as ListType
if (commonCount == 0) return originalList.listReplacer(newList)
if (oldCount > commonCount) {
originalList.deleteChildRange(oldParameters[commonCount - 1].nextSibling, oldParameters.last())
}
else if (newCount > commonCount) {
originalList.addRangeAfter(newParameters[commonCount - 1].nextSibling,
newList.lastChild.prevSibling,
PsiTreeUtil.skipSiblingsBackward(originalList.lastChild,
PsiWhiteSpace::class.java, PsiComment::class.java))
originalList.addRangeAfter(newParameters[commonCount - 1].nextSibling, newParameters.last(), oldParameters.last())
}
return originalList
@@ -0,0 +1,10 @@
data class <caret>X(val a: Int, val c: Int, val b: Int) {
}
fun test() {
val (a, c1, b) = X(1, 3, 2)
val aa = X(1, 3, 2).component1()
val bb = X(1, 3, 2).component3()
for ((a, c, b) in listOf(X(1, 3, 2))) {}
}
@@ -0,0 +1,10 @@
data class <caret>X(val a: Int, val b: Int) {
}
fun test() {
val (a, b) = X(1, 2)
val aa = X(1, 2).component1()
val bb = X(1, 2).component2()
for ((a, b) in listOf(X(1, 2))) {}
}
@@ -0,0 +1,11 @@
data class <caret>X(val d: Int, val c: Int, val e: Int) {
}
fun test() {
val () = X(4, 3, 5)
val aa = X(4, 3, 5).component1()
val bb = X(4, 3, 5).component2()
val cc = X(4, 3, 5).component2()
for (() in listOf(X(4, 3, 5))) {}
}
@@ -0,0 +1,11 @@
data class <caret>X(val a: Int, val b: Int, val c: Int) {
}
fun test() {
val (a, b) = X(1, 2, 3)
val aa = X(1, 2, 3).component1()
val bb = X(1, 2, 3).component2()
val cc = X(1, 2, 3).component3()
for ((a, b) in listOf(X(1, 2, 3))) {}
}
@@ -0,0 +1,11 @@
data class <caret>X(val a: Int, val c: Int) {
}
fun test() {
val (a, c) = X(1, 3)
val aa = X(1, 3).component1()
val bb = X(1, 3).component2()
val cc = X(1, 3).component2()
for ((a, c) in listOf(X(1, 3))) {}
}
@@ -0,0 +1,11 @@
data class <caret>X(val a: Int, val b: Int, val c: Int) {
}
fun test() {
val (a, b, c) = X(1, 2, 3)
val aa = X(1, 2, 3).component1()
val bb = X(1, 2, 3).component2()
val cc = X(1, 2, 3).component3()
for ((a, b, c) in listOf(X(1, 2, 3))) {}
}
@@ -0,0 +1,11 @@
data class <caret>X(val c: Int, val a: Int, val b: Int) {
}
fun test() {
val (c, a, b) = X(3, 1, 2)
val aa = X(3, 1, 2).component2()
val bb = X(3, 1, 2).component3()
val cc = X(3, 1, 2).component1()
for ((c, a, b) in listOf(X(3, 1, 2))) {}
}
@@ -0,0 +1,11 @@
data class <caret>X(val a: Int, val b: Int, val c: Int) {
}
fun test() {
val (a, b, c) = X(1, 2, 3)
val aa = X(1, 2, 3).component1()
val bb = X(1, 2, 3).component2()
val cc = X(1, 2, 3).component3()
for ((a, b, c) in listOf(X(1, 2, 3))) {}
}
@@ -903,4 +903,45 @@ class KotlinChangeSignatureTest : KotlinCodeInsightTestCase() {
fun testSetErrorReceiverType() = doTest { receiverParameterInfo!!.currentTypeInfo = KotlinTypeInfo(true, null, "XYZ") }
fun testSetErrorParameterType() = doTest { newParameters[1].currentTypeInfo = KotlinTypeInfo(true, null, "XYZ") }
fun testSwapDataClassParameters() = doTest {
swapParameters(0, 2)
swapParameters(1, 2)
}
fun testAddDataClassParameter() = doTest {
addParameter(KotlinParameterInfo(originalBaseFunctionDescriptor,
-1,
"c",
KotlinTypeInfo(false, BUILT_INS.intType),
null,
KtPsiFactory(project).createExpression("3"),
KotlinValVar.Val),
1)
}
fun testRemoveDataClassParameter() = doTest { removeParameter(1) }
fun testRemoveAllOriginalDataClassParameters() = doTest {
val psiFactory = KtPsiFactory(project)
swapParameters(1, 2)
setNewParameter(0,
KotlinParameterInfo(originalBaseFunctionDescriptor,
-1,
"d",
KotlinTypeInfo(false, BUILT_INS.intType),
null,
psiFactory.createExpression("4"),
KotlinValVar.Val))
setNewParameter(2,
KotlinParameterInfo(originalBaseFunctionDescriptor,
-1,
"e",
KotlinTypeInfo(false, BUILT_INS.intType),
null,
psiFactory.createExpression("5"),
KotlinValVar.Val))
}
}