From 8728bc0820544227af24de35abd0f738df4f47cc Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Thu, 7 Mar 2019 18:48:46 +0900 Subject: [PATCH] Convert to scope function: also convert binary expression #KT-30228 Fixed --- .../intentions/ConvertToScopeIntention.kt | 55 +++++++++++++------ .../convertToApply/binaryExpression.kt | 12 ++++ .../convertToApply/binaryExpression.kt.after | 13 +++++ .../convertToApply/binaryExpression2.kt | 12 ++++ .../convertToApply/binaryExpression2.kt.after | 13 +++++ .../convertToApply/binaryExpression3.kt | 13 +++++ .../convertToApply/binaryExpression4.kt | 13 +++++ .../convertToApply/binaryExpression5.kt | 11 ++++ .../convertToRun/binaryExpression.kt | 13 +++++ .../convertToRun/binaryExpression.kt.after | 15 +++++ .../convertToRun/binaryExpression2.kt | 13 +++++ .../convertToRun/binaryExpression2.kt.after | 15 +++++ .../convertToRun/binaryExpression3.kt | 13 +++++ .../convertToRun/binaryExpression3.kt.after | 15 +++++ .../intentions/IntentionTestGenerated.java | 40 ++++++++++++++ 15 files changed, 248 insertions(+), 18 deletions(-) create mode 100644 idea/testData/intentions/convertToScope/convertToApply/binaryExpression.kt create mode 100644 idea/testData/intentions/convertToScope/convertToApply/binaryExpression.kt.after create mode 100644 idea/testData/intentions/convertToScope/convertToApply/binaryExpression2.kt create mode 100644 idea/testData/intentions/convertToScope/convertToApply/binaryExpression2.kt.after create mode 100644 idea/testData/intentions/convertToScope/convertToApply/binaryExpression3.kt create mode 100644 idea/testData/intentions/convertToScope/convertToApply/binaryExpression4.kt create mode 100644 idea/testData/intentions/convertToScope/convertToApply/binaryExpression5.kt create mode 100644 idea/testData/intentions/convertToScope/convertToRun/binaryExpression.kt create mode 100644 idea/testData/intentions/convertToScope/convertToRun/binaryExpression.kt.after create mode 100644 idea/testData/intentions/convertToScope/convertToRun/binaryExpression2.kt create mode 100644 idea/testData/intentions/convertToScope/convertToRun/binaryExpression2.kt.after create mode 100644 idea/testData/intentions/convertToScope/convertToRun/binaryExpression3.kt create mode 100644 idea/testData/intentions/convertToScope/convertToRun/binaryExpression3.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToScopeIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToScopeIntention.kt index b308081e884..85f9fa19d0f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToScopeIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertToScopeIntention.kt @@ -68,30 +68,37 @@ sealed class ConvertToScopeIntention( ALSO, APPLY -> property else -> first } ?: return - val parent = element.parent + val parent = element.parent.let { if (it is KtBinaryExpression) it.parent else it } val psiFactory = KtPsiFactory(element) val (scopeFunctionCall, block) = psiFactory.createScopeFunctionCall(propertyOrFirst) ?: return block.addRange(property?.nextSibling ?: first, last) - block.children.forEach { child -> - when (child) { - is KtDotQualifiedExpression -> { - val replaced = child.deleteFirstReceiver() - if (scopeFunction.isParameterScope) { - replaced.replace(psiFactory.createExpressionByPattern("${scopeFunction.receiver}.$0", replaced)) - } + block.children.forEach { replace(it, referenceName, psiFactory) } + parent.addBefore(scopeFunctionCall, propertyOrFirst) + parent.deleteChildRange(propertyOrFirst, last) + } + + private fun replace(element: PsiElement, referenceName: String, psiFactory: KtPsiFactory) { + when (element) { + is KtDotQualifiedExpression -> { + val replaced = element.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)) - } + } + is KtCallExpression -> { + element.valueArguments.forEach { arg -> + if (arg.getArgumentExpression()?.text == referenceName) { + arg.replace(psiFactory.createArgument(scopeFunction.receiver)) } } } + is KtBinaryExpression -> { + listOfNotNull(element.left, element.right).forEach { + replace(it, referenceName, psiFactory) + } + } } - parent.addBefore(scopeFunctionCall, propertyOrFirst) - parent.deleteChildRange(propertyOrFirst, last) } private fun KtExpression.collectTargetElements(): Pair, String>? { @@ -100,7 +107,8 @@ sealed class ConvertToScopeIntention( val property = prevProperty() ?: return null val referenceName = property.name ?: return null val targets = property.collectTargetElements(referenceName, forward = true).toList() - if (this !is KtProperty && this !in targets) return null + val parentOrThis = parent as? KtBinaryExpression ?: this + if (this !is KtProperty && parentOrThis !in targets) return null targets to referenceName } else -> { @@ -116,7 +124,8 @@ sealed class ConvertToScopeIntention( } private fun KtExpression.collectTargetElements(referenceName: String, forward: Boolean): Sequence { - return siblings(forward, withItself = false) + val parentOrThis = parent as? KtBinaryExpression ?: this + return parentOrThis.siblings(forward, withItself = false) .filter { it !is PsiWhiteSpace && it !is PsiComment } .takeWhile { it.isTarget(referenceName) } } @@ -135,6 +144,15 @@ sealed class ConvertToScopeIntention( if (valueArguments.none { it.getArgumentExpression()?.text == referenceName }) return false if (lambdaArguments.isNotEmpty() || valueArguments.any { it.text == scopeFunction.receiver }) return false } + is KtBinaryExpression -> { + val left = this.left ?: return false + val right = this.right ?: return false + if (left !is KtDotQualifiedExpression && left !is KtCallExpression + && right !is KtDotQualifiedExpression && right !is KtCallExpression + ) return false + if ((left is KtDotQualifiedExpression || left is KtCallExpression) && !left.isTarget(referenceName)) return false + if ((right is KtDotQualifiedExpression || right is KtCallExpression) && !right.isTarget(referenceName)) return false + } else -> return false } @@ -142,7 +160,8 @@ sealed class ConvertToScopeIntention( } private fun KtExpression.prevProperty(): KtProperty? { - return siblings(forward = false, withItself = true).firstOrNull { it is KtProperty && it.isLocal } as? KtProperty + val parentOrThis = parent as? KtBinaryExpression ?: this + return parentOrThis.siblings(forward = false, withItself = true).firstOrNull { it is KtProperty && it.isLocal } as? KtProperty } private fun KtPsiFactory.createScopeFunctionCall(element: PsiElement): Pair? { diff --git a/idea/testData/intentions/convertToScope/convertToApply/binaryExpression.kt b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression.kt new file mode 100644 index 00000000000..b5b1f9cefa2 --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression.kt @@ -0,0 +1,12 @@ +// WITH_RUNTIME +class A { + fun foo() {} + fun bar(): Int = 1 +} + +fun test() { + val a = A() + a.foo() + a.bar() + 1 + a.foo() +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToScope/convertToApply/binaryExpression.kt.after b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression.kt.after new file mode 100644 index 00000000000..5827944a734 --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression.kt.after @@ -0,0 +1,13 @@ +// WITH_RUNTIME +class A { + fun foo() {} + fun bar(): Int = 1 +} + +fun test() { + val a = A().apply { + foo() + bar() + 1 + foo() + } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToScope/convertToApply/binaryExpression2.kt b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression2.kt new file mode 100644 index 00000000000..1ce47ca5ba8 --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression2.kt @@ -0,0 +1,12 @@ +// WITH_RUNTIME +class A { + fun foo() {} +} +fun baz(a: A): Int = 1 + +fun test() { + val a = A() + a.foo() + 1 + baz(a) + a.foo() +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToScope/convertToApply/binaryExpression2.kt.after b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression2.kt.after new file mode 100644 index 00000000000..62c03254fa4 --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression2.kt.after @@ -0,0 +1,13 @@ +// WITH_RUNTIME +class A { + fun foo() {} +} +fun baz(a: A): Int = 1 + +fun test() { + val a = A().apply { + foo() + 1 + baz(this) + foo() + } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToScope/convertToApply/binaryExpression3.kt b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression3.kt new file mode 100644 index 00000000000..ecaf12a37e0 --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression3.kt @@ -0,0 +1,13 @@ +// IS_APPLICABLE: false +class A { + fun foo() {} + fun bar(lambda: () -> Unit = {}): Int = 1 +} +fun baz(a: A): Int = 1 + +fun test() { + val a = A() + a.foo() + a.bar {} + baz(a) + a.foo() +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToScope/convertToApply/binaryExpression4.kt b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression4.kt new file mode 100644 index 00000000000..13717c61860 --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression4.kt @@ -0,0 +1,13 @@ +// IS_APPLICABLE: false +class A { + fun foo() {} + fun bar(lambda: () -> Unit = {}): Int = 1 +} +fun baz(a: A): Int = 1 + +fun test() { + val a = A() + a.foo() + baz(a) + a.bar {} + a.foo() +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToScope/convertToApply/binaryExpression5.kt b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression5.kt new file mode 100644 index 00000000000..6fc2eecb8a5 --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToApply/binaryExpression5.kt @@ -0,0 +1,11 @@ +// IS_APPLICABLE: false +class A { + fun foo() {} +} + +fun test() { + val a = A() + a.foo() + 1 + 1 + a.foo() +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToScope/convertToRun/binaryExpression.kt b/idea/testData/intentions/convertToScope/convertToRun/binaryExpression.kt new file mode 100644 index 00000000000..97193481b84 --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToRun/binaryExpression.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +class A { + infix fun foo(x: Any) = A() +} + +fun main() { + val a = A() + a.foo(0) + a.foo(0) + a.foo(0) foo 0 + a.foo(0) + a.foo(0) +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToScope/convertToRun/binaryExpression.kt.after b/idea/testData/intentions/convertToScope/convertToRun/binaryExpression.kt.after new file mode 100644 index 00000000000..9b82865b407 --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToRun/binaryExpression.kt.after @@ -0,0 +1,15 @@ +// WITH_RUNTIME +class A { + infix fun foo(x: Any) = A() +} + +fun main() { + val a = A() + a.run { + foo(0) + foo(0) + foo(0) foo 0 + foo(0) + foo(0) + } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToScope/convertToRun/binaryExpression2.kt b/idea/testData/intentions/convertToScope/convertToRun/binaryExpression2.kt new file mode 100644 index 00000000000..9098acc15b2 --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToRun/binaryExpression2.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +class A { + infix fun foo(x: Any) = A() +} + +fun main() { + val a = A() + a.foo(0) + a.foo(0) + a.foo(0) foo 0 + a.foo(0) + a.foo(0) +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToScope/convertToRun/binaryExpression2.kt.after b/idea/testData/intentions/convertToScope/convertToRun/binaryExpression2.kt.after new file mode 100644 index 00000000000..9b82865b407 --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToRun/binaryExpression2.kt.after @@ -0,0 +1,15 @@ +// WITH_RUNTIME +class A { + infix fun foo(x: Any) = A() +} + +fun main() { + val a = A() + a.run { + foo(0) + foo(0) + foo(0) foo 0 + foo(0) + foo(0) + } +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToScope/convertToRun/binaryExpression3.kt b/idea/testData/intentions/convertToScope/convertToRun/binaryExpression3.kt new file mode 100644 index 00000000000..1609eef6e3d --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToRun/binaryExpression3.kt @@ -0,0 +1,13 @@ +// WITH_RUNTIME +class A { + infix fun foo(x: Any) = A() +} + +fun main() { + val a = A() + a.foo(0) + a.foo(0) + a.foo(0) foo 0 + a.foo(0) + a.foo(0) +} \ No newline at end of file diff --git a/idea/testData/intentions/convertToScope/convertToRun/binaryExpression3.kt.after b/idea/testData/intentions/convertToScope/convertToRun/binaryExpression3.kt.after new file mode 100644 index 00000000000..9b82865b407 --- /dev/null +++ b/idea/testData/intentions/convertToScope/convertToRun/binaryExpression3.kt.after @@ -0,0 +1,15 @@ +// WITH_RUNTIME +class A { + infix fun foo(x: Any) = A() +} + +fun main() { + val a = A() + a.run { + foo(0) + foo(0) + foo(0) foo 0 + foo(0) + foo(0) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index e44d5f0be66..354eb61bb54 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -7601,6 +7601,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("binaryExpression.kt") + public void testBinaryExpression() throws Exception { + runTest("idea/testData/intentions/convertToScope/convertToApply/binaryExpression.kt"); + } + + @TestMetadata("binaryExpression2.kt") + public void testBinaryExpression2() throws Exception { + runTest("idea/testData/intentions/convertToScope/convertToApply/binaryExpression2.kt"); + } + + @TestMetadata("binaryExpression3.kt") + public void testBinaryExpression3() throws Exception { + runTest("idea/testData/intentions/convertToScope/convertToApply/binaryExpression3.kt"); + } + + @TestMetadata("binaryExpression4.kt") + public void testBinaryExpression4() throws Exception { + runTest("idea/testData/intentions/convertToScope/convertToApply/binaryExpression4.kt"); + } + + @TestMetadata("binaryExpression5.kt") + public void testBinaryExpression5() throws Exception { + runTest("idea/testData/intentions/convertToScope/convertToApply/binaryExpression5.kt"); + } + @TestMetadata("callExpression.kt") public void testCallExpression() throws Exception { runTest("idea/testData/intentions/convertToScope/convertToApply/callExpression.kt"); @@ -7704,6 +7729,21 @@ 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("binaryExpression.kt") + public void testBinaryExpression() throws Exception { + runTest("idea/testData/intentions/convertToScope/convertToRun/binaryExpression.kt"); + } + + @TestMetadata("binaryExpression2.kt") + public void testBinaryExpression2() throws Exception { + runTest("idea/testData/intentions/convertToScope/convertToRun/binaryExpression2.kt"); + } + + @TestMetadata("binaryExpression3.kt") + public void testBinaryExpression3() throws Exception { + runTest("idea/testData/intentions/convertToScope/convertToRun/binaryExpression3.kt"); + } + @TestMetadata("callExpression.kt") public void testCallExpression() throws Exception { runTest("idea/testData/intentions/convertToScope/convertToRun/callExpression.kt");