Convert reference to lambda intention introduced #KT-13393 Fixed

This commit is contained in:
Mikhail Glukhikh
2016-11-21 18:04:02 +03:00
parent 27df76b02d
commit b111d1ba69
39 changed files with 346 additions and 15 deletions
@@ -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)
}
@@ -0,0 +1,3 @@
class Person(val name: String)
val persons = listOf("Jack", "Tom").map { Person(it) }
@@ -0,0 +1,3 @@
class Person(val name: String)
val persons = listOf("Jack", "Tom").map(::Person)
@@ -0,0 +1,5 @@
<html>
<body>
This intention converts a callable function reference to the lambda expression
</body>
</html>
+5
View File
@@ -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() }
@@ -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,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,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)