From 26c47cda47b40da4b8d36aa44699f955446e513b Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Wed, 27 Jan 2016 16:06:38 +0300 Subject: [PATCH] Convert Object Literal to Lambda Intention: Move lambda out of parentheses when possible. Fix after-template #KT-10778 Fixed --- .../after.kt.template | 2 +- .../RedundantSamConstructorInspection.kt | 4 +- .../MoveLambdaOutsideParenthesesIntention.kt | 61 +++++++++++-------- .../ObjectLiteralToLambdaIntention.kt | 4 ++ .../NoSamAdapterNeeded.kt.after | 2 +- .../objectLiteralToLambda/after/source/J.java | 7 +++ .../after/source/test.kt | 5 ++ .../before/source/J.java | 7 +++ .../before/source/test.kt | 9 +++ .../objectLiteralToLambda.test | 6 ++ .../MultiFileIntentionTestGenerated.java | 6 ++ 11 files changed, 84 insertions(+), 29 deletions(-) create mode 100644 idea/testData/multiFileIntentions/objectLiteralToLambda/after/source/J.java create mode 100644 idea/testData/multiFileIntentions/objectLiteralToLambda/after/source/test.kt create mode 100644 idea/testData/multiFileIntentions/objectLiteralToLambda/before/source/J.java create mode 100644 idea/testData/multiFileIntentions/objectLiteralToLambda/before/source/test.kt create mode 100644 idea/testData/multiFileIntentions/objectLiteralToLambda/objectLiteralToLambda.test diff --git a/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/after.kt.template b/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/after.kt.template index 4ccb7623c3c..5c345f60bf3 100644 --- a/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/after.kt.template +++ b/idea/resources/intentionDescriptions/ObjectLiteralToLambdaIntention/after.kt.template @@ -1 +1 @@ -SwingUtilities { println("a") } +SwingUtilities.invokeLater { println("a") } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantSamConstructorInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantSamConstructorInspection.kt index 9208a9e6862..382ace006af 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantSamConstructorInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantSamConstructorInspection.kt @@ -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): Boolean { diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt index 9b384aed3d8..df2eaa34d3e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt @@ -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::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() - // 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() + // 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() ?: return false + if (caretOffset < argument.startOffset) return false + val bodyRange = lambdaExpression.bodyExpression?.textRange ?: return true return !bodyRange.containsInside(caretOffset) } diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt index cd6a59042fe..5d7ddd6d5a8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt @@ -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 : SelfTargetingRangeIntentionobject : Runnable { + override fun run() { + println("a") + } + }, 1) +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/objectLiteralToLambda/objectLiteralToLambda.test b/idea/testData/multiFileIntentions/objectLiteralToLambda/objectLiteralToLambda.test new file mode 100644 index 00000000000..2fb18ed7deb --- /dev/null +++ b/idea/testData/multiFileIntentions/objectLiteralToLambda/objectLiteralToLambda.test @@ -0,0 +1,6 @@ +{ + "mainFile": "source/test.kt", + "intentionClass": "org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaIntention", + "intentionText": "Convert to lambda", + "withRuntime": "true" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/MultiFileIntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/MultiFileIntentionTestGenerated.java index 2543f25fb1f..5f66affabe9 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/MultiFileIntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/MultiFileIntentionTestGenerated.java @@ -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");