Skip to content

Commit 92a7635

Browse files
committed
KT-17970 Intention actions to format parameter/argument list placing each on separate line
#KT-17970 Fixed
1 parent 47fec6c commit 92a7635

File tree

28 files changed

+293
-0
lines changed

28 files changed

+293
-0
lines changed

compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameterList.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.intellij.psi.PsiElement;
2121
import org.jetbrains.annotations.NotNull;
2222
import org.jetbrains.annotations.Nullable;
23+
import org.jetbrains.kotlin.lexer.KtTokens;
2324
import org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub;
2425
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes;
2526

@@ -78,4 +79,14 @@ public KtFunction getOwnerFunction() {
7879
if (!(parent instanceof KtFunction)) return null;
7980
return (KtFunction) parent;
8081
}
82+
83+
@Nullable
84+
public PsiElement getRightParenthesis() {
85+
return findChildByType(KtTokens.RPAR);
86+
}
87+
88+
@Nullable
89+
public PsiElement getLeftParenthesis() {
90+
return findChildByType(KtTokens.LPAR);
91+
}
8192
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fun foo() {
2+
bar(
3+
<spot>1,
4+
"abcd",
5+
false</spot>
6+
)
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fun foo() {
2+
bar(<spot>1, "abcd", false</spot>)
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<html>
2+
<body>
3+
This intention formats a function call placing each argument on separate line
4+
</body>
5+
</html>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fun foo(
2+
<spot>param1: String,
3+
param2: Int,
4+
param3: Any</spot>
5+
) {
6+
bar()
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fun foo(<spot>param1: String, param2: Int, param3: Any</spot>) {
2+
bar()
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<html>
2+
<body>
3+
This intention formats parameter list in a declaration placing each parameter on separate line
4+
</body>
5+
</html>

idea/src/META-INF/plugin.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,6 +1575,16 @@
15751575
<category>Kotlin</category>
15761576
</intentionAction>
15771577

1578+
<intentionAction>
1579+
<className>org.jetbrains.kotlin.idea.intentions.ChopParameterListIntention</className>
1580+
<category>Kotlin</category>
1581+
</intentionAction>
1582+
1583+
<intentionAction>
1584+
<className>org.jetbrains.kotlin.idea.intentions.ChopArgumentListIntention</className>
1585+
<category>Kotlin</category>
1586+
</intentionAction>
1587+
15781588
<localInspection implementationClass="org.jetbrains.kotlin.idea.intentions.ObjectLiteralToLambdaInspection"
15791589
displayName="Object literal can be converted to lambda"
15801590
groupName="Kotlin"
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2010-2017 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.idea.intentions
18+
19+
import com.intellij.codeInsight.intention.LowPriorityAction
20+
import com.intellij.openapi.editor.Editor
21+
import com.intellij.psi.PsiDocumentManager
22+
import com.intellij.psi.PsiWhiteSpace
23+
import com.intellij.psi.codeStyle.CodeStyleManager
24+
import com.intellij.psi.util.PsiTreeUtil
25+
import org.jetbrains.kotlin.lexer.KtTokens
26+
import org.jetbrains.kotlin.psi.*
27+
import org.jetbrains.kotlin.psi.psiUtil.allChildren
28+
import org.jetbrains.kotlin.psi.psiUtil.siblings
29+
import org.jetbrains.kotlin.psi.psiUtil.startOffset
30+
31+
abstract class AbstractChopListIntention<TList : KtElement, TElement : KtElement>(
32+
private val listClass: Class<TList>,
33+
private val elementClass: Class<TElement>,
34+
text: String
35+
) : SelfTargetingOffsetIndependentIntention<TList>(listClass, text), LowPriorityAction {
36+
37+
override fun isApplicableTo(element: TList): Boolean {
38+
val elements = element.elements()
39+
if (elements.size <= 1) return false
40+
if (elements.dropLast(1).all { hasLineBreakAfter(it) }) return false
41+
return true
42+
}
43+
44+
override fun applyTo(list: TList, editor: Editor?) {
45+
val project = list.project
46+
val document = editor!!.document
47+
val startOffset = list.startOffset
48+
49+
val elements = list.elements()
50+
if (!hasLineBreakAfter(elements.last())) {
51+
val rpar = list.allChildren.lastOrNull { it.node.elementType == KtTokens.RPAR }
52+
rpar?.startOffset?.let { document.insertString(it, "\n") }
53+
}
54+
55+
for (element in elements.asReversed()) {
56+
if (!hasLineBreakBefore(element)) {
57+
document.insertString(element.startOffset, "\n")
58+
}
59+
}
60+
61+
val documentManager = PsiDocumentManager.getInstance(project)
62+
documentManager.commitDocument(document)
63+
val psiFile = documentManager.getPsiFile(document)!!
64+
val newList = PsiTreeUtil.getParentOfType(psiFile.findElementAt(startOffset)!!, listClass)!!
65+
CodeStyleManager.getInstance(project).adjustLineIndent(psiFile, newList.textRange)
66+
}
67+
68+
private fun hasLineBreakAfter(element: TElement): Boolean {
69+
return element
70+
.siblings(withItself = false)
71+
.takeWhile { !elementClass.isInstance(it) }
72+
.any { it is PsiWhiteSpace && it.textContains('\n') }
73+
}
74+
75+
private fun hasLineBreakBefore(element: TElement): Boolean {
76+
return element
77+
.siblings(withItself = false, forward = false)
78+
.takeWhile { !elementClass.isInstance(it) }
79+
.any { it is PsiWhiteSpace && it.textContains('\n') }
80+
}
81+
82+
private fun TList.elements(): List<TElement> {
83+
return allChildren
84+
.filter { elementClass.isInstance(it) }
85+
.map {
86+
@Suppress("UNCHECKED_CAST")
87+
it as TElement
88+
}
89+
.toList()
90+
}
91+
}
92+
93+
class ChopParameterListIntention : AbstractChopListIntention<KtParameterList, KtParameter>(
94+
KtParameterList::class.java,
95+
KtParameter::class.java,
96+
"Put parameters on separate lines"
97+
) {
98+
override fun isApplicableTo(element: KtParameterList): Boolean {
99+
if (element.parent is KtFunctionLiteral) return false
100+
return super.isApplicableTo(element)
101+
}
102+
}
103+
104+
class ChopArgumentListIntention : AbstractChopListIntention<KtValueArgumentList, KtValueArgument>(
105+
KtValueArgumentList::class.java,
106+
KtValueArgument::class.java,
107+
"Put arguments on separate lines"
108+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.jetbrains.kotlin.idea.intentions.ChopArgumentListIntention
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fun f() {
2+
foo(<caret>1, "a", 2)
3+
}
4+
5+
fun foo(p1: Int, p2: String, p3: Int){}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fun f() {
2+
foo(
3+
1,
4+
"a",
5+
2
6+
)
7+
}
8+
9+
fun foo(p1: Int, p2: String, p3: Int){}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.jetbrains.kotlin.idea.intentions.ChopParameterListIntention
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// IS_APPLICABLE: false
2+
3+
fun foo(
4+
<caret>c: Char,
5+
b: Boolean
6+
) {
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fun foo(p: Int, c: Char,
2+
b: <caret>Boolean) {
3+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fun foo(
2+
p: Int,
3+
c: Char,
4+
b: <caret>Boolean
5+
) {
6+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
fun foo(
2+
p: Int, c: Char, b: <caret>Boolean
3+
) {
4+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fun foo(
2+
p: Int,
3+
c: Char,
4+
b: <caret>Boolean
5+
) {
6+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// IS_APPLICABLE: false
2+
3+
fun foo(<caret>c: Char) {
4+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fun foo(p: Int, c: Char, b: <caret>Boolean) {
2+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fun foo(
2+
p: Int,
3+
c: Char,
4+
b: <caret>Boolean
5+
) {
6+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fun foo(p: Int, <caret>c: Char) {
2+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fun foo(
2+
p: Int,
3+
<caret> c: Char
4+
) {
5+
}

idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithExtraArgs.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// ACTION: Add parameter to constructor 'Foo'
44
// ACTION: Create secondary constructor
55
// ERROR: Too many arguments for public constructor Foo(a: Int) defined in Foo
6+
// ACTION: Put arguments on separate lines
67
// ACTION: To raw string literal
78

89
class Foo(a: Int)

idea/testData/quickfix/createFromUsage/createFunction/call/callInAnnotationEntry.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// ACTION: Make internal
44
// ACTION: Make private
55
// ACTION: Rename reference
6+
// ACTION: Put arguments on separate lines
67
// ACTION: Convert to expression body
78
// ERROR: Unresolved reference: foo
89
// ERROR: Unresolved reference: bar

idea/testData/quickfix/renameToUnderscore/functionExpressionParameterNoRemoveParameter.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// ACTION: Convert parameter to receiver
44
// ACTION: Rename to _
55
// ACTION: Specify return type explicitly
6+
// ACTION: Put parameters on separate lines
67

78
fun foo(block: (String, Int) -> Unit) {
89
block("", 1)

idea/testData/quickfix/typeMismatch/tooManyArgumentsException.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// ACTION: Create function 'join'
55
// ACTION: Flip ','
66
// ACTION: Introduce local variable
7+
// ACTION: Put arguments on separate lines
78

89
//this test checks that there is no ArrayIndexOutOfBoundsException when there are more arguments than parameters
910
fun <T> array1(vararg a : T) = a

idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3144,6 +3144,75 @@ public void testTypeAlias() throws Exception {
31443144
}
31453145
}
31463146

3147+
@TestMetadata("idea/testData/intentions/chop")
3148+
@TestDataPath("$PROJECT_ROOT")
3149+
@RunWith(JUnit3RunnerWithInners.class)
3150+
public static class Chop extends AbstractIntentionTest {
3151+
public void testAllFilesPresentInChop() throws Exception {
3152+
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/chop"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
3153+
}
3154+
3155+
@TestMetadata("idea/testData/intentions/chop/argumentList")
3156+
@TestDataPath("$PROJECT_ROOT")
3157+
@RunWith(JUnit3RunnerWithInners.class)
3158+
public static class ArgumentList extends AbstractIntentionTest {
3159+
public void testAllFilesPresentInArgumentList() throws Exception {
3160+
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/chop/argumentList"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
3161+
}
3162+
3163+
@TestMetadata("threeArgs.kt")
3164+
public void testThreeArgs() throws Exception {
3165+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/argumentList/threeArgs.kt");
3166+
doTest(fileName);
3167+
}
3168+
}
3169+
3170+
@TestMetadata("idea/testData/intentions/chop/parameterList")
3171+
@TestDataPath("$PROJECT_ROOT")
3172+
@RunWith(JUnit3RunnerWithInners.class)
3173+
public static class ParameterList extends AbstractIntentionTest {
3174+
public void testAllFilesPresentInParameterList() throws Exception {
3175+
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/chop/parameterList"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
3176+
}
3177+
3178+
@TestMetadata("hasAllLineBreaks.kt")
3179+
public void testHasAllLineBreaks() throws Exception {
3180+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/parameterList/hasAllLineBreaks.kt");
3181+
doTest(fileName);
3182+
}
3183+
3184+
@TestMetadata("hasSomeLineBreaks1.kt")
3185+
public void testHasSomeLineBreaks1() throws Exception {
3186+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/parameterList/hasSomeLineBreaks1.kt");
3187+
doTest(fileName);
3188+
}
3189+
3190+
@TestMetadata("hasSomeLineBreaks2.kt")
3191+
public void testHasSomeLineBreaks2() throws Exception {
3192+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/parameterList/hasSomeLineBreaks2.kt");
3193+
doTest(fileName);
3194+
}
3195+
3196+
@TestMetadata("oneParameter.kt")
3197+
public void testOneParameter() throws Exception {
3198+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/parameterList/oneParameter.kt");
3199+
doTest(fileName);
3200+
}
3201+
3202+
@TestMetadata("threeParameters.kt")
3203+
public void testThreeParameters() throws Exception {
3204+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/parameterList/threeParameters.kt");
3205+
doTest(fileName);
3206+
}
3207+
3208+
@TestMetadata("twoParameters.kt")
3209+
public void testTwoParameters() throws Exception {
3210+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/parameterList/twoParameters.kt");
3211+
doTest(fileName);
3212+
}
3213+
}
3214+
}
3215+
31473216
@TestMetadata("idea/testData/intentions/conventionNameCalls")
31483217
@TestDataPath("$PROJECT_ROOT")
31493218
@RunWith(JUnit3RunnerWithInners.class)

0 commit comments

Comments
 (0)