Convert reference to lambda intention introduced #KT-13393 Fixed
This commit is contained in:
@@ -81,9 +81,6 @@ class KtPsiFactory(private val project: Project) {
|
||||
fun createThisExpression() =
|
||||
(createExpression("this.x") as KtQualifiedExpression).receiverExpression as KtThisExpression
|
||||
|
||||
fun createClassLiteral(className: String): KtClassLiteralExpression =
|
||||
createExpression("$className::class") as KtClassLiteralExpression
|
||||
|
||||
fun createCallArguments(text: String): KtValueArgumentList {
|
||||
val property = createProperty("val x = foo $text")
|
||||
return (property.initializer as KtCallExpression).valueArgumentList!!
|
||||
@@ -328,9 +325,13 @@ class KtPsiFactory(private val project: Project) {
|
||||
|
||||
fun createTypeParameter(text: String) = createTypeParameterList("<$text>").parameters.first()!!
|
||||
|
||||
fun createFunctionLiteralParameterList(text: String): KtParameterList {
|
||||
return (createExpression("{ $text -> 0}") as KtLambdaExpression).functionLiteral.valueParameterList!!
|
||||
}
|
||||
fun createFunctionLiteralParameterList(text: String) =
|
||||
createLambdaExpression(text, "0").functionLiteral.valueParameterList!!
|
||||
|
||||
fun createLambdaExpression(parameters: String, body: String): KtLambdaExpression =
|
||||
(if (parameters.isNotEmpty()) createExpression("{ $parameters -> $body }")
|
||||
else createExpression("{ $body }")) as KtLambdaExpression
|
||||
|
||||
|
||||
fun createEnumEntry(text: String): KtEnumEntry {
|
||||
return createDeclaration<KtClass>("enum class E {$text}").declarations[0] as KtEnumEntry
|
||||
@@ -387,11 +388,6 @@ class KtPsiFactory(private val project: Project) {
|
||||
return file.importDirectives.first()
|
||||
}
|
||||
|
||||
fun createImportDirectiveWithImportList(importPath: ImportPath): KtImportList {
|
||||
val importDirective = createImportDirective(importPath)
|
||||
return importDirective.parent as KtImportList
|
||||
}
|
||||
|
||||
fun createPrimaryConstructor(): KtPrimaryConstructor {
|
||||
return createClass("class A()").getPrimaryConstructor()!!
|
||||
}
|
||||
@@ -406,10 +402,6 @@ class KtPsiFactory(private val project: Project) {
|
||||
fun createLabeledExpression(labelName: String): KtLabeledExpression
|
||||
= createExpression("$labelName@ 1") as KtLabeledExpression
|
||||
|
||||
fun createFieldIdentifier(fieldName: String): PsiElement {
|
||||
return (createExpression("$" + fieldName) as KtNameReferenceExpression).getReferencedNameElement()
|
||||
}
|
||||
|
||||
fun createTypeCodeFragment(text: String, context: PsiElement?): KtTypeCodeFragment {
|
||||
return KtTypeCodeFragment(project, "fragment.kt", text, context)
|
||||
}
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class Person(val name: String)
|
||||
|
||||
val persons = listOf("Jack", "Tom").map { Person(it) }
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class Person(val name: String)
|
||||
|
||||
val persons = listOf("Jack", "Tom").map(::Person)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This intention converts a callable function reference to the lambda expression
|
||||
</body>
|
||||
</html>
|
||||
@@ -1338,6 +1338,11 @@
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.ConvertReferenceToLambdaIntention</className>
|
||||
<category>Kotlin</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>org.jetbrains.kotlin.idea.intentions.RemoveSingleExpressionStringTemplateIntention</className>
|
||||
<category>Kotlin</category>
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.intentions
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.resolve.BindingContext.DOUBLE_COLON_LHS
|
||||
import org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.expressions.DoubleColonLHS
|
||||
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
||||
|
||||
class ConvertReferenceToLambdaIntention : SelfTargetingOffsetIndependentIntention<KtCallableReferenceExpression>(
|
||||
KtCallableReferenceExpression::class.java, "Convert reference to lambda"
|
||||
) {
|
||||
val SOURCE_RENDERER = IdeDescriptorRenderers.SOURCE_CODE
|
||||
|
||||
override fun applyTo(element: KtCallableReferenceExpression, editor: Editor?) {
|
||||
val context = element.analyze(BodyResolveMode.PARTIAL)
|
||||
val reference = element.callableReference
|
||||
val targetDescriptor = context[REFERENCE_TARGET, reference] as? CallableMemberDescriptor ?: return
|
||||
val parameterNamesAndTypes = targetDescriptor.valueParameters.map { it.name.asString() to it.type }
|
||||
val receiverExpression = element.receiverExpression
|
||||
val receiverType = receiverExpression?.let {
|
||||
(context[DOUBLE_COLON_LHS, it] as? DoubleColonLHS.Type)?.type ?: return
|
||||
}
|
||||
val receiverNameAndType = receiverType?.let { KotlinNameSuggester.suggestNamesByType(it, validator = {
|
||||
name -> name !in parameterNamesAndTypes.map { it.first }
|
||||
}, defaultName = "receiver").first() to it }
|
||||
|
||||
val referenceParent = element.parent
|
||||
val insideCall = referenceParent is KtValueArgument
|
||||
|
||||
val factory = KtPsiFactory(element)
|
||||
val targetName = reference.text
|
||||
val lambdaParameterNamesAndTypes = receiverNameAndType.singletonOrEmptyList() + parameterNamesAndTypes
|
||||
val receiverPrefix = receiverNameAndType?.let { it.first + "." } ?: ""
|
||||
val lambdaExpression = if (insideCall && lambdaParameterNamesAndTypes.size == 1) {
|
||||
factory.createLambdaExpression(
|
||||
parameters = "",
|
||||
body = when {
|
||||
receiverNameAndType != null ->
|
||||
if (targetDescriptor is PropertyDescriptor) "it.$targetName"
|
||||
else "it.$targetName()"
|
||||
else ->
|
||||
"$targetName(it)"
|
||||
}
|
||||
)
|
||||
}
|
||||
else {
|
||||
factory.createLambdaExpression(
|
||||
parameters = lambdaParameterNamesAndTypes.joinToString(separator = ", ") {
|
||||
if (insideCall) it.first
|
||||
else it.first + ": " + SOURCE_RENDERER.renderType(it.second)
|
||||
},
|
||||
body = if (targetDescriptor is PropertyDescriptor) {
|
||||
"$receiverPrefix$targetName"
|
||||
}
|
||||
else {
|
||||
parameterNamesAndTypes.joinToString(
|
||||
prefix = "$receiverPrefix$targetName(",
|
||||
separator = ", ",
|
||||
postfix = ")"
|
||||
) { it.first }
|
||||
}
|
||||
)
|
||||
}
|
||||
val lambdaResult = element.replace(lambdaExpression) as KtLambdaExpression
|
||||
ShortenReferences.DEFAULT.process(lambdaResult)
|
||||
|
||||
if (insideCall) {
|
||||
val call = referenceParent?.parent?.parent as? KtCallExpression ?: return
|
||||
val moveOutOfParenthesis = MoveLambdaOutsideParenthesesIntention()
|
||||
if (moveOutOfParenthesis.isApplicableTo(call, referenceParent.startOffset)) {
|
||||
moveOutOfParenthesis.applyTo(call, editor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isApplicableTo(element: KtCallableReferenceExpression): Boolean {
|
||||
val context = element.analyze(BodyResolveMode.PARTIAL)
|
||||
val receiverExpression = element.receiverExpression
|
||||
if (receiverExpression != null) {
|
||||
val lhs = context[DOUBLE_COLON_LHS, receiverExpression]
|
||||
if (lhs is DoubleColonLHS.Expression) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.intentions.ConvertReferenceToLambdaIntention
|
||||
@@ -0,0 +1,5 @@
|
||||
// IS_APPLICABLE: false
|
||||
|
||||
val x = 1
|
||||
// Not supported yet
|
||||
val y = <caret>x::hashCode
|
||||
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Person(val name: String)
|
||||
|
||||
val x = listOf("Jack", "Tom").map(<caret>::Person)
|
||||
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Person(val name: String)
|
||||
|
||||
val x = listOf("Jack", "Tom").map { Person(it) }
|
||||
@@ -0,0 +1,3 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x = listOf("123", "4567").map(<caret>String::toInt)
|
||||
@@ -0,0 +1,3 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x = listOf("123", "4567").map { it.toInt() }
|
||||
@@ -0,0 +1,3 @@
|
||||
val Any.name: String get() = toString()
|
||||
|
||||
val converted = <caret>Any::name
|
||||
@@ -0,0 +1,3 @@
|
||||
val Any.name: String get() = toString()
|
||||
|
||||
val converted = { any: Any -> any.name }
|
||||
@@ -0,0 +1,13 @@
|
||||
class Foo {
|
||||
class Bar {
|
||||
fun foo() {}
|
||||
}
|
||||
}
|
||||
|
||||
class Bar {
|
||||
fun foo() {}
|
||||
}
|
||||
|
||||
fun use() {
|
||||
val f: (Foo.Bar) -> Unit = <caret>Foo.Bar::foo
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
class Foo {
|
||||
class Bar {
|
||||
fun foo() {}
|
||||
}
|
||||
}
|
||||
|
||||
class Bar {
|
||||
fun foo() {}
|
||||
}
|
||||
|
||||
fun use() {
|
||||
val f: (Foo.Bar) -> Unit = { bar: Foo.Bar -> bar.foo() }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class Owner {
|
||||
inner class Inner
|
||||
|
||||
val x = <caret>Owner::Inner
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class Owner {
|
||||
inner class Inner
|
||||
|
||||
val x = { owner: Owner -> owner.Inner() }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x = listOf("123", "4567").map(<caret>String::length)
|
||||
@@ -0,0 +1,3 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x = listOf("123", "4567").map { it.length }
|
||||
@@ -0,0 +1,3 @@
|
||||
class Person(val name: String)
|
||||
|
||||
val reader = <caret>Person::name
|
||||
@@ -0,0 +1,3 @@
|
||||
class Person(val name: String)
|
||||
|
||||
val reader = { person: Person -> person.name }
|
||||
@@ -0,0 +1,3 @@
|
||||
fun Int?.foo() = this?.hashCode() ?: 0
|
||||
|
||||
val x = <caret>Int?::foo
|
||||
@@ -0,0 +1,3 @@
|
||||
fun Int?.foo() = this?.hashCode() ?: 0
|
||||
|
||||
val x = { i: Int? -> i.foo() }
|
||||
@@ -0,0 +1,3 @@
|
||||
fun Int.foo(x: Int) = this - x
|
||||
|
||||
val x = <caret>Int::foo
|
||||
@@ -0,0 +1,3 @@
|
||||
fun Int.foo(x: Int) = this - x
|
||||
|
||||
val x = { i: Int, x: Int -> i.foo(x) }
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo(y: Int) = y
|
||||
|
||||
val x = <caret>::foo
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo(y: Int) = y
|
||||
|
||||
val x = { y: Int -> foo(y) }
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo(x: Int, y: Int, z: Int) = x - y / z
|
||||
|
||||
val x = <caret>::foo
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo(x: Int, y: Int, z: Int) = x - y / z
|
||||
|
||||
val x = { x: Int, y: Int, z: Int -> foo(x, y, z) }
|
||||
@@ -0,0 +1 @@
|
||||
val x = <caret>Int::toString
|
||||
@@ -0,0 +1 @@
|
||||
val x = { i: Int -> i.toString() }
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Wrapper<T>(private val x: T) {
|
||||
fun unwrap() = x
|
||||
}
|
||||
|
||||
val unwrapped = listOf(Wrapper(1), Wrapper("B")).map(<caret>Wrapper<out Any>::unwrap)
|
||||
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
class Wrapper<T>(private val x: T) {
|
||||
fun unwrap() = x
|
||||
}
|
||||
|
||||
val unwrapped = listOf(Wrapper(1), Wrapper("B")).map { it.unwrap() }
|
||||
Vendored
+1
@@ -1,6 +1,7 @@
|
||||
// "Create function 'foo'" "false"
|
||||
// ACTION: Convert to expression body
|
||||
// ACTION: Rename reference
|
||||
// ACTION: Convert reference to lambda
|
||||
// ERROR: Unresolved reference: foo
|
||||
fun bar(n: Int) = "$n"
|
||||
|
||||
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
// "Create local variable 'foo'" "false"
|
||||
// ACTION: Rename reference
|
||||
// ACTION: Create function 'foo'
|
||||
// ACTION: Convert reference to lambda
|
||||
// ERROR: Unresolved reference: foo
|
||||
fun test(f: (Int) -> Int) {}
|
||||
|
||||
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
// "Create parameter 'foo'" "false"
|
||||
// ACTION: Rename reference
|
||||
// ACTION: Create function 'foo'
|
||||
// ACTION: Convert reference to lambda
|
||||
// ERROR: Unresolved reference: foo
|
||||
fun test(f: (Int) -> Int) {}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// "Create property 'foo'" "false"
|
||||
// ACTION: Rename reference
|
||||
// ACTION: Create function 'foo'
|
||||
// ACTION: Convert reference to lambda
|
||||
// ERROR: Unresolved reference: foo
|
||||
fun test(f: (Int) -> Int) {}
|
||||
|
||||
|
||||
@@ -4878,6 +4878,99 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/intentions/convertReferenceToLambda")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ConvertReferenceToLambda extends AbstractIntentionTest {
|
||||
public void testAllFilesPresentInConvertReferenceToLambda() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertReferenceToLambda"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("boundReference.kt")
|
||||
public void testBoundReference() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/boundReference.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("constructor.kt")
|
||||
public void testConstructor() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/constructor.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("conversion.kt")
|
||||
public void testConversion() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/conversion.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("extensionProperty.kt")
|
||||
public void testExtensionProperty() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/extensionProperty.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("fqNameForReceiver.kt")
|
||||
public void testFqNameForReceiver() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/fqNameForReceiver.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inner.kt")
|
||||
public void testInner() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/inner.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("length.kt")
|
||||
public void testLength() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/length.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("name.kt")
|
||||
public void testName() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/name.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nullable.kt")
|
||||
public void testNullable() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/nullable.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("receiverParameter.kt")
|
||||
public void testReceiverParameter() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/receiverParameter.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("threeParameters.kt")
|
||||
public void testThreeParameters() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/threeParameters.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("toString.kt")
|
||||
public void testToString() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/toString.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("unwrap.kt")
|
||||
public void testUnwrap() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/convertReferenceToLambda/unwrap.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/intentions/convertSealedClassToEnum")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user