diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties index 381d1b95957..a63e3c212ac 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/JetBundle.properties @@ -296,8 +296,6 @@ replace.with.infix.function.call.intention=Replace with infix function call replace.with.infix.function.call.intention.family=Replace with Infix Function Call replace.with.infix.function.call.intention.error.resolution.failed=The element cannot be resolved replace.with.infix.function.call.intention.error.package.call=Cannot be applied with a package as the receiver -replace.explicit.function.literal.param.with.it=Replace explicit parameter ''{0}'' with ''it'' -replace.explicit.function.literal.param.with.it.family=Replace Explicit Parameter with 'it' replace.it.with.explicit.function.literal.param=Replace 'it' with explicit parameter replace.it.with.explicit.function.literal.param.family=Replace 'it' with Explicit Parameter split.if=Split into 2 if's diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceExplicitFunctionLiteralParamWithItIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceExplicitFunctionLiteralParamWithItIntention.kt index f4b95af4d3f..b0d58479bc3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceExplicitFunctionLiteralParamWithItIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ReplaceExplicitFunctionLiteralParamWithItIntention.kt @@ -21,93 +21,78 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.codeStyle.CodeStyleManager -import com.intellij.psi.util.PsiTreeUtil import com.intellij.refactoring.rename.RenameProcessor import com.intellij.usageView.UsageInfo -import org.jetbrains.kotlin.descriptors.VariableDescriptor +import org.jetbrains.kotlin.descriptors.ParameterDescriptor import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor -import org.jetbrains.kotlin.idea.JetBundle import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.references.JetReference -import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.JetFile +import org.jetbrains.kotlin.psi.JetFunctionLiteral +import org.jetbrains.kotlin.psi.JetSimpleNameExpression +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils -import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode public class ReplaceExplicitFunctionLiteralParamWithItIntention() : PsiElementBaseIntentionAction() { - override fun invoke(project: Project, editor: Editor, element: PsiElement) { - val funcExpr = findFunctionLiteralToActOn(element)!! - val cursorWasOverParameterList = PsiTreeUtil.getParentOfType(element, javaClass()) != null - ParamRenamingProcessor(editor, funcExpr, cursorWasOverParameterList).run() - } + override fun getFamilyName() = "Replace explicit lambda parameter with 'it'" override fun isAvailable(project: Project, editor: Editor, element: PsiElement): Boolean { - val funcExpr = findFunctionLiteralToActOn(element) - if (funcExpr == null || funcExpr.getValueParameters().size() != 1) { - return false - } + val functionLiteral = targetFunctionLiteral(element, editor.getCaretModel().getOffset()) ?: return false - val parameter = funcExpr.getValueParameters().first() - if (parameter.getTypeReference() != null) { - return false - } + val parameter = functionLiteral.getValueParameters().singleOrNull() ?: return false + if (parameter.getTypeReference() != null) return false - setText(JetBundle.message("replace.explicit.function.literal.param.with.it", parameter.getName())) + setText("Replace explicit parameter '${parameter.getName()}' with 'it'") return true } - override fun getFamilyName(): String { - return JetBundle.message("replace.explicit.function.literal.param.with.it.family") + override fun invoke(project: Project, editor: Editor, element: PsiElement) { + val caretOffset = editor.getCaretModel().getOffset() + val functionLiteral = targetFunctionLiteral(element, editor.getCaretModel().getOffset())!! + val cursorInParameterList = functionLiteral.getValueParameterList()!!.getTextRange().containsOffset(caretOffset) + ParamRenamingProcessor(editor, functionLiteral, cursorInParameterList).run() } - private fun findFunctionLiteralToActOn(element: PsiElement): JetFunctionLiteral? { - if (PsiTreeUtil.getParentOfType(element, javaClass()) == null) { - return null + private fun targetFunctionLiteral(element: PsiElement, caretOffset: Int): JetFunctionLiteral? { + val innermostFunctionLiteral = element.getParentOfType(true) ?: return null + + val expression = element.getParentOfType(true) + if (expression != null) { + val reference = expression.getReference() as JetReference? + val target = reference?.resolveToDescriptors(expression.analyze())?.firstOrNull() as? ParameterDescriptor ?: return null + val functionDescriptor = target.getContainingDeclaration() as? AnonymousFunctionDescriptor ?: return null + return DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor) as? JetFunctionLiteral } - val expression = PsiTreeUtil.getParentOfType(element, javaClass(), javaClass()) - if (expression == null) { - return null - } + val arrow = innermostFunctionLiteral.getArrowNode() ?: return null + if (caretOffset > arrow.getTextRange().getEndOffset()) return null - when (expression) { - is JetParameter -> { - return PsiTreeUtil.skipParentsOfType(expression, javaClass()) as? JetFunctionLiteral - } - is JetSimpleNameExpression -> { - val reference = expression.getReference() as JetReference? - val variableDescriptor = reference?.resolveToDescriptors(expression.analyze(BodyResolveMode.PARTIAL))?.firstOrNull() as? VariableDescriptor? - if (variableDescriptor != null) { - val containingDescriptor = variableDescriptor.getContainingDeclaration() - if (containingDescriptor is AnonymousFunctionDescriptor) { - return DescriptorToSourceUtils.descriptorToDeclaration(containingDescriptor) as? JetFunctionLiteral - } - } - - return null - } - else -> return null - } + return innermostFunctionLiteral } private class ParamRenamingProcessor( val editor: Editor, - val funcLiteral: JetFunctionLiteral, - val cursorWasOverParameterList: Boolean) : RenameProcessor(editor.getProject(), - funcLiteral.getValueParameters().first(), - "it", - false, - false + val functionLiteral: JetFunctionLiteral, + val cursorWasInParameterList: Boolean + ) : RenameProcessor(editor.getProject(), + functionLiteral.getValueParameters().single(), + "it", + false, + false ) { public override fun performRefactoring(usages: Array) { super.performRefactoring(usages) - funcLiteral.deleteChildRange(funcLiteral.getValueParameterList(), funcLiteral.getArrowNode()!!.getPsi()) - if (cursorWasOverParameterList) { - editor.getCaretModel().moveToOffset(funcLiteral.getBodyExpression()!!.getTextOffset()) + + functionLiteral.deleteChildRange(functionLiteral.getValueParameterList(), functionLiteral.getArrowNode()!!.getPsi()) + + if (cursorWasInParameterList) { + editor.getCaretModel().moveToOffset(functionLiteral.getBodyExpression()!!.getTextOffset()) } - CodeStyleManager.getInstance(editor.getProject()!!)!!.reformatText(funcLiteral.getContainingFile()!!, - funcLiteral.getTextRange()!!.getStartOffset(), - funcLiteral.getTextRange()!!.getEndOffset()) + val range = functionLiteral.getTextRange() + CodeStyleManager.getInstance(functionLiteral.getProject()).reformatText(functionLiteral.getContainingFile(), + range.getStartOffset(), + range.getEndOffset()) } } diff --git a/idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_wrongPosition.kt b/idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_wrongPosition.kt new file mode 100644 index 00000000000..ca84c760356 --- /dev/null +++ b/idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_wrongPosition.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false + +fun applyTwice(f: (A) -> A, x: A) = f(f(x)) +val x = applyTwice({ p -> p + 1 }, 40) diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 2b12c8e2fa9..538013f9420 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -5176,6 +5176,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_parameterHasExplicitType.kt"); doTest(fileName); } + + @TestMetadata("notApplicable_wrongPosition.kt") + public void testNotApplicable_wrongPosition() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/replaceExplicitFunctionLiteralParamWithIt/notApplicable_wrongPosition.kt"); + doTest(fileName); + } } @TestMetadata("idea/testData/intentions/replaceItWithExplicitFunctionLiteralParam")