From cda3344250e7e388c305a7502a7d548cf9b093ee Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Tue, 28 Apr 2015 11:54:42 +0300 Subject: [PATCH] ForEach to for-loop intention: smaller range + refactoring --- .../ConvertForEachToForLoopIntention.kt | 50 +++++++++++-------- .../convertForEachToForLoop/infixCall.kt | 2 +- .../infixCallNotAvailable.kt | 7 +++ .../typeArgumentPresent.kt | 2 +- .../intentions/IntentionTestGenerated.java | 6 +++ 5 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 idea/testData/intentions/convertForEachToForLoop/infixCallNotAvailable.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertForEachToForLoopIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertForEachToForLoopIntention.kt index 23ab4a4aa49..6bb42fd09b5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertForEachToForLoopIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertForEachToForLoopIntention.kt @@ -24,34 +24,44 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.getCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver -public class ConvertForEachToForLoopIntention : JetSelfTargetingIntention(javaClass(), "Replace with a for loop") { - override fun isApplicableTo(element: JetExpression, caretOffset: Int): Boolean { - val functionLiteral = extractFunctionLiteral(element) ?: return false - if (functionLiteral.getValueParameters().size() > 1) return false - if (functionLiteral.getBodyExpression() == null) return false +public class ConvertForEachToForLoopIntention : JetSelfTargetingOffsetIndependentIntention(javaClass(), "Replace with a for loop") { + override fun isApplicableTo(element: JetSimpleNameExpression): Boolean { + if (element.getReferencedName() != "forEach") return false - if (caretOffset > functionLiteral.getTextRange().getStartOffset()) return false // not available within function literal body + val data = extractData(element) ?: return false + if (data.functionLiteral.getValueParameters().size() > 1) return false + if (data.functionLiteral.getBodyExpression() == null) return false - val resolvedCall = element.getResolvedCall(element.analyze()) ?: return false - if (DescriptorUtils.getFqName(resolvedCall.getResultingDescriptor()).toString() != "kotlin.forEach") return false - return resolvedCall.getCall().getExplicitReceiver() is ExpressionReceiver + return true } - override fun applyTo(element: JetExpression, editor: Editor) { - val functionLiteral = extractFunctionLiteral(element)!! - val receiver = element.getCall(element.analyze())!!.getExplicitReceiver() as ExpressionReceiver - val receiverExpression = receiver.getExpression() - val loopText = generateLoopText(functionLiteral, receiverExpression) - val expressionToReplace = receiverExpression.getParent() // it's correct for both JetDotQualifiedExpression and JetBinaryExpression + override fun applyTo(element: JetSimpleNameExpression, editor: Editor) { + val (expressionToReplace, receiver, functionLiteral) = extractData(element)!! + val loopText = generateLoopText(functionLiteral, receiver) expressionToReplace.replace(JetPsiFactory(element).createExpression(loopText)) } - private fun extractFunctionLiteral(element: JetExpression): JetFunctionLiteralExpression? { - return when (element) { - is JetCallExpression -> element.getValueArguments().singleOrNull()?.getArgumentExpression() - is JetBinaryExpression -> element.getRight() + private data class Data( + val expressionToReplace: JetExpression, + val receiver: JetExpression, + val functionLiteral: JetFunctionLiteralExpression + ) + + private fun extractData(nameExpr: JetSimpleNameExpression): Data? { + val parent = nameExpr.getParent() + val expression = (when (parent) { + is JetCallExpression -> parent.getParent() as? JetDotQualifiedExpression + is JetBinaryExpression -> parent else -> null - } as? JetFunctionLiteralExpression + } ?: return null) as JetExpression //TODO: submit bug + + val resolvedCall = expression.getResolvedCall(expression.analyze()) ?: return null + if (DescriptorUtils.getFqName(resolvedCall.getResultingDescriptor()).toString() != "kotlin.forEach") return null + + val receiver = resolvedCall.getCall().getExplicitReceiver() as? ExpressionReceiver ?: return null + val argument = resolvedCall.getCall().getValueArguments().singleOrNull() ?: return null + val functionLiteral = argument.getArgumentExpression() as? JetFunctionLiteralExpression ?: return null + return Data(expression, receiver.getExpression(), functionLiteral) } private fun generateLoopText(functionLiteral: JetFunctionLiteralExpression, receiver: JetExpression): String { diff --git a/idea/testData/intentions/convertForEachToForLoop/infixCall.kt b/idea/testData/intentions/convertForEachToForLoop/infixCall.kt index 8fa22e684c4..cfb82e118d3 100644 --- a/idea/testData/intentions/convertForEachToForLoop/infixCall.kt +++ b/idea/testData/intentions/convertForEachToForLoop/infixCall.kt @@ -2,5 +2,5 @@ fun foo() { val x = 1..4 - x forEach { a -> a } + x forEach { a -> a } } \ No newline at end of file diff --git a/idea/testData/intentions/convertForEachToForLoop/infixCallNotAvailable.kt b/idea/testData/intentions/convertForEachToForLoop/infixCallNotAvailable.kt new file mode 100644 index 00000000000..e4a9a07a8af --- /dev/null +++ b/idea/testData/intentions/convertForEachToForLoop/infixCallNotAvailable.kt @@ -0,0 +1,7 @@ +// WITH_RUNTIME +// IS_APPLICABLE: false +fun foo() { + val x = 1..4 + + x forEach { a -> a } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertForEachToForLoop/typeArgumentPresent.kt b/idea/testData/intentions/convertForEachToForLoop/typeArgumentPresent.kt index d5cc7b393fb..be83eef46de 100644 --- a/idea/testData/intentions/convertForEachToForLoop/typeArgumentPresent.kt +++ b/idea/testData/intentions/convertForEachToForLoop/typeArgumentPresent.kt @@ -2,5 +2,5 @@ fun foo() { val x = 1..4 - x.forEach({ it }) + x.forEach({ it }) } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 0d4998fb94f..3e7167b4a3d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -2578,6 +2578,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("infixCallNotAvailable.kt") + public void testInfixCallNotAvailable() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertForEachToForLoop/infixCallNotAvailable.kt"); + doTest(fileName); + } + @TestMetadata("parenthesizedExpression.kt") public void testParenthesizedExpression() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertForEachToForLoop/parenthesizedExpression.kt");