From 9a231bbbfa408248b2edd5dc0ee7f942bbaa519c Mon Sep 17 00:00:00 2001 From: Pavel Kirpichenkov Date: Tue, 12 Nov 2019 14:22:08 +0300 Subject: [PATCH] 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. --- .../AddSemicolonBeforeLambdaExpressionFix.kt | 42 ++++++++++++------- .../chainedDotsEnd.kt | 8 ++++ .../chainedDotsEnd.kt.after | 8 ++++ .../chainedDotsStart.kt | 6 +++ .../chainedDotsStart.kt.after | 6 +++ .../chainedDotsStartEnd.kt | 6 +++ .../chainedDotsStartEnd.kt.after | 6 +++ .../complex.kt | 14 +++++++ .../complex.kt.after | 14 +++++++ .../{multipleCalls.kt => multipleCallsEnd.kt} | 2 +- ...lls.kt.after => multipleCallsEnd.kt.after} | 2 +- .../multipleCallsStart.kt | 9 ++++ .../multipleCallsStart.kt.after | 9 ++++ .../multipleCallsStartEnd.kt | 9 ++++ .../multipleCallsStartEnd.kt.after | 9 ++++ .../idea/quickfix/QuickFixTestGenerated.java | 36 ++++++++++++++-- 16 files changed, 165 insertions(+), 21 deletions(-) create mode 100644 idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsEnd.kt create mode 100644 idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsEnd.kt.after create mode 100644 idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStart.kt create mode 100644 idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStart.kt.after create mode 100644 idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStartEnd.kt create mode 100644 idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStartEnd.kt.after create mode 100644 idea/testData/quickfix/addSemicolonBeforeLambdaExpression/complex.kt create mode 100644 idea/testData/quickfix/addSemicolonBeforeLambdaExpression/complex.kt.after rename idea/testData/quickfix/addSemicolonBeforeLambdaExpression/{multipleCalls.kt => multipleCallsEnd.kt} (98%) rename idea/testData/quickfix/addSemicolonBeforeLambdaExpression/{multipleCalls.kt.after => multipleCallsEnd.kt.after} (98%) create mode 100644 idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStart.kt create mode 100644 idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStart.kt.after create mode 100644 idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStartEnd.kt create mode 100644 idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStartEnd.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddSemicolonBeforeLambdaExpressionFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddSemicolonBeforeLambdaExpressionFix.kt index b43675cdfc0..76c85467926 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/AddSemicolonBeforeLambdaExpressionFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AddSemicolonBeforeLambdaExpressionFix.kt @@ -36,24 +36,34 @@ class AddSemicolonBeforeLambdaExpressionFix(element: KtLambdaExpression) : Kotli lastLambdaAcceptor = parent, 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 -> { val grandparent = parent.parent - if (grandparent.isCallOrDotExpression) { - // Similar to call expression parent, but here correct lambda receiver is one level higher, - // since our parent is the dot expression to the left - liftTrailingNodesAndRelocateLastLambda( - psiFactory, callExpression, endOfCall, - lastLambdaAcceptor = grandparent, - nodeBeforeSemicolon = parent - ) - } else { - // Extract trailing lambdas (and possible formatting) two levels higher, - // before dot-qualified expression to the left of this call expression - liftTrailingNodes( - psiFactory, callExpression, endOfCall, - addNodesAfter = parent - ) + when { + // Call expression is a receiver of the dot expression. Give last lambda as a new receiver to that dot expression + parent.receiverExpression === callExpression -> { + liftTrailingNodesAndRelocateLastLambda( + psiFactory, callExpression, endOfCall, + lastLambdaAcceptor = parent, + nodeBeforeSemicolon = callExpression + ) + } + // Call expression is the right node of parent dot expression, possible call / dot to the right is a grandparent + // If grandparent is call or dot expression, last lambda becomes its new receiver + grandparent.isCallOrDotExpression -> { + liftTrailingNodesAndRelocateLastLambda( + 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 diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsEnd.kt b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsEnd.kt new file mode 100644 index 00000000000..e10abd54c54 --- /dev/null +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsEnd.kt @@ -0,0 +1,8 @@ +// "Terminate preceding call with semicolon" "true" + +fun foo() {} + +fun test() { + foo() + {"test"}.invoke().toString().toString() +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsEnd.kt.after b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsEnd.kt.after new file mode 100644 index 00000000000..d7f092392ca --- /dev/null +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsEnd.kt.after @@ -0,0 +1,8 @@ +// "Terminate preceding call with semicolon" "true" + +fun foo() {} + +fun test() { + foo(); + {"test"}.invoke().toString().toString() +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStart.kt b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStart.kt new file mode 100644 index 00000000000..3df67cddd2d --- /dev/null +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStart.kt @@ -0,0 +1,6 @@ +// "Terminate preceding call with semicolon" "true" + +fun test() { + "test".toString().toString().toString() + {} +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStart.kt.after b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStart.kt.after new file mode 100644 index 00000000000..6b21e9f96c7 --- /dev/null +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStart.kt.after @@ -0,0 +1,6 @@ +// "Terminate preceding call with semicolon" "true" + +fun test() { + "test".toString().toString().toString(); + {} +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStartEnd.kt b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStartEnd.kt new file mode 100644 index 00000000000..b34846bd3a9 --- /dev/null +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStartEnd.kt @@ -0,0 +1,6 @@ +// "Terminate preceding call with semicolon" "true" + +fun test() { + "test".toString().toString().toString() + {"test"}.invoke().toString().toString() +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStartEnd.kt.after b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStartEnd.kt.after new file mode 100644 index 00000000000..8f63100b0fa --- /dev/null +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/chainedDotsStartEnd.kt.after @@ -0,0 +1,6 @@ +// "Terminate preceding call with semicolon" "true" + +fun test() { + "test".toString().toString().toString(); + {"test"}.invoke().toString().toString() +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/complex.kt b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/complex.kt new file mode 100644 index 00000000000..2e1fcb22697 --- /dev/null +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/complex.kt @@ -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() +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/complex.kt.after b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/complex.kt.after new file mode 100644 index 00000000000..2e34e8931c9 --- /dev/null +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/complex.kt.after @@ -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() +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCalls.kt b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsEnd.kt similarity index 98% rename from idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCalls.kt rename to idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsEnd.kt index faa993ab598..8ef5e0d43a0 100644 --- a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCalls.kt +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsEnd.kt @@ -6,4 +6,4 @@ fun test { foo() // comment and formatting { { { foo() } } }()()() -} \ No newline at end of file +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCalls.kt.after b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsEnd.kt.after similarity index 98% rename from idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCalls.kt.after rename to idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsEnd.kt.after index 875c2383113..9a1c9d362ec 100644 --- a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCalls.kt.after +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsEnd.kt.after @@ -6,4 +6,4 @@ fun test { foo(); // comment and formatting { { { foo() } } }()()() -} \ No newline at end of file +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStart.kt b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStart.kt new file mode 100644 index 00000000000..6a393840a3c --- /dev/null +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStart.kt @@ -0,0 +1,9 @@ +// "Terminate preceding call with semicolon" "true" + +fun foo() {} + +fun test { + { { { foo() } } }()()() + // comment and formatting + {} +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStart.kt.after b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStart.kt.after new file mode 100644 index 00000000000..c4822d94101 --- /dev/null +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStart.kt.after @@ -0,0 +1,9 @@ +// "Terminate preceding call with semicolon" "true" + +fun foo() {} + +fun test { + { { { foo() } } }()()(); + // comment and formatting + {} +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStartEnd.kt b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStartEnd.kt new file mode 100644 index 00000000000..2ea772b19ed --- /dev/null +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStartEnd.kt @@ -0,0 +1,9 @@ +// "Terminate preceding call with semicolon" "true" + +fun foo() {} + +fun test { + { { { foo() } } }()()() + // comment and formatting + { { { foo() } } }()()() +} diff --git a/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStartEnd.kt.after b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStartEnd.kt.after new file mode 100644 index 00000000000..7abf0657aa9 --- /dev/null +++ b/idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCallsStartEnd.kt.after @@ -0,0 +1,9 @@ +// "Terminate preceding call with semicolon" "true" + +fun foo() {} + +fun test { + { { { foo() } } }()()(); + // comment and formatting + { { { foo() } } }()()() +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java index eb0e0143fb8..1c1952cfee9 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java @@ -1113,14 +1113,44 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest { 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") public void testDotExpressionAndCallExpression() throws Exception { runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/dotExpressionAndCallExpression.kt"); } - @TestMetadata("multipleCalls.kt") - public void testMultipleCalls() throws Exception { - runTest("idea/testData/quickfix/addSemicolonBeforeLambdaExpression/multipleCalls.kt"); + @TestMetadata("multipleCallsEnd.kt") + public void testMultipleCallsEnd() throws Exception { + 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")