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:
+26
-16
@@ -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()
|
||||||
|
}
|
||||||
+8
@@ -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>}
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
// "Terminate preceding call with semicolon" "true"
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
"test".toString().toString().toString();
|
||||||
|
{}
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
// "Terminate preceding call with semicolon" "true"
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
"test".toString().toString().toString()
|
||||||
|
{<caret>"test"}.invoke().toString().toString()
|
||||||
|
}
|
||||||
+6
@@ -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()
|
||||||
|
}
|
||||||
+1
-1
@@ -6,4 +6,4 @@ fun test {
|
|||||||
foo()
|
foo()
|
||||||
// comment and formatting
|
// comment and formatting
|
||||||
{ { { foo() }<caret> } }()()()
|
{ { { foo() }<caret> } }()()()
|
||||||
}
|
}
|
||||||
+1
-1
@@ -6,4 +6,4 @@ fun test {
|
|||||||
foo();
|
foo();
|
||||||
// comment and formatting
|
// comment and formatting
|
||||||
{ { { foo() } } }()()()
|
{ { { foo() } } }()()()
|
||||||
}
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// "Terminate preceding call with semicolon" "true"
|
||||||
|
|
||||||
|
fun foo() {}
|
||||||
|
|
||||||
|
fun test {
|
||||||
|
{ { { foo() } } }()()()
|
||||||
|
// comment and formatting
|
||||||
|
{<caret>}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// "Terminate preceding call with semicolon" "true"
|
||||||
|
|
||||||
|
fun foo() {}
|
||||||
|
|
||||||
|
fun test {
|
||||||
|
{ { { foo() } } }()()();
|
||||||
|
// comment and formatting
|
||||||
|
{}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
// "Terminate preceding call with semicolon" "true"
|
||||||
|
|
||||||
|
fun foo() {}
|
||||||
|
|
||||||
|
fun test {
|
||||||
|
{ { { foo() } } }()()()
|
||||||
|
// comment and formatting
|
||||||
|
{ { { foo() }<caret> } }()()()
|
||||||
|
}
|
||||||
+9
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user