Convert to scope function: Also convert call expression
#KT-28698 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
f15c8f78fa
commit
bd467f39d6
@@ -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)
|
||||
}
|
||||
+16
@@ -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)
|
||||
}
|
||||
+16
@@ -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)
|
||||
}
|
||||
+16
@@ -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)
|
||||
}
|
||||
+16
@@ -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)
|
||||
}
|
||||
+16
@@ -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)
|
||||
}
|
||||
+16
@@ -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)
|
||||
}
|
||||
+16
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user