Intentions: Convert function type receiver to parameter
#KT-14246 Fixed
This commit is contained in:
@@ -338,6 +338,7 @@ These artifacts include extensions for the types available in the latter JDKs, s
|
|||||||
- [`KT-14044`](https://youtrack.jetbrains.com/issue/KT-14044) Fix exception on deleting unused declaration in IDEA 2016.3
|
- [`KT-14044`](https://youtrack.jetbrains.com/issue/KT-14044) Fix exception on deleting unused declaration in IDEA 2016.3
|
||||||
- [`KT-14019`](https://youtrack.jetbrains.com/issue/KT-14019) Create from Usage: Support generation of abstract members for superclasses
|
- [`KT-14019`](https://youtrack.jetbrains.com/issue/KT-14019) Create from Usage: Support generation of abstract members for superclasses
|
||||||
- [`KT-14246`](https://youtrack.jetbrains.com/issue/KT-14246) Intentions: Convert function type parameter to receiver
|
- [`KT-14246`](https://youtrack.jetbrains.com/issue/KT-14246) Intentions: Convert function type parameter to receiver
|
||||||
|
- [`KT-14246`](https://youtrack.jetbrains.com/issue/KT-14246) Intentions: Convert function type receiver to parameter
|
||||||
|
|
||||||
##### New features
|
##### New features
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ object EditCommaSeparatedListHelper {
|
|||||||
fun <TItem: KtElement> addItemAfter(list: KtElement, allItems: List<TItem>, item: TItem, anchor: TItem?, prefix: KtToken = KtTokens.LPAR): TItem {
|
fun <TItem: KtElement> addItemAfter(list: KtElement, allItems: List<TItem>, item: TItem, anchor: TItem?, prefix: KtToken = KtTokens.LPAR): TItem {
|
||||||
assert(anchor == null || anchor.parent == list)
|
assert(anchor == null || anchor.parent == list)
|
||||||
if (allItems.isEmpty()) {
|
if (allItems.isEmpty()) {
|
||||||
if (list.firstChild.node.elementType == prefix) {
|
if (list.firstChild?.node?.elementType == prefix) {
|
||||||
return list.addAfter(item, list.firstChild) as TItem
|
return list.addAfter(item, list.firstChild) as TItem
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -112,6 +112,10 @@ class KtPsiFactory(private val project: Project) {
|
|||||||
return (createType("A.() -> B").typeElement as KtFunctionType).receiver!!.apply { this.typeReference.replace(typeReference) }
|
return (createType("A.() -> B").typeElement as KtFunctionType).receiver!!.apply { this.typeReference.replace(typeReference) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createFunctionTypeParameter(typeReference: KtTypeReference): KtParameter {
|
||||||
|
return (createType("(A) -> B").typeElement as KtFunctionType).parameters.first().apply { this.typeReference!!.replace(typeReference) }
|
||||||
|
}
|
||||||
|
|
||||||
fun createTypeAlias(name: String, typeParameters: List<String>, typeElement: KtTypeElement): KtTypeAlias {
|
fun createTypeAlias(name: String, typeParameters: List<String>, typeElement: KtTypeElement): KtTypeAlias {
|
||||||
return createTypeAlias(name, typeParameters, "X").apply { getTypeReference()!!.replace(createType(typeElement)) }
|
return createTypeAlias(name, typeParameters, "X").apply { getTypeReference()!!.replace(createType(typeElement)) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -475,4 +475,25 @@ fun isTypeConstructorReference(e: PsiElement): Boolean {
|
|||||||
|
|
||||||
fun KtParameter.isPropertyParameter() = ownerFunction is KtPrimaryConstructor && hasValOrVar()
|
fun KtParameter.isPropertyParameter() = ownerFunction is KtPrimaryConstructor && hasValOrVar()
|
||||||
|
|
||||||
fun isDoubleColonReceiver(expression: KtExpression) = expression.getParentOfTypeAndBranch<KtDoubleColonExpression> { this.receiverExpression } != null
|
fun isDoubleColonReceiver(expression: KtExpression) = expression.getParentOfTypeAndBranch<KtDoubleColonExpression> { this.receiverExpression } != null
|
||||||
|
|
||||||
|
fun KtFunctionLiteral.getOrCreateParameterList(): KtParameterList {
|
||||||
|
valueParameterList?.let { return it }
|
||||||
|
|
||||||
|
val psiFactory = KtPsiFactory(this)
|
||||||
|
|
||||||
|
val anchor = lBrace
|
||||||
|
val newParameterList = addAfter(psiFactory.createLambdaParameterList("x"), anchor) as KtParameterList
|
||||||
|
newParameterList.removeParameter(0)
|
||||||
|
if (arrow == null) {
|
||||||
|
val whitespaceAndArrow = psiFactory.createWhitespaceAndArrow()
|
||||||
|
addRangeAfter(whitespaceAndArrow.first, whitespaceAndArrow.second, newParameterList)
|
||||||
|
}
|
||||||
|
return newParameterList
|
||||||
|
}
|
||||||
|
|
||||||
|
fun KtCallExpression.getOrCreateValueArgumentList(): KtValueArgumentList {
|
||||||
|
valueArgumentList?.let { return it }
|
||||||
|
return addAfter(KtPsiFactory(this).createCallArguments("()"),
|
||||||
|
typeArgumentList ?: calleeExpression) as KtValueArgumentList
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo(f: (i: <spot>Int</spot>, s: String) -> Boolean) = f(1, "2")
|
||||||
|
|
||||||
|
fun bar = foo { i, s -> s.length() > n }
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo(f: <spot>Int</spot>.(s: String) -> Boolean) = 1.f("2")
|
||||||
|
|
||||||
|
fun bar = foo { s -> s.length() > this }
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
This intention converts receiver of a function type used in a function parameter to its first parameter
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1461,6 +1461,11 @@
|
|||||||
<category>Kotlin</category>
|
<category>Kotlin</category>
|
||||||
</intentionAction>
|
</intentionAction>
|
||||||
|
|
||||||
|
<intentionAction>
|
||||||
|
<className>org.jetbrains.kotlin.idea.intentions.ConvertFunctionTypeReceiverToParameterIntention</className>
|
||||||
|
<category>Kotlin</category>
|
||||||
|
</intentionAction>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
|
||||||
displayName="Object literal can be converted to lambda"
|
displayName="Object literal can be converted to lambda"
|
||||||
groupName="Kotlin"
|
groupName="Kotlin"
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 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.intentions
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.usageView.UsageInfo
|
||||||
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
|
||||||
|
abstract class AbstractProcessableUsageInfo<out T : PsiElement, in D: Any>(element: T) : UsageInfo(element) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun getElement() = super.getElement() as T?
|
||||||
|
|
||||||
|
abstract fun process(data: D, elementsToShorten: MutableList<KtElement>)
|
||||||
|
}
|
||||||
+11
-15
@@ -26,7 +26,6 @@ import com.intellij.psi.search.LocalSearchScope
|
|||||||
import com.intellij.psi.search.searches.MethodReferencesSearch
|
import com.intellij.psi.search.searches.MethodReferencesSearch
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
import com.intellij.refactoring.util.RefactoringUIUtil
|
import com.intellij.refactoring.util.RefactoringUIUtil
|
||||||
import com.intellij.usageView.UsageInfo
|
|
||||||
import com.intellij.util.containers.MultiMap
|
import com.intellij.util.containers.MultiMap
|
||||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
@@ -60,14 +59,7 @@ class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntent
|
|||||||
KtTypeReference::class.java,
|
KtTypeReference::class.java,
|
||||||
"Convert function type parameter to receiver"
|
"Convert function type parameter to receiver"
|
||||||
) {
|
) {
|
||||||
abstract class AbstractUsageInfo<out T : PsiElement>(element: T) : UsageInfo(element) {
|
class FunctionDefinitionInfo(element: KtFunction) : AbstractProcessableUsageInfo<KtFunction, ConversionData>(element) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
override fun getElement() = super.getElement() as T?
|
|
||||||
|
|
||||||
abstract fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>)
|
|
||||||
}
|
|
||||||
|
|
||||||
class FunctionDefinitionInfo(element: KtFunction) : AbstractUsageInfo<KtFunction>(element) {
|
|
||||||
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
|
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
|
||||||
val function = element ?: return
|
val function = element ?: return
|
||||||
val functionParameter = function.valueParameters.getOrNull(data.functionParameterIndex) ?: return
|
val functionParameter = function.valueParameters.getOrNull(data.functionParameterIndex) ?: return
|
||||||
@@ -80,7 +72,7 @@ class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ParameterCallInfo(element: KtCallExpression) : AbstractUsageInfo<KtCallExpression>(element) {
|
class ParameterCallInfo(element: KtCallExpression) : AbstractProcessableUsageInfo<KtCallExpression, ConversionData>(element) {
|
||||||
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
|
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
|
||||||
val callExpression = element ?: return
|
val callExpression = element ?: return
|
||||||
val argumentList = callExpression.valueArgumentList ?: return
|
val argumentList = callExpression.valueArgumentList ?: return
|
||||||
@@ -91,7 +83,7 @@ class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InternalReferencePassInfo(element: KtSimpleNameExpression) : AbstractUsageInfo<KtSimpleNameExpression>(element) {
|
class InternalReferencePassInfo(element: KtSimpleNameExpression) : AbstractProcessableUsageInfo<KtSimpleNameExpression, ConversionData>(element) {
|
||||||
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
|
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
|
||||||
val expression = element ?: return
|
val expression = element ?: return
|
||||||
val lambdaType = data.lambdaType
|
val lambdaType = data.lambdaType
|
||||||
@@ -111,7 +103,7 @@ class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LambdaInfo(element: KtExpression) : AbstractUsageInfo<KtExpression>(element) {
|
class LambdaInfo(element: KtExpression) : AbstractProcessableUsageInfo<KtExpression, ConversionData>(element) {
|
||||||
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
|
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
|
||||||
val expression = element ?: return
|
val expression = element ?: return
|
||||||
val context = expression.analyze(BodyResolveMode.PARTIAL)
|
val context = expression.analyze(BodyResolveMode.PARTIAL)
|
||||||
@@ -185,7 +177,7 @@ class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntent
|
|||||||
|
|
||||||
val conflicts = MultiMap<PsiElement, String>()
|
val conflicts = MultiMap<PsiElement, String>()
|
||||||
|
|
||||||
val usages = ArrayList<AbstractUsageInfo<*>>()
|
val usages = ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>()
|
||||||
|
|
||||||
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
|
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
|
||||||
runReadAction {
|
runReadAction {
|
||||||
@@ -233,7 +225,11 @@ class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processExternalUsage(conflicts: MultiMap<PsiElement, String>, refElement: PsiElement, usages: java.util.ArrayList<AbstractUsageInfo<*>>) {
|
private fun processExternalUsage(
|
||||||
|
conflicts: MultiMap<PsiElement, String>,
|
||||||
|
refElement: PsiElement,
|
||||||
|
usages: ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>
|
||||||
|
) {
|
||||||
val callElement = refElement.getParentOfTypeAndBranch<KtCallElement> { calleeExpression }
|
val callElement = refElement.getParentOfTypeAndBranch<KtCallElement> { calleeExpression }
|
||||||
if (callElement != null) {
|
if (callElement != null) {
|
||||||
val context = callElement.analyze(BodyResolveMode.PARTIAL)
|
val context = callElement.analyze(BodyResolveMode.PARTIAL)
|
||||||
@@ -294,7 +290,7 @@ class ConvertFunctionTypeParameterToReceiverIntention : SelfTargetingRangeIntent
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processInternalUsages(callable: KtFunction, usages: ArrayList<AbstractUsageInfo<*>>) {
|
private fun processInternalUsages(callable: KtFunction, usages: ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>) {
|
||||||
val body = when (callable) {
|
val body = when (callable) {
|
||||||
is KtConstructor<*> -> callable.containingClassOrObject?.getBody()
|
is KtConstructor<*> -> callable.containingClassOrObject?.getBody()
|
||||||
else -> callable.bodyExpression
|
else -> callable.bodyExpression
|
||||||
|
|||||||
+224
@@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 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.intentions
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.search.LocalSearchScope
|
||||||
|
import com.intellij.psi.search.searches.MethodReferencesSearch
|
||||||
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
|
import com.intellij.refactoring.util.RefactoringUIUtil
|
||||||
|
import com.intellij.util.containers.MultiMap
|
||||||
|
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||||
|
import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType
|
||||||
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||||
|
import org.jetbrains.kotlin.idea.core.*
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.CallableRefactoring
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.getAffectedCallables
|
||||||
|
import org.jetbrains.kotlin.idea.references.KtSimpleReference
|
||||||
|
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
|
import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.bindingContextUtil.getAbbreviatedTypeOrType
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getArgumentByParameterIndex
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
|
||||||
|
class ConvertFunctionTypeReceiverToParameterIntention : SelfTargetingRangeIntention<KtTypeReference>(
|
||||||
|
KtTypeReference::class.java,
|
||||||
|
"Convert function type receiver to parameter"
|
||||||
|
) {
|
||||||
|
class ConversionData(
|
||||||
|
val functionParameterIndex: Int,
|
||||||
|
val lambdaReceiverType: KotlinType,
|
||||||
|
val function: KtFunction
|
||||||
|
) {
|
||||||
|
val functionDescriptor by lazy { function.resolveToDescriptor() as FunctionDescriptor }
|
||||||
|
}
|
||||||
|
|
||||||
|
class FunctionDefinitionInfo(element: KtFunction) : AbstractProcessableUsageInfo<KtFunction, ConversionData>(element) {
|
||||||
|
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
|
||||||
|
val function = element as? KtFunction ?: return
|
||||||
|
val functionParameter = function.valueParameters.getOrNull(data.functionParameterIndex) ?: return
|
||||||
|
val functionType = functionParameter.typeReference?.typeElement as? KtFunctionType ?: return
|
||||||
|
val functionTypeParameterList = functionType.parameterList ?: return
|
||||||
|
val functionTypeReceiver = functionType.receiverTypeReference ?: return
|
||||||
|
val parameterToAdd = KtPsiFactory(project).createFunctionTypeParameter(functionTypeReceiver)
|
||||||
|
functionTypeParameterList.addParameterBefore(parameterToAdd, functionTypeParameterList.parameters.firstOrNull())
|
||||||
|
functionType.setReceiverTypeReference(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParameterCallInfo(element: KtCallExpression) : AbstractProcessableUsageInfo<KtCallExpression, ConversionData>(element) {
|
||||||
|
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
|
||||||
|
val callExpression = element ?: return
|
||||||
|
val qualifiedExpression = callExpression.getQualifiedExpressionForSelector() ?: return
|
||||||
|
val receiverExpression = qualifiedExpression.receiverExpression
|
||||||
|
val argumentList = callExpression.getOrCreateValueArgumentList()
|
||||||
|
argumentList.addArgumentBefore(KtPsiFactory(project).createArgument(receiverExpression), argumentList.arguments.firstOrNull())
|
||||||
|
qualifiedExpression.replace(callExpression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LambdaInfo(element: KtLambdaExpression) : AbstractProcessableUsageInfo<KtLambdaExpression, ConversionData>(element) {
|
||||||
|
override fun process(data: ConversionData, elementsToShorten: MutableList<KtElement>) {
|
||||||
|
val lambda = element?.functionLiteral ?: return
|
||||||
|
val context = lambda.analyze(BodyResolveMode.PARTIAL)
|
||||||
|
|
||||||
|
val psiFactory = KtPsiFactory(project)
|
||||||
|
|
||||||
|
val validator = CollectingNameValidator(
|
||||||
|
lambda.valueParameters.mapNotNull { it.name },
|
||||||
|
NewDeclarationNameValidator(lambda.bodyExpression!!, null, NewDeclarationNameValidator.Target.VARIABLES)
|
||||||
|
)
|
||||||
|
val newParameterName = KotlinNameSuggester.suggestNamesByType(data.lambdaReceiverType, validator, "p").first()
|
||||||
|
val newParameterRefExpression = psiFactory.createExpression(newParameterName)
|
||||||
|
|
||||||
|
lambda.forEachDescendantOfType<KtThisExpression> {
|
||||||
|
val thisTarget = context[BindingContext.REFERENCE_TARGET, it.instanceReference] ?: return@forEachDescendantOfType
|
||||||
|
if (DescriptorToSourceUtilsIde.getAnyDeclaration(project, thisTarget) == lambda) {
|
||||||
|
it.replace(newParameterRefExpression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val lambdaParameterList = lambda.getOrCreateParameterList()
|
||||||
|
val parameterToAdd = psiFactory.createLambdaParameterList(newParameterName).parameters.first()
|
||||||
|
lambdaParameterList.addParameterBefore(parameterToAdd, lambdaParameterList.parameters.firstOrNull())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class Converter(
|
||||||
|
private val data: ConversionData
|
||||||
|
) : CallableRefactoring<CallableDescriptor>(data.function.project, data.functionDescriptor, text) {
|
||||||
|
override fun performRefactoring(descriptorsForChange: Collection<CallableDescriptor>) {
|
||||||
|
val callables = getAffectedCallables(project, descriptorsForChange)
|
||||||
|
|
||||||
|
val conflicts = MultiMap<PsiElement, String>()
|
||||||
|
|
||||||
|
val usages = ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>()
|
||||||
|
|
||||||
|
project.runSynchronouslyWithProgress("Looking for usages and conflicts...", true) {
|
||||||
|
runReadAction {
|
||||||
|
val progressStep = 1.0/callables.size
|
||||||
|
for ((i, callable) in callables.withIndex()) {
|
||||||
|
ProgressManager.getInstance().progressIndicator.fraction = (i + 1) * progressStep
|
||||||
|
|
||||||
|
if (callable !is KtFunction) continue
|
||||||
|
|
||||||
|
if (!checkModifiable(callable)) {
|
||||||
|
val renderedCallable = RefactoringUIUtil.getDescription(callable, true).capitalize()
|
||||||
|
conflicts.putValue(callable, "Can't modify $renderedCallable")
|
||||||
|
}
|
||||||
|
|
||||||
|
val references = callable.toLightMethods().flatMapTo(LinkedHashSet()) { MethodReferencesSearch.search(it) }
|
||||||
|
for (ref in references) {
|
||||||
|
if (ref !is KtSimpleReference<*>) continue
|
||||||
|
processExternalUsage(ref, usages)
|
||||||
|
}
|
||||||
|
|
||||||
|
usages += FunctionDefinitionInfo(callable)
|
||||||
|
|
||||||
|
processInternalUsages(callable, usages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project.checkConflictsInteractively(conflicts) {
|
||||||
|
project.executeWriteCommand(text) {
|
||||||
|
val elementsToShorten = ArrayList<KtElement>()
|
||||||
|
usages.forEach { it.process(data, elementsToShorten) }
|
||||||
|
ShortenReferences.DEFAULT.process(elementsToShorten)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processExternalUsage(ref: KtSimpleReference<*>, usages: java.util.ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>) {
|
||||||
|
val callElement = ref.element.getParentOfTypeAndBranch<KtCallElement> { calleeExpression } ?: return
|
||||||
|
val context = callElement.analyze(BodyResolveMode.PARTIAL)
|
||||||
|
val expressionToProcess = callElement
|
||||||
|
.getArgumentByParameterIndex(data.functionParameterIndex, context)
|
||||||
|
.singleOrNull()
|
||||||
|
?.getArgumentExpression()
|
||||||
|
?.let { KtPsiUtil.safeDeparenthesize(it) }
|
||||||
|
?: return
|
||||||
|
if (expressionToProcess is KtLambdaExpression) {
|
||||||
|
usages += LambdaInfo(expressionToProcess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processInternalUsages(callable: KtFunction, usages: java.util.ArrayList<AbstractProcessableUsageInfo<*, ConversionData>>) {
|
||||||
|
val body = when (callable) {
|
||||||
|
is KtConstructor<*> -> callable.containingClassOrObject?.getBody()
|
||||||
|
else -> callable.bodyExpression
|
||||||
|
}
|
||||||
|
if (body != null) {
|
||||||
|
val functionParameter = callable.valueParameters.getOrNull(data.functionParameterIndex) ?: return
|
||||||
|
for (ref in ReferencesSearch.search(functionParameter, LocalSearchScope(body))) {
|
||||||
|
val element = ref.element as? KtSimpleNameExpression ?: continue
|
||||||
|
val callExpression = element.getParentOfTypeAndBranch<KtCallExpression> { calleeExpression } ?: continue
|
||||||
|
usages += ParameterCallInfo(callExpression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtTypeReference.getConversionData(): ConversionData? {
|
||||||
|
val functionTypeReceiver = parent as? KtFunctionTypeReceiver ?: return null
|
||||||
|
val functionType = functionTypeReceiver.parent as? KtFunctionType ?: return null
|
||||||
|
val lambdaReceiverType = functionType
|
||||||
|
.getAbbreviatedTypeOrType(functionType.analyze(BodyResolveMode.PARTIAL))
|
||||||
|
?.getReceiverTypeFromFunctionType()
|
||||||
|
?: return null
|
||||||
|
val containingParameter = (functionType.parent as? KtTypeReference)?.parent as? KtParameter ?: return null
|
||||||
|
val ownerFunction = containingParameter.ownerFunction ?: return null
|
||||||
|
val functionParameterIndex = ownerFunction.valueParameters.indexOf(containingParameter)
|
||||||
|
return ConversionData(functionParameterIndex, lambdaReceiverType, ownerFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startInWriteAction(): Boolean = false
|
||||||
|
|
||||||
|
override fun applicabilityRange(element: KtTypeReference): TextRange? {
|
||||||
|
val data = element.getConversionData() ?: return null
|
||||||
|
|
||||||
|
val elementBefore = data.function.valueParameters[data.functionParameterIndex].typeReference!!.typeElement as KtFunctionType
|
||||||
|
val elementAfter = elementBefore.copied().apply {
|
||||||
|
parameterList!!.addParameterBefore(
|
||||||
|
KtPsiFactory(element).createFunctionTypeParameter(element),
|
||||||
|
parameterList!!.parameters.firstOrNull()
|
||||||
|
)
|
||||||
|
setReceiverTypeReference(null)
|
||||||
|
}
|
||||||
|
text = "Convert '${elementBefore.text}' to '${elementAfter.text}'"
|
||||||
|
|
||||||
|
return element.textRange
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyTo(element: KtTypeReference, editor: Editor?) {
|
||||||
|
element.getConversionData()?.let { Converter(it).run() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.jetbrains.kotlin.idea.intentions.ConvertFunctionTypeReceiverToParameterIntention
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
fun foo(f: <caret>Int.(Boolean) -> String) {
|
||||||
|
1.f(false)
|
||||||
|
bar(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(f: (Int, Boolean) -> String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
|
||||||
|
|
||||||
|
fun baz(f: (Int, Boolean) -> String) {
|
||||||
|
fun g(i: Int, b: Boolean) = ""
|
||||||
|
|
||||||
|
foo(f)
|
||||||
|
|
||||||
|
foo(::g)
|
||||||
|
|
||||||
|
foo(lambda())
|
||||||
|
|
||||||
|
foo { b -> "${this + 1} $b" }
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
fun foo(f: <caret>(Int, Boolean) -> String) {
|
||||||
|
f(1, false)
|
||||||
|
bar(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(f: (Int, Boolean) -> String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
|
||||||
|
|
||||||
|
fun baz(f: (Int, Boolean) -> String) {
|
||||||
|
fun g(i: Int, b: Boolean) = ""
|
||||||
|
|
||||||
|
foo(f)
|
||||||
|
|
||||||
|
foo(::g)
|
||||||
|
|
||||||
|
foo(lambda())
|
||||||
|
|
||||||
|
foo { i, b -> "${i + 1} $b" }
|
||||||
|
}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
// IS_APPLICABLE: false
|
||||||
|
fun foo(f: Int.(Boolean) -> String): Int.(<caret>Boolean) -> String = f
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// IS_APPLICABLE: false
|
||||||
|
fun foo(f: Int.(<caret>Boolean) -> String) {
|
||||||
|
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo(f: <caret>Int.(() -> Unit) -> String) {
|
||||||
|
1.f {}
|
||||||
|
}
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
fun foo(f: <caret>(Int, () -> Unit) -> String) {
|
||||||
|
f(1) {}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
fun foo(f: <caret>Int.() -> String) {
|
||||||
|
1.f()
|
||||||
|
bar(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(f: (Int) -> String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lambda(): (Int) -> String = { i -> "$i"}
|
||||||
|
|
||||||
|
fun baz(f: (Int) -> String) {
|
||||||
|
fun g(i: Int) = ""
|
||||||
|
|
||||||
|
foo(f)
|
||||||
|
|
||||||
|
foo(::g)
|
||||||
|
|
||||||
|
foo(lambda())
|
||||||
|
|
||||||
|
foo { "${this + 1}" }
|
||||||
|
}
|
||||||
Vendored
+22
@@ -0,0 +1,22 @@
|
|||||||
|
fun foo(f: <caret>(Int) -> String) {
|
||||||
|
f(1)
|
||||||
|
bar(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(f: (Int) -> String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lambda(): (Int) -> String = { i -> "$i"}
|
||||||
|
|
||||||
|
fun baz(f: (Int) -> String) {
|
||||||
|
fun g(i: Int) = ""
|
||||||
|
|
||||||
|
foo(f)
|
||||||
|
|
||||||
|
foo(::g)
|
||||||
|
|
||||||
|
foo(lambda())
|
||||||
|
|
||||||
|
foo { i -> "${i + 1}" }
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
open class Foo(f: <caret>Int.(Boolean) -> String) {
|
||||||
|
constructor(a: Int, f: (Int, Boolean) -> String) : this(f)
|
||||||
|
constructor(a: Int) : this(::g)
|
||||||
|
constructor(a: Int, b: Int) : this(lambda())
|
||||||
|
constructor(a: Int, b: Int, c: Int) : this({ b -> "${this + 1} $b" })
|
||||||
|
|
||||||
|
init {
|
||||||
|
1.f(false)
|
||||||
|
bar(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(f: (Int, Boolean) -> String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
|
||||||
|
|
||||||
|
fun g(i: Int, b: Boolean) = ""
|
||||||
|
|
||||||
|
fun baz(f: (Int, Boolean) -> String) {
|
||||||
|
Foo(f)
|
||||||
|
|
||||||
|
Foo(::g)
|
||||||
|
|
||||||
|
Foo(lambda())
|
||||||
|
|
||||||
|
Foo { b -> "${this + 1} $b" }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Baz1(f: (Int, Boolean) -> String) : Foo(f)
|
||||||
|
class Baz2 : Foo(::g)
|
||||||
|
class Baz3 : Foo(lambda())
|
||||||
|
class Baz4 : Foo({ b -> "${this + 1} $b" })
|
||||||
|
|
||||||
|
class Baz5 : Foo {
|
||||||
|
constructor(f: (Int, Boolean) -> String) : super(f)
|
||||||
|
constructor(a: Int) : super(::g)
|
||||||
|
constructor(a: Int, b: Int) : super(lambda())
|
||||||
|
constructor(a: Int, b: Int, c: Int) : super({ b -> "${this + 1} $b" })
|
||||||
|
}
|
||||||
Vendored
+41
@@ -0,0 +1,41 @@
|
|||||||
|
open class Foo(f: <caret>(Int, Boolean) -> String) {
|
||||||
|
constructor(a: Int, f: (Int, Boolean) -> String) : this(f)
|
||||||
|
constructor(a: Int) : this(::g)
|
||||||
|
constructor(a: Int, b: Int) : this(lambda())
|
||||||
|
constructor(a: Int, b: Int, c: Int) : this({ i, b -> "${i + 1} $b" })
|
||||||
|
|
||||||
|
init {
|
||||||
|
f(1, false)
|
||||||
|
bar(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(f: (Int, Boolean) -> String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
|
||||||
|
|
||||||
|
fun g(i: Int, b: Boolean) = ""
|
||||||
|
|
||||||
|
fun baz(f: (Int, Boolean) -> String) {
|
||||||
|
Foo(f)
|
||||||
|
|
||||||
|
Foo(::g)
|
||||||
|
|
||||||
|
Foo(lambda())
|
||||||
|
|
||||||
|
Foo { i, b -> "${i + 1} $b" }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Baz1(f: (Int, Boolean) -> String) : Foo(f)
|
||||||
|
class Baz2 : Foo(::g)
|
||||||
|
class Baz3 : Foo(lambda())
|
||||||
|
class Baz4 : Foo({ i, b -> "${i + 1} $b" })
|
||||||
|
|
||||||
|
class Baz5 : Foo {
|
||||||
|
constructor(f: (Int, Boolean) -> String) : super(f)
|
||||||
|
constructor(a: Int) : super(::g)
|
||||||
|
constructor(a: Int, b: Int) : super(lambda())
|
||||||
|
constructor(a: Int, b: Int, c: Int) : super({ i, b -> "${i + 1} $b" })
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
open class Foo {
|
||||||
|
constructor(f: <caret>Int.(Boolean) -> String) {
|
||||||
|
1.f(false)
|
||||||
|
bar(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(a: Int, f: (Int, Boolean) -> String) : this(f)
|
||||||
|
constructor(a: Int) : this(::g)
|
||||||
|
constructor(a: Int, b: Int) : this(lambda())
|
||||||
|
constructor(a: Int, b: Int, c: Int) : this({ b -> "${this + 1} $b" })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(f: (Int, Boolean) -> String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
|
||||||
|
|
||||||
|
fun g(i: Int, b: Boolean) = ""
|
||||||
|
|
||||||
|
fun baz(f: (Int, Boolean) -> String) {
|
||||||
|
Foo(f)
|
||||||
|
|
||||||
|
Foo(::g)
|
||||||
|
|
||||||
|
Foo(lambda())
|
||||||
|
|
||||||
|
Foo { b -> "${this + 1} $b" }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Baz1(f: (Int, Boolean) -> String) : Foo(f)
|
||||||
|
class Baz2 : Foo(::g)
|
||||||
|
class Baz3 : Foo(lambda())
|
||||||
|
class Baz4 : Foo({ b -> "${this + 1} $b" })
|
||||||
|
|
||||||
|
class Baz5 : Foo {
|
||||||
|
constructor(f: (Int, Boolean) -> String) : super(f)
|
||||||
|
constructor(a: Int) : super(::g)
|
||||||
|
constructor(a: Int, b: Int) : super(lambda())
|
||||||
|
constructor(a: Int, b: Int, c: Int) : super({ b -> "${this + 1} $b" })
|
||||||
|
}
|
||||||
Vendored
+41
@@ -0,0 +1,41 @@
|
|||||||
|
open class Foo {
|
||||||
|
constructor(f: <caret>(Int, Boolean) -> String) {
|
||||||
|
f(1, false)
|
||||||
|
bar(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(a: Int, f: (Int, Boolean) -> String) : this(f)
|
||||||
|
constructor(a: Int) : this(::g)
|
||||||
|
constructor(a: Int, b: Int) : this(lambda())
|
||||||
|
constructor(a: Int, b: Int, c: Int) : this({ i, b -> "${i + 1} $b" })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(f: (Int, Boolean) -> String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lambda(): (Int, Boolean) -> String = { i, b -> "$i $b"}
|
||||||
|
|
||||||
|
fun g(i: Int, b: Boolean) = ""
|
||||||
|
|
||||||
|
fun baz(f: (Int, Boolean) -> String) {
|
||||||
|
Foo(f)
|
||||||
|
|
||||||
|
Foo(::g)
|
||||||
|
|
||||||
|
Foo(lambda())
|
||||||
|
|
||||||
|
Foo { i, b -> "${i + 1} $b" }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Baz1(f: (Int, Boolean) -> String) : Foo(f)
|
||||||
|
class Baz2 : Foo(::g)
|
||||||
|
class Baz3 : Foo(lambda())
|
||||||
|
class Baz4 : Foo({ i, b -> "${i + 1} $b" })
|
||||||
|
|
||||||
|
class Baz5 : Foo {
|
||||||
|
constructor(f: (Int, Boolean) -> String) : super(f)
|
||||||
|
constructor(a: Int) : super(::g)
|
||||||
|
constructor(a: Int, b: Int) : super(lambda())
|
||||||
|
constructor(a: Int, b: Int, c: Int) : super({ i, b -> "${i + 1} $b" })
|
||||||
|
}
|
||||||
@@ -4101,6 +4101,57 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("idea/testData/intentions/convertFunctionTypeReceiverToParameter")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class ConvertFunctionTypeReceiverToParameter extends AbstractIntentionTest {
|
||||||
|
public void testAllFilesPresentInConvertFunctionTypeReceiverToParameter() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertFunctionTypeReceiverToParameter"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("function.kt")
|
||||||
|
public void testFunction() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeReceiverToParameter/function.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("notInFunctionParameter.kt")
|
||||||
|
public void testNotInFunctionParameter() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeReceiverToParameter/notInFunctionParameter.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("notOnReceiver.kt")
|
||||||
|
public void testNotOnReceiver() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeReceiverToParameter/notOnReceiver.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("onlyLambdaArgument.kt")
|
||||||
|
public void testOnlyLambdaArgument() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeReceiverToParameter/onlyLambdaArgument.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("parameterlessFunction.kt")
|
||||||
|
public void testParameterlessFunction() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeReceiverToParameter/parameterlessFunction.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("primaryConstructor.kt")
|
||||||
|
public void testPrimaryConstructor() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeReceiverToParameter/primaryConstructor.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("secondaryConstructor.kt")
|
||||||
|
public void testSecondaryConstructor() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertFunctionTypeReceiverToParameter/secondaryConstructor.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("idea/testData/intentions/convertIfWithThrowToAssert")
|
@TestMetadata("idea/testData/intentions/convertIfWithThrowToAssert")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user