diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt index dde653af000..220e67cbef9 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/psiModificationUtils.kt @@ -21,6 +21,7 @@ import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.impl.source.codeStyle.CodeEditUtil import com.intellij.psi.tree.IElementType import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.builtins.isFunctionType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.extensions.DeclarationAttributeAltererExtension import org.jetbrains.kotlin.idea.caches.resolve.analyze @@ -100,6 +101,32 @@ private fun shouldLambdaParameterBeNamed(args: List, callExpr: Kt return if (calee.valueParameters.any { it.isVarArg }) true else calee.valueParameters.size - 1 > args.size } +fun KtCallExpression.getLastLambdaExpression(): KtLambdaExpression? { + if (lambdaArguments.isNotEmpty()) return null + return valueArguments.lastOrNull()?.getArgumentExpression()?.unpackFunctionLiteral() +} + +fun KtCallExpression.canMoveLambdaOutsideParentheses(): Boolean { + if (getLastLambdaExpression() == null) return false + + val callee = calleeExpression + if (callee is KtNameReferenceExpression) { + val bindingContext = 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 && lastParameter.type.isFunctionType + }) { + return false + } + } + + return true +} fun KtCallExpression.moveFunctionLiteralOutsideParentheses() { assert(lambdaArguments.isEmpty()) diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt index 7e5afdb0ab9..ca4484073c4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/MoveLambdaOutsideParenthesesIntention.kt @@ -17,58 +17,31 @@ package org.jetbrains.kotlin.idea.intentions import com.intellij.openapi.editor.Editor -import org.jetbrains.kotlin.builtins.isFunctionType -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.core.canMoveLambdaOutsideParentheses +import org.jetbrains.kotlin.idea.core.getLastLambdaExpression import org.jetbrains.kotlin.idea.core.moveFunctionLiteralOutsideParentheses -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtLambdaExpression +import org.jetbrains.kotlin.psi.KtValueArgument +import org.jetbrains.kotlin.psi.KtValueArgumentList 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.resolve.BindingContext -import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode class MoveLambdaOutsideParenthesesIntention : SelfTargetingIntention(KtCallExpression::class.java, "Move lambda argument out of parentheses") { companion object { - private fun getLambdaExpression(callExpression: KtCallExpression): KtLambdaExpression? { - if (callExpression.lambdaArguments.isNotEmpty()) return null - return callExpression.valueArguments.lastOrNull()?.getArgumentExpression()?.unpackFunctionLiteral() - } - - 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 && lastParameter.type.isFunctionType - }) { - return false - } - } - - return true - } - fun moveFunctionLiteralOutsideParenthesesIfPossible(expression: KtLambdaExpression) { val call = ((expression.parent as? KtValueArgument)?.parent as? KtValueArgumentList)?.parent as? KtCallExpression ?: return - if (canMove(call)) { + if (call.canMoveLambdaOutsideParentheses()) { call.moveFunctionLiteralOutsideParentheses() } } } override fun isApplicableTo(element: KtCallExpression, caretOffset: Int): Boolean { - if (!canMove(element)) return false + if (!element.canMoveLambdaOutsideParentheses()) return false - val lambdaExpression = getLambdaExpression(element) ?: return false + val lambdaExpression = element.getLastLambdaExpression() ?: return false val argument = lambdaExpression.getStrictParentOfType() ?: return false if (caretOffset < argument.startOffset) return false val bodyRange = lambdaExpression.bodyExpression?.textRange ?: return true diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt index ff41db6d329..b079e3f7584 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.core.canMoveLambdaOutsideParentheses import org.jetbrains.kotlin.idea.core.moveFunctionLiteralOutsideParentheses import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection @@ -36,7 +37,10 @@ import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.contentRange +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall @@ -152,7 +156,7 @@ class ObjectLiteralToLambdaIntention : SelfTargetingRangeIntention