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
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<List<PsiElement>, 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<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 }
.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<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);
}
@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");