diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/createByPattern.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/createByPattern.kt index ca5f5c9e7c8..e6c8853afa3 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/createByPattern.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/createByPattern.kt @@ -18,15 +18,17 @@ package org.jetbrains.kotlin.psi import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement +import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.SmartPointerManager import com.intellij.psi.SmartPsiElementPointer import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.impl.source.codeStyle.CodeEditUtil +import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.renderName +import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.parents -import org.jetbrains.kotlin.psi.psiUtil.replaced import org.jetbrains.kotlin.psi.psiUtil.startOffset import java.util.ArrayList import java.util.HashMap @@ -38,15 +40,53 @@ public fun JetPsiFactory.createExpressionByPattern(pattern: String, vararg args: public fun JetPsiFactory.createDeclarationByPattern(pattern: String, vararg args: Any): TDeclaration = createByPattern(pattern, *args) { createDeclaration(it) } -private open class ArgumentType(val klass: Class) +private abstract class ArgumentType(val klass: Class) -class PlainTextArgumentType(klass: Class, val toPlainText: (T) -> String) : ArgumentType(klass) +private class PlainTextArgumentType(klass: Class, val toPlainText: (T) -> String) : ArgumentType(klass) + +private abstract class PsiElementPlaceholderArgumentType(klass: Class, val placeholderClass: Class) : ArgumentType(klass) { + abstract fun replacePlaceholderElement(placeholder: TPlaceholder, argument: T) +} + +private class PsiElementArgumentType(klass: Class) : PsiElementPlaceholderArgumentType(klass, klass) { + override fun replacePlaceholderElement(placeholder: T, argument: T) { + // if argument element has generated flag then it has not been formatted yet and we should do this manually + // (because we cleared this flag for the whole tree above and PostprocessReformattingAspect won't format anything) + val reformat = CodeEditUtil.isNodeGenerated(argument.getNode()) + val result = placeholder.replace(argument) + if (reformat) { + CodeStyleManager.getInstance(result.getProject()).reformat(result, true) + } + } +} + +private object PsiChildRangeArgumentType : PsiElementPlaceholderArgumentType(javaClass(), javaClass()) { + override fun replacePlaceholderElement(placeholder: JetElement, argument: PsiChildRange) { + val project = placeholder.getProject() + val codeStyleManager = CodeStyleManager.getInstance(project) + + if (argument.isEmpty) { + placeholder.delete() + } + else { + val first = placeholder.getParent().addRangeBefore(argument.first!!, argument.last!!, placeholder) + val last = placeholder.getPrevSibling() + placeholder.delete() + + codeStyleManager.reformatNewlyAddedElement(first.getNode().getTreeParent(), first.getNode()) + if (last != first) { + codeStyleManager.reformatNewlyAddedElement(last.getNode().getTreeParent(), last.getNode()) + } + } + } +} private val SUPPORTED_ARGUMENT_TYPES = listOf( - ArgumentType(javaClass()), - ArgumentType(javaClass()), + PsiElementArgumentType(javaClass()), + PsiElementArgumentType(javaClass()), PlainTextArgumentType(javaClass(), toPlainText = { it }), - PlainTextArgumentType(javaClass(), toPlainText = { it.renderName() }) + PlainTextArgumentType(javaClass(), toPlainText = { it.renderName() }), + PsiChildRangeArgumentType ) public fun createByPattern(pattern: String, vararg args: Any, factory: (String) -> TElement): TElement { @@ -60,7 +100,7 @@ public fun createByPattern(pattern: String, vararg args: val args = args.zip(argumentTypes).map { val (arg, type) = it if (type is PlainTextArgumentType) - (type.toPlainText as Function1).invoke(arg) // TODO: see KT-7833 + (type.toPlainText as Function1).invoke(arg) // TODO: see KT-7833 else arg } @@ -80,7 +120,7 @@ public fun createByPattern(pattern: String, vararg args: for ((n, placeholders) in allPlaceholders) { val arg = args[n] if (arg is String) continue // already in the text - val expectedElementType = argumentTypes[n].klass + val expectedElementType = (argumentTypes[n] as PsiElementPlaceholderArgumentType<*, *>).placeholderClass for ((range, text) in placeholders) { val token = resultElement.findElementAt(range.getStartOffset())!! @@ -130,14 +170,7 @@ public fun createByPattern(pattern: String, vararg args: if (element is JetFunctionLiteral) { element = element.getParent() as JetFunctionLiteralExpression } - val argument = args[n] as PsiElement - // if argument element has generated flag then it has not been formatted yet and we should do this manually - // (because we cleared this flag for the whole tree above and PostprocessReformattingAspect won't format anything) - val reformat = CodeEditUtil.isNodeGenerated(argument.getNode()) - element = element.replaced(argument) - if (reformat) { - codeStyleManager.reformat(element, true) - } + (argumentTypes[n] as PsiElementPlaceholderArgumentType).replacePlaceholderElement(element, args[n]) } codeStyleManager.adjustLineIndent(resultElement.getContainingFile(), resultElement.getTextRange()) diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToForEachFunctionCallIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToForEachFunctionCallIntention.kt index 106a5a60bfd..87749794e96 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToForEachFunctionCallIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToForEachFunctionCallIntention.kt @@ -37,21 +37,10 @@ public class ConvertToForEachFunctionCallIntention : JetSelfTargetingIntention { - val content = body.contentRange() - val text = content.getText() - if (content.last?.getNode()?.getElementType() == JetTokens.EOL_COMMENT) - text + "\n" - else - text - } - - else -> body.getText() - } + val functionBodyArgument: Any = if (body is JetBlockExpression) body.contentRange() else body val foreachExpression = JetPsiFactory(element).createExpressionByPattern( - "$0.forEach{$1->$2}", element.getLoopRange()!!, loopParameter, functionBodyText) + "$0.forEach{$1->$2}", element.getLoopRange()!!, loopParameter, functionBodyArgument) element.replace(foreachExpression) } } diff --git a/idea/testData/intentions/convertToForEachFunctionCall/commentsInBody.kt.after b/idea/testData/intentions/convertToForEachFunctionCall/commentsInBody.kt.after index f3365518d72..5eca38a41f8 100644 --- a/idea/testData/intentions/convertToForEachFunctionCall/commentsInBody.kt.after +++ b/idea/testData/intentions/convertToForEachFunctionCall/commentsInBody.kt.after @@ -1,7 +1,8 @@ fun foo() { val list = 1..4 - list.forEach { x ->// start of loop + list.forEach { x -> + // start of loop // comment 1 var v = x + 1 // comment 2 diff --git a/idea/testData/intentions/convertToForEachFunctionCall/commentsInBody2.kt.after b/idea/testData/intentions/convertToForEachFunctionCall/commentsInBody2.kt.after index 4d3b54f82dd..7c2819ed6ad 100644 --- a/idea/testData/intentions/convertToForEachFunctionCall/commentsInBody2.kt.after +++ b/idea/testData/intentions/convertToForEachFunctionCall/commentsInBody2.kt.after @@ -1,7 +1,8 @@ fun foo() { val list = 1..4 - list.forEach { x ->// comment + list.forEach { x -> + // comment var v = x + 1 } } \ No newline at end of file diff --git a/idea/testData/intentions/convertToForEachFunctionCall/emptyBody.kt b/idea/testData/intentions/convertToForEachFunctionCall/emptyBody.kt new file mode 100644 index 00000000000..d756bf79947 --- /dev/null +++ b/idea/testData/intentions/convertToForEachFunctionCall/emptyBody.kt @@ -0,0 +1,4 @@ +fun foo() { + for (x in 1..10) { + } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToForEachFunctionCall/emptyBody.kt.after b/idea/testData/intentions/convertToForEachFunctionCall/emptyBody.kt.after new file mode 100644 index 00000000000..16ca7d6182a --- /dev/null +++ b/idea/testData/intentions/convertToForEachFunctionCall/emptyBody.kt.after @@ -0,0 +1,3 @@ +fun foo() { + (1..10).forEach { x -> } +} \ 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 219c4222404..1c922f6e738 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -3943,6 +3943,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("emptyBody.kt") + public void testEmptyBody() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertToForEachFunctionCall/emptyBody.kt"); + doTest(fileName); + } + @TestMetadata("iterativeElementTypeSpecified.kt") public void testIterativeElementTypeSpecified() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/convertToForEachFunctionCall/iterativeElementTypeSpecified.kt");