Update trailing lambda quickfix, add tests

Case of incorrect call expression as the receiver of dot-qualified expression
was not handled properly.

Now the following order is used:
- If parent is a call expression or if this call is a receiver of parent
dot-qualified expression, parent must get last lambda as a new receiver;
- Otherwise, if parent is a dot-qualified expression, this call is in selector position.
Then grandparent is checked for being call or dot-qualified expression. If so, trailing
lambda must become grandparent's new receiver. If not, trailing nodes become standalone
epressions after parent.
- Otherwise, incorrect call expression is a standalone expression, so trailing nodes
may be lifted and put after it.
This commit is contained in:
Pavel Kirpichenkov
2019-11-12 14:22:08 +03:00
parent f1934fa49d
commit 9a231bbbfa
16 changed files with 165 additions and 21 deletions
@@ -36,24 +36,34 @@ class AddSemicolonBeforeLambdaExpressionFix(element: KtLambdaExpression) : Kotli
lastLambdaAcceptor = parent, lastLambdaAcceptor = parent,
nodeBeforeSemicolon = callExpression nodeBeforeSemicolon = callExpression
) )
// Incorrect call is a part of dot-qualified expression before it // Incorrect call is a part of the dot-qualified expression before or after it
is KtDotQualifiedExpression -> { is KtDotQualifiedExpression -> {
val grandparent = parent.parent val grandparent = parent.parent
if (grandparent.isCallOrDotExpression) { when {
// Similar to call expression parent, but here correct lambda receiver is one level higher, // Call expression is a receiver of the dot expression. Give last lambda as a new receiver to that dot expression
// since our parent is the dot expression to the left parent.receiverExpression === callExpression -> {
liftTrailingNodesAndRelocateLastLambda( liftTrailingNodesAndRelocateLastLambda(
psiFactory, callExpression, endOfCall, psiFactory, callExpression, endOfCall,
lastLambdaAcceptor = grandparent, lastLambdaAcceptor = parent,
nodeBeforeSemicolon = parent nodeBeforeSemicolon = callExpression
) )
} else { }
// Extract trailing lambdas (and possible formatting) two levels higher, // Call expression is the right node of parent dot expression, possible call / dot to the right is a grandparent
// before dot-qualified expression to the left of this call expression // If grandparent is call or dot expression, last lambda becomes its new receiver
liftTrailingNodes( grandparent.isCallOrDotExpression -> {
psiFactory, callExpression, endOfCall, liftTrailingNodesAndRelocateLastLambda(
addNodesAfter = parent psiFactory, callExpression, endOfCall,
) lastLambdaAcceptor = grandparent,
nodeBeforeSemicolon = parent
)
}
// Parent is dot expression, but there is no call or dot after it, so just lift everything up
else -> {
liftTrailingNodes(
psiFactory, callExpression, endOfCall,
addNodesAfter = parent
)
}
} }
} }
// Simple case: extract all trailing nodes right after call - it is a standalone call expression // Simple case: extract all trailing nodes right after call - it is a standalone call expression
@@ -0,0 +1,8 @@
// "Terminate preceding call with semicolon" "true"
fun foo() {}
fun test() {
foo()
{<caret>"test"}.invoke().toString().toString()
}
@@ -0,0 +1,8 @@
// "Terminate preceding call with semicolon" "true"
fun foo() {}
fun test() {
foo();
{"test"}.invoke().toString().toString()
}
@@ -0,0 +1,6 @@
// "Terminate preceding call with semicolon" "true"
fun test() {
"test".toString().toString().toString()
{<caret>}
}
@@ -0,0 +1,6 @@
// "Terminate preceding call with semicolon" "true"
fun test() {
"test".toString().toString().toString();
{}
}
@@ -0,0 +1,6 @@
// "Terminate preceding call with semicolon" "true"
fun test() {
"test".toString().toString().toString()
{<caret>"test"}.invoke().toString().toString()
}
@@ -0,0 +1,6 @@
// "Terminate preceding call with semicolon" "true"
fun test() {
"test".toString().toString().toString();
{"test"}.invoke().toString().toString()
}
@@ -0,0 +1,14 @@
// "Terminate preceding call with semicolon" "true"
fun foo() {}
fun String.withLambda(lambda: (String) -> Unit) {}
fun test {
{ { "test" } }.invoke()().toString().withLambda()
// comment and formatting
{} // correct trailing lambda
/*
block comment
*/
{ { { foo() }<caret> } }.invoke()().invoke()
}
@@ -0,0 +1,14 @@
// "Terminate preceding call with semicolon" "true"
fun foo() {}
fun String.withLambda(lambda: (String) -> Unit) {}
fun test {
{ { "test" } }.invoke()().toString().withLambda()
// comment and formatting
{}; // correct trailing lambda
/*
block comment
*/
{ { { foo() } } }.invoke()().invoke()
}
@@ -6,4 +6,4 @@ fun test {
foo() foo()
// comment and formatting // comment and formatting
{ { { foo() }<caret> } }()()() { { { foo() }<caret> } }()()()
} }
@@ -6,4 +6,4 @@ fun test {
foo(); foo();
// comment and formatting // comment and formatting
{ { { foo() } } }()()() { { { foo() } } }()()()
} }
@@ -0,0 +1,9 @@
// "Terminate preceding call with semicolon" "true"
fun foo() {}
fun test {
{ { { foo() } } }()()()
// comment and formatting
{<caret>}
}
@@ -0,0 +1,9 @@
// "Terminate preceding call with semicolon" "true"
fun foo() {}
fun test {
{ { { foo() } } }()()();
// comment and formatting
{}
}
@@ -0,0 +1,9 @@
// "Terminate preceding call with semicolon" "true"
fun foo() {}
fun test {
{ { { foo() } } }()()()
// comment and formatting
{ { { foo() }<caret> } }()()()
}
@@ -0,0 +1,9 @@
// "Terminate preceding call with semicolon" "true"
fun foo() {}
fun test {
{ { { foo() } } }()()();
// comment and formatting
{ { { foo() } } }()()()
}
@@ -1113,14 +1113,44 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/basic.kt"); runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/basic.kt");
} }
@TestMetadata("chainedDotsEnd.kt")
public void testChainedDotsEnd() throws Exception {
runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsEnd.kt");
}
@TestMetadata("chainedDotsStart.kt")
public void testChainedDotsStart() throws Exception {
runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStart.kt");
}
@TestMetadata("chainedDotsStartEnd.kt")
public void testChainedDotsStartEnd() throws Exception {
runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStartEnd.kt");
}
@TestMetadata("complex.kt")
public void testComplex() throws Exception {
runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/complex.kt");
}
@TestMetadata("dotExpressionAndCallExpression.kt") @TestMetadata("dotExpressionAndCallExpression.kt")
public void testDotExpressionAndCallExpression() throws Exception { public void testDotExpressionAndCallExpression() throws Exception {
runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/dotExpressionAndCallExpression.kt"); runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/dotExpressionAndCallExpression.kt");
} }
@TestMetadata("multipleCalls.kt") @TestMetadata("multipleCallsEnd.kt")
public void testMultipleCalls() throws Exception { public void testMultipleCallsEnd() throws Exception {
runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCalls.kt"); runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsEnd.kt");
}
@TestMetadata("multipleCallsStart.kt")
public void testMultipleCallsStart() throws Exception {
runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStart.kt");
}
@TestMetadata("multipleCallsStartEnd.kt")
public void testMultipleCallsStartEnd() throws Exception {
runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStartEnd.kt");
} }
@TestMetadata("multipleLambdas.kt") @TestMetadata("multipleLambdas.kt")