Introduce Parameter: Suggest removing extension receiver which becomes unused after new parameter is added
This commit is contained in:
@@ -59,7 +59,9 @@ fun DeclarationDescriptorWithVisibility.isVisible(
|
||||
public fun compareDescriptors(project: Project, d1: DeclarationDescriptor?, d2: DeclarationDescriptor?): Boolean {
|
||||
if (d1 == d2) return true
|
||||
if (d1 == null || d2 == null) return false
|
||||
if (DescriptorToSourceUtilsIde.getAllDeclarations(project, d1) == DescriptorToSourceUtilsIde.getAllDeclarations(project, d2)) return true
|
||||
val declarations1 = DescriptorToSourceUtilsIde.getAllDeclarations(project, d1)
|
||||
val declarations2 = DescriptorToSourceUtilsIde.getAllDeclarations(project, d2)
|
||||
if (declarations1 == declarations2 && declarations1.isNotEmpty()) return true
|
||||
return DescriptorRenderer.FQ_NAMES_IN_TYPES.render(d1) == DescriptorRenderer.FQ_NAMES_IN_TYPES.render(d2)
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -34,7 +34,10 @@ public class JetMutableMethodDescriptor(val original: JetMethodDescriptor): JetM
|
||||
}
|
||||
|
||||
public fun removeParameter(index: Int) {
|
||||
parameters.remove(index)
|
||||
val paramInfo = parameters.remove(index)
|
||||
if (paramInfo == receiver) {
|
||||
receiver = null
|
||||
}
|
||||
}
|
||||
|
||||
public fun renameParameter(index: Int, newName: String) {
|
||||
|
||||
+21
-11
@@ -22,6 +22,7 @@ import com.intellij.refactoring.changeSignature.ParameterInfo
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.compareDescriptors
|
||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.JetFunctionDefinitionUsage
|
||||
import org.jetbrains.kotlin.idea.references.JetReference
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
@@ -53,21 +54,34 @@ public class JetParameterInfo(
|
||||
val file = defaultValueForCall?.getContainingFile() as? JetFile
|
||||
defaultValueParameterReferences =
|
||||
if (defaultValueForCall != null && file != null && (file.isPhysical() || file.analysisContext != null)) {
|
||||
val project = file.getProject()
|
||||
val map = LinkedHashMap<PsiReference, DeclarationDescriptor>()
|
||||
|
||||
defaultValueForCall!!.accept(
|
||||
object : JetTreeVisitorVoid() {
|
||||
private fun selfParameterOrNull(parameter: DeclarationDescriptor?): ValueParameterDescriptor? {
|
||||
return if (parameter is ValueParameterDescriptor &&
|
||||
parameter.getContainingDeclaration() == functionDescriptor) parameter else null
|
||||
compareDescriptors(project, parameter.getContainingDeclaration(), functionDescriptor)) {
|
||||
parameter
|
||||
} else null
|
||||
}
|
||||
|
||||
private fun selfReceiverOrNull(receiverDescriptor: DeclarationDescriptor?): DeclarationDescriptor? {
|
||||
if (compareDescriptors(project,
|
||||
receiverDescriptor,
|
||||
functionDescriptor.getExtensionReceiverParameter()?.getContainingDeclaration())) {
|
||||
return receiverDescriptor
|
||||
}
|
||||
if (compareDescriptors(project,
|
||||
receiverDescriptor,
|
||||
functionDescriptor.getDispatchReceiverParameter()?.getContainingDeclaration())) {
|
||||
return receiverDescriptor
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun selfReceiverOrNull(receiver: ThisReceiver?): DeclarationDescriptor? {
|
||||
return when (receiver) {
|
||||
functionDescriptor.getExtensionReceiverParameter()?.getValue(),
|
||||
functionDescriptor.getDispatchReceiverParameter()?.getValue()-> receiver?.getDeclarationDescriptor()
|
||||
else -> null
|
||||
}
|
||||
return selfReceiverOrNull(receiver?.getDeclarationDescriptor())
|
||||
}
|
||||
|
||||
private fun getRelevantDescriptor(
|
||||
@@ -86,11 +100,7 @@ public class JetParameterInfo(
|
||||
|
||||
val resolvedCall = expression.getResolvedCall(context) ?: return null
|
||||
(resolvedCall.getResultingDescriptor() as? ReceiverParameterDescriptor)?.let {
|
||||
return when (it) {
|
||||
functionDescriptor.getDispatchReceiverParameter(),
|
||||
functionDescriptor.getExtensionReceiverParameter() -> it
|
||||
else -> null
|
||||
}
|
||||
return if (selfReceiverOrNull(it.getContainingDeclaration()) != null) it else null
|
||||
}
|
||||
|
||||
selfReceiverOrNull(resolvedCall.getExtensionReceiver() as? ThisReceiver)?.let { return it }
|
||||
|
||||
+8
-1
@@ -168,6 +168,13 @@ public class KotlinInplaceParameterIntroducer(
|
||||
var addedRange: TextRange? = null
|
||||
val builder = StringBuilder()
|
||||
|
||||
(callable as? JetFunction)?.getReceiverTypeReference()?.let { receiverTypeRef ->
|
||||
builder.append(receiverTypeRef.getText()).append('.')
|
||||
if (!descriptor.withDefaultValue && receiverTypeRef in parametersToRemove) {
|
||||
rangesToRemove.add(TextRange(0, builder.length()))
|
||||
}
|
||||
}
|
||||
|
||||
builder.append(callable.getName())
|
||||
|
||||
val parameters = callable.getValueParameters()
|
||||
@@ -192,7 +199,7 @@ public class KotlinInplaceParameterIntroducer(
|
||||
if (parameter == addedParameter) {
|
||||
addedRange = range
|
||||
}
|
||||
else if (parameter in parametersToRemove && !descriptor.withDefaultValue) {
|
||||
else if (!descriptor.withDefaultValue && parameter in parametersToRemove) {
|
||||
rangesToRemove.add(range)
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -90,7 +90,7 @@ public class KotlinIntroduceParameterDialog private constructor(
|
||||
private val typeField = NameSuggestionsField(typeNameSuggestions, project, JetFileType.INSTANCE)
|
||||
private var replaceAllCheckBox: JCheckBox? = null
|
||||
private var defaultValueCheckBox: JCheckBox? = null
|
||||
private val removeParamsCheckBoxes = LinkedHashMap<JCheckBox, JetParameter>(descriptor.parametersToRemove.size())
|
||||
private val removeParamsCheckBoxes = LinkedHashMap<JCheckBox, JetElement>(descriptor.parametersToRemove.size())
|
||||
private var parameterTablePanel: KotlinParameterTablePanel? = null
|
||||
private val commandName = if (lambdaExtractionDescriptor != null) INTRODUCE_LAMBDA_PARAMETER else INTRODUCE_PARAMETER
|
||||
|
||||
@@ -218,7 +218,8 @@ public class KotlinIntroduceParameterDialog private constructor(
|
||||
}
|
||||
|
||||
for (parameter in descriptor.parametersToRemove) {
|
||||
val cb = NonFocusableCheckBox("Remove parameter '${parameter.getName()}' no longer used")
|
||||
val removeWhat = if (parameter is JetParameter) "parameter '${parameter.getName()}'" else "receiver"
|
||||
val cb = NonFocusableCheckBox("Remove $removeWhat no longer used")
|
||||
|
||||
removeParamsCheckBoxes[cb] = parameter
|
||||
cb.setSelected(true)
|
||||
|
||||
+56
-15
@@ -33,6 +33,7 @@ import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.usageView.UsageViewDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.JetNameSuggester
|
||||
@@ -58,8 +59,10 @@ import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils
|
||||
import java.util.Collections
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisReceiver
|
||||
import java.util.*
|
||||
import kotlin.test.fail
|
||||
|
||||
public data class IntroduceParameterDescriptor(
|
||||
@@ -70,9 +73,9 @@ public data class IntroduceParameterDescriptor(
|
||||
val newParameterTypeText: String,
|
||||
val newArgumentValue: JetExpression,
|
||||
val withDefaultValue: Boolean,
|
||||
val parametersUsages: Map<JetParameter, List<PsiReference>>,
|
||||
val parametersUsages: Map<JetElement, List<JetElement>>,
|
||||
val occurrencesToReplace: List<JetPsiRange>,
|
||||
val parametersToRemove: List<JetParameter> = getParametersToRemove(withDefaultValue, parametersUsages, occurrencesToReplace),
|
||||
val parametersToRemove: List<JetElement> = getParametersToRemove(withDefaultValue, parametersUsages, occurrencesToReplace),
|
||||
val occurrenceReplacer: IntroduceParameterDescriptor.(JetPsiRange) -> Unit = {}
|
||||
) {
|
||||
val originalOccurrence: JetPsiRange
|
||||
@@ -103,16 +106,16 @@ public data class IntroduceParameterDescriptor(
|
||||
|
||||
fun getParametersToRemove(
|
||||
withDefaultValue: Boolean,
|
||||
parametersUsages: Map<JetParameter, List<PsiReference>>,
|
||||
parametersUsages: Map<JetElement, List<JetElement>>,
|
||||
occurrencesToReplace: List<JetPsiRange>
|
||||
): List<JetParameter> {
|
||||
): List<JetElement> {
|
||||
if (withDefaultValue) return Collections.emptyList()
|
||||
|
||||
val occurrenceRanges = occurrencesToReplace.map { it.getTextRange() }
|
||||
return parametersUsages.entrySet()
|
||||
.filter {
|
||||
it.value.all { paramRef ->
|
||||
occurrenceRanges.any { occurrenceRange -> occurrenceRange.contains(paramRef.getElement().getTextRange()) }
|
||||
it.value.all { paramUsage ->
|
||||
occurrenceRanges.any { occurrenceRange -> occurrenceRange.contains(paramUsage.getTextRange()) }
|
||||
}
|
||||
}
|
||||
.map { it.key }
|
||||
@@ -127,7 +130,11 @@ fun IntroduceParameterDescriptor.performRefactoring() {
|
||||
val parameters = callable.getValueParameters()
|
||||
val withReceiver = methodDescriptor.receiver != null
|
||||
parametersToRemove
|
||||
.map { parameters.indexOf(it) + if (withReceiver) 1 else 0 }
|
||||
.map {
|
||||
if (it is JetParameter) {
|
||||
parameters.indexOf(it) + if (withReceiver) 1 else 0
|
||||
} else 0
|
||||
}
|
||||
.sortDescending()
|
||||
.forEach { methodDescriptor.removeParameter(it) }
|
||||
}
|
||||
@@ -227,7 +234,7 @@ public open class KotlinIntroduceParameterHandler(
|
||||
val nameValidator = JetNameValidatorImpl(body, null, JetNameValidatorImpl.Target.PROPERTIES)
|
||||
val suggestedNames = linkedSetOf(*JetNameSuggester.suggestNames(replacementType, nameValidator, "p"))
|
||||
|
||||
val parametersUsages = findInternalParameterUsages(targetParent)
|
||||
val parametersUsages = findInternalUsagesOfParametersAndReceiver(targetParent, functionDescriptor)
|
||||
|
||||
val forbiddenRanges =
|
||||
if (targetParent is JetClass) {
|
||||
@@ -331,17 +338,51 @@ public open class KotlinIntroduceParameterHandler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun findInternalParameterUsages(targetParent: JetNamedDeclaration): Map<JetParameter, List<PsiReference>> {
|
||||
return targetParent.getValueParameters()
|
||||
private fun findInternalUsagesOfParametersAndReceiver(
|
||||
targetParent: JetNamedDeclaration,
|
||||
targetDescriptor: FunctionDescriptor
|
||||
): Map<JetElement, List<JetElement>> {
|
||||
val usages = ArrayList<Pair<JetElement, List<JetElement>>>()
|
||||
targetParent.getValueParameters()
|
||||
.filter { !it.hasValOrVar() }
|
||||
.map {
|
||||
it to DefaultSearchHelper<JetParameter>()
|
||||
.newRequest(UsagesSearchTarget(element = it))
|
||||
.search()
|
||||
.toList()
|
||||
.map { it.getElement() as JetElement }
|
||||
}
|
||||
.filter { it.second.isNotEmpty() }
|
||||
.toMap()
|
||||
.filterTo(usages) { it.second.isNotEmpty() }
|
||||
val receiverTypeRef = (targetParent as? JetFunction)?.getReceiverTypeReference()
|
||||
if (receiverTypeRef != null) {
|
||||
val receiverUsages = ArrayList<JetElement>()
|
||||
targetParent.acceptChildren(
|
||||
object : JetTreeVisitorVoid() {
|
||||
override fun visitThisExpression(expression: JetThisExpression) {
|
||||
super.visitThisExpression(expression)
|
||||
|
||||
if (expression.getInstanceReference().getReference()?.resolve() == targetDescriptor) {
|
||||
receiverUsages.add(expression)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitJetElement(element: JetElement) {
|
||||
super.visitJetElement(element)
|
||||
|
||||
val bindingContext = element.analyze()
|
||||
val resolvedCall = element.getResolvedCall(bindingContext) ?: return
|
||||
|
||||
if ((resolvedCall.getExtensionReceiver() as? ThisReceiver)?.getDeclarationDescriptor() == targetDescriptor ||
|
||||
(resolvedCall.getDispatchReceiver() as? ThisReceiver)?.getDeclarationDescriptor() == targetDescriptor) {
|
||||
receiverUsages.add(resolvedCall.getCall().getCallElement())
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
if (receiverUsages.isNotEmpty()) {
|
||||
usages.add(receiverTypeRef to receiverUsages)
|
||||
}
|
||||
}
|
||||
return usages.toMap()
|
||||
}
|
||||
|
||||
trait KotlinIntroduceLambdaParameterHelper: KotlinIntroduceParameterHelper {
|
||||
@@ -376,7 +417,7 @@ public open class KotlinIntroduceLambdaParameterHandler(
|
||||
newParameterTypeText = "", // to be chosen in the dialog
|
||||
newArgumentValue = JetPsiFactory(project).createExpression("{}"), // substituted later
|
||||
withDefaultValue = false,
|
||||
parametersUsages = findInternalParameterUsages(callable),
|
||||
parametersUsages = findInternalUsagesOfParametersAndReceiver(callable, callableDescriptor),
|
||||
occurrencesToReplace = listOf(originalRange),
|
||||
parametersToRemove = listOf()
|
||||
)
|
||||
|
||||
+3
-3
@@ -2,19 +2,19 @@
|
||||
public inline fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f()
|
||||
|
||||
class A(val a: Int) {
|
||||
fun A.foo(i: Int): Int {
|
||||
fun foo(i: Int): Int {
|
||||
return i / 2
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val a1 = A(1)
|
||||
a1.foo(a + a1.a)
|
||||
foo(a + a1.a)
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val t = with(A(1)) {
|
||||
val a = A(2)
|
||||
a.foo(this.a + a.a)
|
||||
foo(this.a + a.a)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// WITH_RUNTIME
|
||||
// WITH_DEFAULT_VALUE: false
|
||||
class A(val n: Int) {
|
||||
fun foo(): Int {
|
||||
return <selection>n + 1</selection>
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A(1).foo()
|
||||
with(A(1)) {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// WITH_RUNTIME
|
||||
// WITH_DEFAULT_VALUE: false
|
||||
class A(val n: Int) {
|
||||
fun foo(i: Int): Int {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val a = A(1)
|
||||
a.foo(a.n + 1)
|
||||
with(A(1)) {
|
||||
foo(n + 1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// WITH_RUNTIME
|
||||
// WITH_DEFAULT_VALUE: false
|
||||
class A(val n: Int)
|
||||
|
||||
fun A.foo(): Int {
|
||||
return <selection>n + 1</selection>
|
||||
}
|
||||
|
||||
fun test() {
|
||||
A(1).foo()
|
||||
with(A(1)) {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// WITH_RUNTIME
|
||||
// WITH_DEFAULT_VALUE: false
|
||||
class A(val n: Int)
|
||||
|
||||
fun foo(i: Int): Int {
|
||||
return i
|
||||
}
|
||||
|
||||
fun test() {
|
||||
val a = A(1)
|
||||
foo(a.n + 1)
|
||||
with(A(1)) {
|
||||
foo(n + 1)
|
||||
}
|
||||
}
|
||||
+12
@@ -2569,6 +2569,18 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
|
||||
doIntroduceSimpleParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("unusedDispatchReceiver.kt")
|
||||
public void testUnusedDispatchReceiver() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/unusedDispatchReceiver.kt");
|
||||
doIntroduceSimpleParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("unusedExtensionReceiver.kt")
|
||||
public void testUnusedExtensionReceiver() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/unusedExtensionReceiver.kt");
|
||||
doIntroduceSimpleParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("valueAtCallSite.kt")
|
||||
public void testValueAtCallSite() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/valueAtCallSite.kt");
|
||||
|
||||
Reference in New Issue
Block a user