KT-33384 Intention to switch between single-line/multi-line lambda (#2790)

Add intention for single-line lambda <-> multi-line lambda conversion

#KT-33384 Fixed
This commit is contained in:
Toshiaki Kameyama
2020-01-29 18:32:26 +09:00
committed by GitHub
parent f94d026e64
commit ebe3619251
43 changed files with 383 additions and 0 deletions
+10
View File
@@ -1704,6 +1704,16 @@
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ConvertLambdaToMultiLineIntention</className>
<category>Kotlin</category>
</intentionAction>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.ConvertLambdaToSingleLineIntention</className>
<category>Kotlin</category>
</intentionAction>
<lang.inspectionSuppressor language="kotlin" implementationClass="org.jetbrains.kotlin.idea.inspections.KotlinInspectionSuppressor"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
@@ -0,0 +1,3 @@
list.forEach { item ->
println(item)
}
@@ -0,0 +1 @@
list.forEach { item -> println(item) }
@@ -0,0 +1,5 @@
<html>
<body>
This intention converts a single-line lambda to a multi-line one.
</body>
</html>
@@ -0,0 +1 @@
list.forEach { item -> println(item) }
@@ -0,0 +1,3 @@
list.forEach { item ->
println(item)
}
@@ -0,0 +1,5 @@
<html>
<body>
This intention converts a multi-line lambda to a single-line one.
</body>
</html>
@@ -0,0 +1,65 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.intentions
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiComment
import org.jetbrains.kotlin.idea.inspections.RedundantSemicolonInspection
import org.jetbrains.kotlin.idea.refactoring.getLineNumber
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.allChildren
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespace
import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespace
sealed class ConvertLambdaLineIntention(private val toMultiLine: Boolean) : SelfTargetingIntention<KtLambdaExpression>(
KtLambdaExpression::class.java, "Convert to ${if (toMultiLine) "multi" else "single"}-line lambda"
) {
override fun isApplicableTo(element: KtLambdaExpression, caretOffset: Int): Boolean {
val functionLiteral = element.functionLiteral
val body = functionLiteral.bodyBlockExpression ?: return false
val startLine = functionLiteral.getLineNumber(start = true)
val endLine = functionLiteral.getLineNumber(start = false)
return if (toMultiLine) {
startLine == endLine
} else {
if (startLine == endLine) return false
val allChildren = body.allChildren
if (allChildren.any { it is PsiComment && it.node.elementType == KtTokens.EOL_COMMENT }) return false
val first = allChildren.first?.getNextSiblingIgnoringWhitespace(withItself = true) ?: return true
val last = allChildren.last?.getPrevSiblingIgnoringWhitespace(withItself = true)
first.getLineNumber(start = true) == last?.getLineNumber(start = false)
}
}
override fun applyTo(element: KtLambdaExpression, editor: Editor?) {
val functionLiteral = element.functionLiteral
val body = functionLiteral.bodyBlockExpression ?: return
val psiFactory = KtPsiFactory(element)
if (toMultiLine) {
body.allChildren.forEach {
if (it.node.elementType == KtTokens.SEMICOLON) {
body.addAfter(psiFactory.createNewLine(), it)
if (RedundantSemicolonInspection.isRedundantSemicolon(it)) it.delete()
}
}
}
val bodyText = body.text
val startLineBreak = if (toMultiLine) "\n" else ""
val endLineBreak = if (toMultiLine && bodyText != "") "\n" else ""
element.replace(
psiFactory.createLambdaExpression(
functionLiteral.valueParameters.joinToString { it.text },
"$startLineBreak$bodyText$endLineBreak"
)
)
}
}
class ConvertLambdaToMultiLineIntention : ConvertLambdaLineIntention(toMultiLine = true)
class ConvertLambdaToSingleLineIntention : ConvertLambdaLineIntention(toMultiLine = false)
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.ConvertLambdaToMultiLineIntention
@@ -0,0 +1,7 @@
// IS_APPLICABLE: false
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach {
println(it)
}<caret>
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach <caret>{}
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach {
}
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { <caret>println(it) }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach {
println(it)
}
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item -> println(item)<caret> }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item ->
println(item)
}
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item -> println(item); println(item); println(item) /* comment */ }<caret>
}
@@ -0,0 +1,8 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item ->
println(item)
println(item)
println(item) /* comment */
}
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEachIndexed { index, s -> println(index) }<caret>
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEachIndexed { index, s ->
println(index)
}
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<Pair<String, Int>>) {
list.forEach { (s, _) -> println(s) }<caret>
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test(list: List<Pair<String, Int>>) {
list.forEach { (s, _) ->
println(s)
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.intentions.ConvertLambdaToSingleLineIntention
@@ -0,0 +1,7 @@
// IS_APPLICABLE: false
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item ->
println(item) // comment
}<caret>
}
@@ -0,0 +1,8 @@
// IS_APPLICABLE: false
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item ->
println(item)
println(item)
}<caret>
}
@@ -0,0 +1,10 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item ->
println(item) /* comment */
}<caret>
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item -> println(item) /* comment */ }
}
@@ -0,0 +1,7 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach {
item ->
println(item)
}<caret>
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item -> println(item) }
}
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach <caret>{
}
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach {
<caret>println(it)
}
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { println(it) }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item ->
println(item)<caret>
}
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item -> println(item) }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item ->
println(item); println(item); println(item) /* comment */
}<caret>
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { item -> println(item); println(item); println(item) /* comment */ }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEachIndexed { index, s ->
println(index)
}<caret>
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEachIndexed { index, s -> println(index) }
}
@@ -0,0 +1,6 @@
// WITH_RUNTIME
fun test(list: List<Pair<String, Int>>) {
list.forEach { (s, _) ->
println(s)
}<caret>
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
fun test(list: List<Pair<String, Int>>) {
list.forEach { (s, _) -> println(s) }
}
@@ -0,0 +1,5 @@
// IS_APPLICABLE: false
// WITH_RUNTIME
fun test(list: List<String>) {
list.forEach { println(it) }
}<caret>
@@ -4692,6 +4692,54 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/convertLambdaToMultiLine")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ConvertLambdaToMultiLine extends AbstractIntentionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInConvertLambdaToMultiLine() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/intentions/convertLambdaToMultiLine"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true);
}
@TestMetadata("multiLine.kt")
public void testMultiLine() throws Exception {
runTest("idea/testData/intentions/convertLambdaToMultiLine/multiLine.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("idea/testData/intentions/convertLambdaToMultiLine/simple.kt");
}
@TestMetadata("simple2.kt")
public void testSimple2() throws Exception {
runTest("idea/testData/intentions/convertLambdaToMultiLine/simple2.kt");
}
@TestMetadata("simple3.kt")
public void testSimple3() throws Exception {
runTest("idea/testData/intentions/convertLambdaToMultiLine/simple3.kt");
}
@TestMetadata("simple4.kt")
public void testSimple4() throws Exception {
runTest("idea/testData/intentions/convertLambdaToMultiLine/simple4.kt");
}
@TestMetadata("simple5.kt")
public void testSimple5() throws Exception {
runTest("idea/testData/intentions/convertLambdaToMultiLine/simple5.kt");
}
@TestMetadata("simple6.kt")
public void testSimple6() throws Exception {
runTest("idea/testData/intentions/convertLambdaToMultiLine/simple6.kt");
}
}
@TestMetadata("idea/testData/intentions/convertLambdaToReference")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -5050,6 +5098,74 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
}
}
@TestMetadata("idea/testData/intentions/convertLambdaToSingleLine")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ConvertLambdaToSingleLine extends AbstractIntentionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInConvertLambdaToSingleLine() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/intentions/convertLambdaToSingleLine"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true);
}
@TestMetadata("hasEolComment.kt")
public void testHasEolComment() throws Exception {
runTest("idea/testData/intentions/convertLambdaToSingleLine/hasEolComment.kt");
}
@TestMetadata("multiLineBody.kt")
public void testMultiLineBody() throws Exception {
runTest("idea/testData/intentions/convertLambdaToSingleLine/multiLineBody.kt");
}
@TestMetadata("multiLineBody2.kt")
public void testMultiLineBody2() throws Exception {
runTest("idea/testData/intentions/convertLambdaToSingleLine/multiLineBody2.kt");
}
@TestMetadata("multiLineBody3.kt")
public void testMultiLineBody3() throws Exception {
runTest("idea/testData/intentions/convertLambdaToSingleLine/multiLineBody3.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("idea/testData/intentions/convertLambdaToSingleLine/simple.kt");
}
@TestMetadata("simple2.kt")
public void testSimple2() throws Exception {
runTest("idea/testData/intentions/convertLambdaToSingleLine/simple2.kt");
}
@TestMetadata("simple3.kt")
public void testSimple3() throws Exception {
runTest("idea/testData/intentions/convertLambdaToSingleLine/simple3.kt");
}
@TestMetadata("simple4.kt")
public void testSimple4() throws Exception {
runTest("idea/testData/intentions/convertLambdaToSingleLine/simple4.kt");
}
@TestMetadata("simple5.kt")
public void testSimple5() throws Exception {
runTest("idea/testData/intentions/convertLambdaToSingleLine/simple5.kt");
}
@TestMetadata("simple6.kt")
public void testSimple6() throws Exception {
runTest("idea/testData/intentions/convertLambdaToSingleLine/simple6.kt");
}
@TestMetadata("singleLine.kt")
public void testSingleLine() throws Exception {
runTest("idea/testData/intentions/convertLambdaToSingleLine/singleLine.kt");
}
}
@TestMetadata("idea/testData/intentions/convertLateinitPropertyToNullable")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)