Convert to scope function: Also convert call expression

#KT-28698 Fixed
This commit is contained in:
Toshiaki Kameyama
2018-12-25 05:45:55 +03:00
committed by Mikhail Glukhikh
parent f15c8f78fa
commit bd467f39d6
24 changed files with 451 additions and 17 deletions
@@ -43,6 +43,11 @@ sealed class ConvertToScopeIntention(
when (element) {
is KtProperty ->
if (!element.isLocal) return false
is KtCallExpression -> {
if (element.parent is KtDotQualifiedExpression) return false
val propertyName = element.prevProperty()?.name ?: return false
if (!element.isTarget(propertyName)) return false
}
is KtDotQualifiedExpression -> {
if (element.parent is KtDotQualifiedExpression) return false
val name = element.getLeftMostReceiverExpression().text
@@ -55,7 +60,7 @@ sealed class ConvertToScopeIntention(
}
override fun applyTo(element: KtExpression, editor: Editor?) {
val targets = element.collectTargetElements() ?: return
val (targets, referenceName) = element.collectTargetElements() ?: return
val first = targets.firstOrNull() ?: return
val last = targets.lastOrNull() ?: return
val property = element.prevProperty()
@@ -68,33 +73,46 @@ sealed class ConvertToScopeIntention(
val psiFactory = KtPsiFactory(element)
val (scopeFunctionCall, block) = psiFactory.createScopeFunctionCall(propertyOrFirst) ?: return
block.addRange(property?.nextSibling ?: first, last)
block.children.filterIsInstance(KtDotQualifiedExpression::class.java)
.forEach {
val replaced = it.deleteFirstReceiver()
if (scopeFunction.isParameterScope) {
replaced.replace(psiFactory.createExpressionByPattern("${scopeFunction.receiver}.$0", replaced))
block.children.forEach { child ->
when (child) {
is KtDotQualifiedExpression -> {
val replaced = child.deleteFirstReceiver()
if (scopeFunction.isParameterScope) {
replaced.replace(psiFactory.createExpressionByPattern("${scopeFunction.receiver}.$0", replaced))
}
}
is KtCallExpression -> {
child.valueArguments.forEach { arg ->
if (arg.getArgumentExpression()?.text == referenceName) {
arg.replace(psiFactory.createArgument(scopeFunction.receiver))
}
}
}
}
}
parent.addBefore(scopeFunctionCall, propertyOrFirst)
parent.deleteChildRange(propertyOrFirst, last)
}
private fun KtExpression.collectTargetElements(): List<PsiElement>? {
val targets = when (scopeFunction) {
private fun KtExpression.collectTargetElements(): Pair<List<PsiElement>, String>? {
val (targets, referenceName) = when (scopeFunction) {
ALSO, APPLY -> {
val property = prevProperty() ?: return null
val referenceName = property.name ?: return null
property.collectTargetElements(referenceName, forward = true).toList().takeIf { this is KtProperty || this in it }
val targets = property.collectTargetElements(referenceName, forward = true).toList()
if (this !is KtProperty && this !in targets) return null
targets to referenceName
}
else -> {
if (this !is KtDotQualifiedExpression) return null
val referenceName = getLeftMostReceiverExpression().text
val prev = collectTargetElements(referenceName, forward = false).toList().reversed()
val next = collectTargetElements(referenceName, forward = true)
prev + listOf(this) + next
(prev + listOf(this) + next) to referenceName
}
}
return targets?.takeIf { it.size >= 2 }
if (targets.size < 2) return null
return targets to referenceName
}
private fun KtExpression.collectTargetElements(referenceName: String, forward: Boolean): Sequence<PsiElement> {
@@ -104,12 +122,22 @@ sealed class ConvertToScopeIntention(
}
private fun PsiElement.isTarget(referenceName: String): Boolean {
if (this !is KtDotQualifiedExpression) return false
val leftMostReceiver = getLeftMostReceiverExpression()
if (leftMostReceiver.text != referenceName) return false
if (leftMostReceiver.mainReference?.resolve() is PsiClass) return false
val callExpr = callExpression ?: return false
if (callExpr.lambdaArguments.isNotEmpty() || callExpr.valueArguments.any { it.text == scopeFunction.receiver }) return false
when (this) {
is KtDotQualifiedExpression -> {
val leftMostReceiver = getLeftMostReceiverExpression()
if (leftMostReceiver.text != referenceName) return false
if (leftMostReceiver.mainReference?.resolve() is PsiClass) return false
val callExpr = callExpression ?: return false
if (callExpr.lambdaArguments.isNotEmpty() || callExpr.valueArguments.any { it.text == scopeFunction.receiver }) return false
}
is KtCallExpression -> {
val valueArguments = this.valueArguments
if (valueArguments.none { it.getArgumentExpression()?.text == referenceName }) return false
if (lambdaArguments.isNotEmpty() || valueArguments.any { it.text == scopeFunction.receiver }) return false
}
else ->
return false
}
return !anyDescendantOfType<KtNameReferenceExpression> { it.text == scopeFunction.receiver }
}
@@ -0,0 +1,15 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo()<caret>
f.foo(1)
bar(2, f)
bar(3, f2)
}
@@ -0,0 +1,16 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo().also {
it.foo(1)
bar(2, it)
}
bar(3, f2)
}
@@ -0,0 +1,15 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo()
f.foo(1)<caret>
bar(2, f)
bar(3, f2)
}
@@ -0,0 +1,16 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo().also {
it.foo(1)
bar(2, it)
}
bar(3, f2)
}
@@ -0,0 +1,15 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo()
f.foo(1)
bar(2, f)<caret>
bar(3, f2)
}
@@ -0,0 +1,16 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo().also {
it.foo(1)
bar(2, it)
}
bar(3, f2)
}
@@ -0,0 +1,16 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo()
f.foo(1)
bar(2, f)
bar(3, f2)<caret>
}
@@ -0,0 +1,16 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
listOf(1).forEach {
val f = Foo()<caret>
f.foo(1)
bar(it, f)
}
}
@@ -0,0 +1,15 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo()<caret>
f.foo(1)
bar(2, f)
bar(3, f2)
}
@@ -0,0 +1,16 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo().apply {
foo(1)
bar(2, this)
}
bar(3, f2)
}
@@ -0,0 +1,15 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo()
f.foo(1)<caret>
bar(2, f)
bar(3, f2)
}
@@ -0,0 +1,16 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo().apply {
foo(1)
bar(2, this)
}
bar(3, f2)
}
@@ -0,0 +1,15 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo()
f.foo(1)
bar(2, f)<caret>
bar(3, f2)
}
@@ -0,0 +1,16 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo().apply {
foo(1)
bar(2, this)
}
bar(3, f2)
}
@@ -0,0 +1,16 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test() {
val f2 = Foo()
val f = Foo()
f.foo(1)
bar(2, f)
bar(3, f2)<caret>
}
@@ -0,0 +1,16 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
class Foo {
fun foo(i: Int) {}
fun test(f: Foo) {
val f = Foo()<caret>
f.foo(1)
bar(2, this)
}
}
fun bar(i: Int, f: Foo) {}
@@ -0,0 +1,14 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test(f: Foo) {
val f2 = Foo()
f.foo(1)<caret>
bar(2, f)
bar(3, f2)
}
@@ -0,0 +1,16 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test(f: Foo) {
val f2 = Foo()
f.run {
foo(1)
bar(2, this)
}
bar(3, f2)
}
@@ -0,0 +1,13 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test(f: Foo) {
f.foo(1)
bar(2, f)<caret>
}
@@ -0,0 +1,14 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test(f: Foo) {
val f2 = Foo()
f.foo(1)<caret>
bar(2, f)
bar(3, f2)
}
@@ -0,0 +1,16 @@
// WITH_RUNTIME
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test(f: Foo) {
val f2 = Foo()
with(f) {
foo(1)
bar(2, this)
}
bar(3, f2)
}
@@ -0,0 +1,13 @@
// WITH_RUNTIME
// IS_APPLICABLE: false
class Foo {
fun foo(i: Int) {}
}
fun bar(i: Int, f: Foo) {}
fun test(f: Foo) {
f.foo(1)
bar(2, f)<caret>
}
@@ -7458,6 +7458,31 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertToScope/convertToAlso"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("callExpression.kt")
public void testCallExpression() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToAlso/callExpression.kt");
}
@TestMetadata("callExpression2.kt")
public void testCallExpression2() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToAlso/callExpression2.kt");
}
@TestMetadata("callExpression3.kt")
public void testCallExpression3() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToAlso/callExpression3.kt");
}
@TestMetadata("callExpression4.kt")
public void testCallExpression4() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToAlso/callExpression4.kt");
}
@TestMetadata("callExpression5.kt")
public void testCallExpression5() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToAlso/callExpression5.kt");
}
@TestMetadata("itParameter.kt")
public void testItParameter() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToAlso/itParameter.kt");
@@ -7531,6 +7556,31 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertToScope/convertToApply"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("callExpression.kt")
public void testCallExpression() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToApply/callExpression.kt");
}
@TestMetadata("callExpression2.kt")
public void testCallExpression2() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToApply/callExpression2.kt");
}
@TestMetadata("callExpression3.kt")
public void testCallExpression3() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToApply/callExpression3.kt");
}
@TestMetadata("callExpression4.kt")
public void testCallExpression4() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToApply/callExpression4.kt");
}
@TestMetadata("callExpression5.kt")
public void testCallExpression5() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToApply/callExpression5.kt");
}
@TestMetadata("methodChain.kt")
public void testMethodChain() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToApply/methodChain.kt");
@@ -7604,6 +7654,16 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertToScope/convertToRun"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("callExpression.kt")
public void testCallExpression() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToRun/callExpression.kt");
}
@TestMetadata("callExpression2.kt")
public void testCallExpression2() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToRun/callExpression2.kt");
}
@TestMetadata("itReceiver.kt")
public void testItReceiver() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToRun/itReceiver.kt");
@@ -7692,6 +7752,16 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/convertToScope/convertToWith"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
}
@TestMetadata("callExpression.kt")
public void testCallExpression() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToWith/callExpression.kt");
}
@TestMetadata("callExpression2.kt")
public void testCallExpression2() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToWith/callExpression2.kt");
}
@TestMetadata("itReceiver.kt")
public void testItReceiver() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToWith/itReceiver.kt");