Convert Object Literal to Lambda Intention: Move lambda out of parentheses when possible. Fix after-template
#KT-10778 Fixed
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
SwingUtilities { println("a") }
|
||||
SwingUtilities.invokeLater { println("a") }
|
||||
@@ -106,10 +106,10 @@ class RedundantSamConstructorInspection : AbstractKotlinInspection() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun replaceSamConstructorCall(callExpression: KtCallExpression): KtExpression {
|
||||
fun replaceSamConstructorCall(callExpression: KtCallExpression): KtLambdaExpression {
|
||||
val functionalArgument = callExpression.samConstructorValueArgument()?.getArgumentExpression()
|
||||
?: throw AssertionError("SAM-constructor should have a FunctionLiteralExpression as single argument: ${callExpression.getElementTextWithContext()}")
|
||||
return callExpression.getQualifiedExpressionForSelectorOrThis().replace(functionalArgument) as KtExpression
|
||||
return callExpression.getQualifiedExpressionForSelectorOrThis().replace(functionalArgument) as KtLambdaExpression
|
||||
}
|
||||
|
||||
private fun canBeReplaced(parentCall: KtCallExpression, samConstructorArguments: List<KtValueArgument>): Boolean {
|
||||
|
||||
+36
-25
@@ -21,39 +21,50 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.moveFunctionLiteralOutsideParentheses
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containsInside
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.psi.unpackFunctionLiteral
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
|
||||
class MoveLambdaOutsideParenthesesIntention : SelfTargetingIntention<KtCallExpression>(KtCallExpression::class.java, "Move lambda argument out of parentheses") {
|
||||
override fun isApplicableTo(element: KtCallExpression, caretOffset: Int): Boolean {
|
||||
if (element.lambdaArguments.isNotEmpty()) return false
|
||||
val argument = element.valueArguments.lastOrNull() ?: return false
|
||||
val expression = argument.getArgumentExpression() ?: return false
|
||||
val functionLiteral = expression.unpackFunctionLiteral() ?: return false
|
||||
|
||||
val callee = element.calleeExpression
|
||||
if (callee is KtNameReferenceExpression) {
|
||||
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
|
||||
val targets = bindingContext[BindingContext.REFERENCE_TARGET, callee]?.let { listOf(it) }
|
||||
?: bindingContext[BindingContext.AMBIGUOUS_REFERENCE_TARGET, callee]
|
||||
?: listOf()
|
||||
val candidates = targets.filterIsInstance<FunctionDescriptor>()
|
||||
// if there are functions among candidates but none of them have last function parameter then not show the intention
|
||||
if (candidates.isNotEmpty() && candidates.none {
|
||||
val lastParameter = it.valueParameters.lastOrNull()
|
||||
lastParameter != null && KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(lastParameter.type)
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
companion object {
|
||||
private fun getLambdaExpression(callExpression: KtCallExpression): KtLambdaExpression? {
|
||||
if (callExpression.lambdaArguments.isNotEmpty()) return null
|
||||
return callExpression.valueArguments.lastOrNull()?.getArgumentExpression()?.unpackFunctionLiteral()
|
||||
}
|
||||
|
||||
if (caretOffset < argument.asElement().startOffset) return false
|
||||
val bodyRange = functionLiteral.bodyExpression?.textRange ?: return true
|
||||
fun canMove(element: KtCallExpression): Boolean {
|
||||
if (getLambdaExpression(element) == null) return false
|
||||
|
||||
val callee = element.calleeExpression
|
||||
if (callee is KtNameReferenceExpression) {
|
||||
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
|
||||
val targets = bindingContext[BindingContext.REFERENCE_TARGET, callee]?.let { listOf(it) }
|
||||
?: bindingContext[BindingContext.AMBIGUOUS_REFERENCE_TARGET, callee]
|
||||
?: listOf()
|
||||
val candidates = targets.filterIsInstance<FunctionDescriptor>()
|
||||
// if there are functions among candidates but none of them have last function parameter then not show the intention
|
||||
if (candidates.isNotEmpty() && candidates.none {
|
||||
val lastParameter = it.valueParameters.lastOrNull()
|
||||
lastParameter != null && KotlinBuiltIns.isExactFunctionOrExtensionFunctionType(lastParameter.type)
|
||||
}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
override fun isApplicableTo(element: KtCallExpression, caretOffset: Int): Boolean {
|
||||
if (!canMove(element)) return false
|
||||
|
||||
val lambdaExpression = getLambdaExpression(element) ?: return false
|
||||
val argument = lambdaExpression.getStrictParentOfType<KtValueArgument>() ?: return false
|
||||
if (caretOffset < argument.startOffset) return false
|
||||
val bodyRange = lambdaExpression.bodyExpression?.textRange ?: return true
|
||||
return !bodyRange.containsInside(caretOffset)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.moveFunctionLiteralOutsideParentheses
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
||||
import org.jetbrains.kotlin.idea.inspections.RedundantSamConstructorInspection
|
||||
@@ -135,6 +136,9 @@ class ObjectLiteralToLambdaIntention : SelfTargetingRangeIntention<KtObjectLiter
|
||||
?.parent as? KtCallExpression
|
||||
if (parentCall != null && RedundantSamConstructorInspection.samConstructorCallsToBeConverted(parentCall).singleOrNull() == callExpression) {
|
||||
RedundantSamConstructorInspection.replaceSamConstructorCall(callExpression)
|
||||
if (MoveLambdaOutsideParenthesesIntention.canMove(parentCall)) {
|
||||
parentCall.moveFunctionLiteralOutsideParentheses()
|
||||
}
|
||||
}
|
||||
else {
|
||||
ShortenReferences.DEFAULT.process(replaced.getContainingKtFile(), replaced.startOffset, callee.endOffset)
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
fun bar() {
|
||||
SwingUtilities.invokeLater({ throw UnsupportedOperationException() })
|
||||
SwingUtilities.invokeLater { throw UnsupportedOperationException() }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package source;
|
||||
|
||||
public class J {
|
||||
static void foo(Runnable r, int n) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package source
|
||||
|
||||
fun bar() {
|
||||
J.foo({ println("a") }, 1)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package source;
|
||||
|
||||
public class J {
|
||||
static void foo(Runnable r, int n) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package source
|
||||
|
||||
fun bar() {
|
||||
J.foo(<caret>object : Runnable {
|
||||
override fun run() {
|
||||
println("a")
|
||||
}
|
||||
}, 1)
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"mainFile": "source/test.kt",
|
||||
"intentionClass": "org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaIntention",
|
||||
"intentionText": "Convert to lambda",
|
||||
"withRuntime": "true"
|
||||
}
|
||||
@@ -95,6 +95,12 @@ public class MultiFileIntentionTestGenerated extends AbstractMultiFileIntentionT
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("objectLiteralToLambda/objectLiteralToLambda.test")
|
||||
public void testObjectLiteralToLambda_ObjectLiteralToLambda() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/objectLiteralToLambda/objectLiteralToLambda.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("reconcilePackageWithDirectory/changeToDefaultPackage/changeToDefaultPackage.test")
|
||||
public void testReconcilePackageWithDirectory_changeToDefaultPackage_ChangeToDefaultPackage() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/reconcilePackageWithDirectory/changeToDefaultPackage/changeToDefaultPackage.test");
|
||||
|
||||
Reference in New Issue
Block a user