Convert to scope function: also convert binary expression

#KT-30228 Fixed
This commit is contained in:
Toshiaki Kameyama
2019-03-07 18:48:46 +09:00
committed by Mikhail Glukhikh
parent e340af51df
commit 8728bc0820
15 changed files with 248 additions and 18 deletions
@@ -68,30 +68,37 @@ sealed class ConvertToScopeIntention(
ALSO, APPLY -> property ALSO, APPLY -> property
else -> first else -> first
} ?: return } ?: return
val parent = element.parent val parent = element.parent.let { if (it is KtBinaryExpression) it.parent else it }
val psiFactory = KtPsiFactory(element) val psiFactory = KtPsiFactory(element)
val (scopeFunctionCall, block) = psiFactory.createScopeFunctionCall(propertyOrFirst) ?: return val (scopeFunctionCall, block) = psiFactory.createScopeFunctionCall(propertyOrFirst) ?: return
block.addRange(property?.nextSibling ?: first, last) block.addRange(property?.nextSibling ?: first, last)
block.children.forEach { child -> block.children.forEach { replace(it, referenceName, psiFactory) }
when (child) { parent.addBefore(scopeFunctionCall, propertyOrFirst)
is KtDotQualifiedExpression -> { parent.deleteChildRange(propertyOrFirst, last)
val replaced = child.deleteFirstReceiver() }
if (scopeFunction.isParameterScope) {
replaced.replace(psiFactory.createExpressionByPattern("${scopeFunction.receiver}.$0", replaced)) 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 -> is KtCallExpression -> {
if (arg.getArgumentExpression()?.text == referenceName) { element.valueArguments.forEach { arg ->
arg.replace(psiFactory.createArgument(scopeFunction.receiver)) 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<List<PsiElement>, String>? { private fun KtExpression.collectTargetElements(): Pair<List<PsiElement>, String>? {
@@ -100,7 +107,8 @@ sealed class ConvertToScopeIntention(
val property = prevProperty() ?: return null val property = prevProperty() ?: return null
val referenceName = property.name ?: return null val referenceName = property.name ?: return null
val targets = property.collectTargetElements(referenceName, forward = true).toList() 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 targets to referenceName
} }
else -> { else -> {
@@ -116,7 +124,8 @@ sealed class ConvertToScopeIntention(
} }
private fun KtExpression.collectTargetElements(referenceName: String, forward: Boolean): Sequence<PsiElement> { private fun KtExpression.collectTargetElements(referenceName: String, forward: Boolean): Sequence<PsiElement> {
return siblings(forward, withItself = false) val parentOrThis = parent as? KtBinaryExpression ?: this
return parentOrThis.siblings(forward, withItself = false)
.filter { it !is PsiWhiteSpace && it !is PsiComment } .filter { it !is PsiWhiteSpace && it !is PsiComment }
.takeWhile { it.isTarget(referenceName) } .takeWhile { it.isTarget(referenceName) }
} }
@@ -135,6 +144,15 @@ sealed class ConvertToScopeIntention(
if (valueArguments.none { it.getArgumentExpression()?.text == referenceName }) return false if (valueArguments.none { it.getArgumentExpression()?.text == referenceName }) return false
if (lambdaArguments.isNotEmpty() || valueArguments.any { it.text == scopeFunction.receiver }) 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 -> else ->
return false return false
} }
@@ -142,7 +160,8 @@ sealed class ConvertToScopeIntention(
} }
private fun KtExpression.prevProperty(): KtProperty? { 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<KtExpression, KtBlockExpression>? { private fun KtPsiFactory.createScopeFunctionCall(element: PsiElement): Pair<KtExpression, KtBlockExpression>? {
@@ -0,0 +1,12 @@
// WITH_RUNTIME
class A {
fun foo() {}
fun bar(): Int = 1
}
fun test() {
val a = A()
a.foo()
<caret>a.bar() + 1
a.foo()
}
@@ -0,0 +1,13 @@
// WITH_RUNTIME
class A {
fun foo() {}
fun bar(): Int = 1
}
fun test() {
val a = A().apply {
foo()
bar() + 1
foo()
}
}
@@ -0,0 +1,12 @@
// WITH_RUNTIME
class A {
fun foo() {}
}
fun baz(a: A): Int = 1
fun test() {
val a = A()
a.foo()
1 + <caret>baz(a)
a.foo()
}
@@ -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()
}
}
@@ -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()
<caret>a.bar {} + baz(a)
a.foo()
}
@@ -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) + <caret>a.bar {}
a.foo()
}
@@ -0,0 +1,11 @@
// IS_APPLICABLE: false
class A {
fun foo() {}
}
fun test() {
val a = A()
a.foo()
<caret>1 + 1
a.foo()
}
@@ -0,0 +1,13 @@
// WITH_RUNTIME
class A {
infix fun foo(x: Any) = A()
}
fun main() {
val a = A()
<caret>a.foo(0)
a.foo(0)
a.foo(0) foo 0
a.foo(0)
a.foo(0)
}
@@ -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)
}
}
@@ -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)
<caret>a.foo(0) foo 0
a.foo(0)
a.foo(0)
}
@@ -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)
}
}
@@ -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)
<caret>a.foo(0)
}
@@ -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)
}
}
@@ -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); 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") @TestMetadata("callExpression.kt")
public void testCallExpression() throws Exception { public void testCallExpression() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToApply/callExpression.kt"); 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); 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") @TestMetadata("callExpression.kt")
public void testCallExpression() throws Exception { public void testCallExpression() throws Exception {
runTest("idea/testData/intentions/convertToScope/convertToRun/callExpression.kt"); runTest("idea/testData/intentions/convertToScope/convertToRun/callExpression.kt");