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 {
|
companion object {
|
||||||
fun replaceSamConstructorCall(callExpression: KtCallExpression): KtExpression {
|
fun replaceSamConstructorCall(callExpression: KtCallExpression): KtLambdaExpression {
|
||||||
val functionalArgument = callExpression.samConstructorValueArgument()?.getArgumentExpression()
|
val functionalArgument = callExpression.samConstructorValueArgument()?.getArgumentExpression()
|
||||||
?: throw AssertionError("SAM-constructor should have a FunctionLiteralExpression as single argument: ${callExpression.getElementTextWithContext()}")
|
?: 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 {
|
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.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.core.moveFunctionLiteralOutsideParentheses
|
import org.jetbrains.kotlin.idea.core.moveFunctionLiteralOutsideParentheses
|
||||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.containsInside
|
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.psiUtil.startOffset
|
||||||
import org.jetbrains.kotlin.psi.unpackFunctionLiteral
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
|
||||||
class MoveLambdaOutsideParenthesesIntention : SelfTargetingIntention<KtCallExpression>(KtCallExpression::class.java, "Move lambda argument out of parentheses") {
|
class MoveLambdaOutsideParenthesesIntention : SelfTargetingIntention<KtCallExpression>(KtCallExpression::class.java, "Move lambda argument out of parentheses") {
|
||||||
override fun isApplicableTo(element: KtCallExpression, caretOffset: Int): Boolean {
|
companion object {
|
||||||
if (element.lambdaArguments.isNotEmpty()) return false
|
private fun getLambdaExpression(callExpression: KtCallExpression): KtLambdaExpression? {
|
||||||
val argument = element.valueArguments.lastOrNull() ?: return false
|
if (callExpression.lambdaArguments.isNotEmpty()) return null
|
||||||
val expression = argument.getArgumentExpression() ?: return false
|
return callExpression.valueArguments.lastOrNull()?.getArgumentExpression()?.unpackFunctionLiteral()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (caretOffset < argument.asElement().startOffset) return false
|
fun canMove(element: KtCallExpression): Boolean {
|
||||||
val bodyRange = functionLiteral.bodyExpression?.textRange ?: return true
|
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)
|
return !bodyRange.containsInside(caretOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
|||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
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.core.replaced
|
||||||
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
||||||
import org.jetbrains.kotlin.idea.inspections.RedundantSamConstructorInspection
|
import org.jetbrains.kotlin.idea.inspections.RedundantSamConstructorInspection
|
||||||
@@ -135,6 +136,9 @@ class ObjectLiteralToLambdaIntention : SelfTargetingRangeIntention<KtObjectLiter
|
|||||||
?.parent as? KtCallExpression
|
?.parent as? KtCallExpression
|
||||||
if (parentCall != null && RedundantSamConstructorInspection.samConstructorCallsToBeConverted(parentCall).singleOrNull() == callExpression) {
|
if (parentCall != null && RedundantSamConstructorInspection.samConstructorCallsToBeConverted(parentCall).singleOrNull() == callExpression) {
|
||||||
RedundantSamConstructorInspection.replaceSamConstructorCall(callExpression)
|
RedundantSamConstructorInspection.replaceSamConstructorCall(callExpression)
|
||||||
|
if (MoveLambdaOutsideParenthesesIntention.canMove(parentCall)) {
|
||||||
|
parentCall.moveFunctionLiteralOutsideParentheses()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ShortenReferences.DEFAULT.process(replaced.getContainingKtFile(), replaced.startOffset, callee.endOffset)
|
ShortenReferences.DEFAULT.process(replaced.getContainingKtFile(), replaced.startOffset, callee.endOffset)
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
|
|
||||||
fun bar() {
|
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);
|
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")
|
@TestMetadata("reconcilePackageWithDirectory/changeToDefaultPackage/changeToDefaultPackage.test")
|
||||||
public void testReconcilePackageWithDirectory_changeToDefaultPackage_ChangeToDefaultPackage() throws Exception {
|
public void testReconcilePackageWithDirectory_changeToDefaultPackage_ChangeToDefaultPackage() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/reconcilePackageWithDirectory/changeToDefaultPackage/changeToDefaultPackage.test");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/reconcilePackageWithDirectory/changeToDefaultPackage/changeToDefaultPackage.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user