Add intention to convert lambda to anonymous function #KT-7710 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
8ef4b9a8e1
commit
8a20d1bf01
+7
@@ -0,0 +1,7 @@
|
||||
fun foo(f: () -> Int) {
|
||||
f()
|
||||
}
|
||||
|
||||
fun main(args: String) {
|
||||
foo(fun(): Int { return 1 })
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fun foo(f: () -> Int) {
|
||||
f()
|
||||
}
|
||||
|
||||
fun main(args: String) {
|
||||
foo { 1 }
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This intention converts a lambda expression into a call of an anonymous function.
|
||||
</body>
|
||||
</html>
|
||||
@@ -1609,6 +1609,11 @@
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.LambdaToAnonymousFunctionIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
|
||||
displayName="Object literal can be converted to lambda"
|
||||
groupPath="Kotlin"
|
||||
|
||||
@@ -1609,6 +1609,11 @@
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.LambdaToAnonymousFunctionIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
|
||||
displayName="Object literal can be converted to lambda"
|
||||
groupPath="Kotlin"
|
||||
|
||||
@@ -1609,6 +1609,11 @@
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.LambdaToAnonymousFunctionIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
|
||||
displayName="Object literal can be converted to lambda"
|
||||
groupPath="Kotlin"
|
||||
|
||||
@@ -1610,6 +1610,11 @@
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.LambdaToAnonymousFunctionIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
|
||||
displayName="Object literal can be converted to lambda"
|
||||
groupPath="Kotlin"
|
||||
|
||||
@@ -1609,6 +1609,11 @@
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.LambdaToAnonymousFunctionIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
|
||||
displayName="Object literal can be converted to lambda"
|
||||
groupPath="Kotlin"
|
||||
|
||||
@@ -1609,6 +1609,11 @@
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.LambdaToAnonymousFunctionIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
|
||||
displayName="Object literal can be converted to lambda"
|
||||
groupPath="Kotlin"
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. 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 org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.moveInsideParentheses
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.BranchedFoldingUtils
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getTargetFunctionDescriptor
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||
|
||||
class LambdaToAnonymousFunctionIntention : SelfTargetingIntention<KtLambdaExpression>(
|
||||
KtLambdaExpression::class.java,
|
||||
"Convert to anonymous function",
|
||||
"Convert lambda expression to anonymous function"
|
||||
) {
|
||||
|
||||
override fun isApplicableTo(element: KtLambdaExpression, caretOffset: Int): Boolean {
|
||||
if (element.getStrictParentOfType<KtValueArgument>() == null) return false
|
||||
val descriptor = element.functionLiteral.descriptor as? AnonymousFunctionDescriptor ?: return false
|
||||
return descriptor.valueParameters.none { it.name.isSpecial }
|
||||
}
|
||||
|
||||
override fun applyTo(element: KtLambdaExpression, editor: Editor?) {
|
||||
val functionLiteral = element.functionLiteral
|
||||
val bodyExpression = functionLiteral.bodyExpression ?: return
|
||||
val descriptor = functionLiteral.descriptor as? AnonymousFunctionDescriptor ?: return
|
||||
val psiFactory = KtPsiFactory(element)
|
||||
|
||||
val context = element.analyze(BodyResolveMode.PARTIAL)
|
||||
bodyExpression.collectDescendantsOfType<KtReturnExpression>().forEach {
|
||||
if (it.getTargetFunctionDescriptor(context) == descriptor) it.labeledExpression?.delete()
|
||||
}
|
||||
|
||||
val extension = descriptor.extensionReceiverParameter?.type?.let { "$it." } ?: ""
|
||||
val params = descriptor.valueParameters.joinToString { "${it.name}: ${it.type}" }
|
||||
val returnType = descriptor.returnType?.let { if (it.isUnit()) "" else ": $it" } ?: ""
|
||||
if (returnType.isNotEmpty()) {
|
||||
val lastStatement = bodyExpression.statements.lastOrNull()
|
||||
if (lastStatement != null && lastStatement !is KtReturnExpression) {
|
||||
val foldableReturns = BranchedFoldingUtils.getFoldableReturns(lastStatement)
|
||||
if (foldableReturns == null || foldableReturns.isEmpty()) {
|
||||
lastStatement.replace(psiFactory.createExpressionByPattern("return $0", lastStatement))
|
||||
}
|
||||
}
|
||||
}
|
||||
val anonymousFunction = element.replaced(
|
||||
psiFactory.createExpressionByPattern("fun $0($1)$2 { $3 }", extension, params, returnType, bodyExpression, reformat = false)
|
||||
) as KtNamedFunction
|
||||
|
||||
(anonymousFunction.parent as? KtLambdaArgument)?.also { it.moveInsideParentheses(it.analyze(BodyResolveMode.PARTIAL)) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.intentions.LambdaToAnonymousFunctionIntention
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo(<caret>{ "" })
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo(fun(it: Int): String { return "" })
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// IS_APPLICABLE: false
|
||||
// WITH_RUNTIME
|
||||
fun foo(f: (Pair<Int, Int>) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo <caret>{ (i, j) -> "" }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun bar(f: (Int, Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
bar <caret>{ i, j -> "$i$j" }
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun bar(f: (Int, Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
bar(fun(i: Int, j: Int): String { return "$i$j" })
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class Foo
|
||||
fun bar(f: Foo.() -> Unit) {}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
bar {}<caret>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class Foo
|
||||
fun bar(f: Foo.() -> Unit) {}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
bar(fun Foo.() {})
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class Foo
|
||||
fun baz(f: Foo.(i: Int, j: Int) -> Int) {}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
baz { i, j -> i + j }<caret>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class Foo
|
||||
fun baz(f: Foo.(i: Int, j: Int) -> Int) {}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
baz(fun Foo.(i: Int, j: Int): Int { return i + j })
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo <caret>{
|
||||
// comment1
|
||||
""
|
||||
// comment2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo(fun(it: Int): String {
|
||||
// comment1
|
||||
return ""
|
||||
// comment2
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo {<caret> return@foo "$it" }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo(fun(it: Int): String { return "$it" })
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo {
|
||||
if (it == 1) {
|
||||
return@foo "1"
|
||||
}
|
||||
"$it"
|
||||
<caret>}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo(fun(it: Int): String {
|
||||
if (it == 1) {
|
||||
return "1"
|
||||
}
|
||||
return "$it"
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo {
|
||||
if (it == 1) {
|
||||
return@foo "1"
|
||||
} else if (it == 2) {
|
||||
return@foo "2"
|
||||
} else {
|
||||
return@foo "$it"
|
||||
}
|
||||
}<caret>
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo(fun(it: Int): String {
|
||||
if (it == 1) {
|
||||
return "1"
|
||||
} else if (it == 2) {
|
||||
return "2"
|
||||
} else {
|
||||
return "$it"
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo {<caret>
|
||||
listOf(1).map {
|
||||
return@map 2
|
||||
}
|
||||
return@foo "$it"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo(fun(it: Int): String {
|
||||
listOf(1).map {
|
||||
return@map 2
|
||||
}
|
||||
return "$it"
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo <caret>{
|
||||
val b = it == 1
|
||||
if (b) "1" else "2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo(fun(it: Int): String {
|
||||
val b = it == 1
|
||||
return if (b) "1" else "2"
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo <caret>{ "$it" }
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo(f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo(fun(it: Int): String { return "$it" })
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun baz(name: String, f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
baz(name = "", f = <caret>{ "$it" })
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun baz(name: String, f: (Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
baz(name = "", f = fun(it: Int): String { return "$it" })
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(f: () -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo <caret>{ "" }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo(f: () -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo(fun(): String { return "" })
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fun unit(f: (Int) -> Unit) {}
|
||||
|
||||
fun foo(i: Int) {}
|
||||
|
||||
fun test() {
|
||||
unit {
|
||||
foo(it)
|
||||
foo(it)
|
||||
}<caret>
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fun unit(f: (Int) -> Unit) {}
|
||||
|
||||
fun foo(i: Int) {}
|
||||
|
||||
fun test() {
|
||||
unit(fun(it: Int) {
|
||||
foo(it)
|
||||
foo(it)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// IS_APPLICABLE: false
|
||||
fun foo(f: (Int, Int) -> String) {}
|
||||
|
||||
fun test() {
|
||||
foo <caret>{ i, _ -> "" }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// IS_APPLICABLE: false
|
||||
fun test() {
|
||||
val a: (Int) -> String = <caret>{ "" }
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
// ACTION: Rename to _
|
||||
// ACTION: Specify explicit lambda signature
|
||||
// ACTION: Specify type explicitly
|
||||
// ACTION: Convert to anonymous function
|
||||
// RUNTIME_WITH_FULL_JDK
|
||||
|
||||
fun main() {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// ACTION: Move lambda argument into parentheses
|
||||
// ACTION: Remove explicit lambda parameter types (may break code)
|
||||
// ACTION: Rename to _
|
||||
// ACTION: Convert to anonymous function
|
||||
|
||||
fun foo(block: (String, Int) -> Unit) {
|
||||
block("", 1)
|
||||
|
||||
+1
@@ -6,6 +6,7 @@
|
||||
// ACTION: Replace with safe (this?.) call
|
||||
// ACTION: Specify explicit lambda signature
|
||||
// ACTION: Add return@let
|
||||
// ACTION: Convert to anonymous function
|
||||
// ERROR: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
|
||||
|
||||
fun String?.foo(a: String?) {
|
||||
|
||||
@@ -9627,6 +9627,104 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/intentions/lambdaToAnonymousFunction")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class LambdaToAnonymousFunction extends AbstractIntentionTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInLambdaToAnonymousFunction() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/lambdaToAnonymousFunction"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("argument.kt")
|
||||
public void testArgument() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/argument.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("destructuringParameter.kt")
|
||||
public void testDestructuringParameter() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/destructuringParameter.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("explicitParameterName.kt")
|
||||
public void testExplicitParameterName() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/explicitParameterName.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("extention1.kt")
|
||||
public void testExtention1() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/extention1.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("extention2.kt")
|
||||
public void testExtention2() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/extention2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("hasComment.kt")
|
||||
public void testHasComment() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/hasComment.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("hasReturn1.kt")
|
||||
public void testHasReturn1() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/hasReturn1.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("hasReturn2.kt")
|
||||
public void testHasReturn2() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/hasReturn2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("hasReturn3.kt")
|
||||
public void testHasReturn3() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/hasReturn3.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("hasReturn4.kt")
|
||||
public void testHasReturn4() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/hasReturn4.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("hasSomeStatements.kt")
|
||||
public void testHasSomeStatements() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/hasSomeStatements.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("implicitParameterName.kt")
|
||||
public void testImplicitParameterName() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/implicitParameterName.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("namedArgument.kt")
|
||||
public void testNamedArgument() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/namedArgument.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("noParameter.kt")
|
||||
public void testNoParameter() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/noParameter.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("returnUnit.kt")
|
||||
public void testReturnUnit() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/returnUnit.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("underscoreParameter.kt")
|
||||
public void testUnderscoreParameter() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/underscoreParameter.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("variable.kt")
|
||||
public void testVariable() throws Exception {
|
||||
runTest("idea/testData/intentions/lambdaToAnonymousFunction/variable.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/intentions/loopToCallChain")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user