diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameterList.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameterList.java index 2c74d1d572f..aa66ee0734c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameterList.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtParameterList.java @@ -20,6 +20,7 @@ import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub; import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes; @@ -78,4 +79,14 @@ public class KtParameterList extends KtElementImplStub1, + "abcd", + false + ) +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ChopArgumentListIntention/before.kt.template b/idea/resources/intentionDescriptions/ChopArgumentListIntention/before.kt.template new file mode 100644 index 00000000000..d7c0cd5caa6 --- /dev/null +++ b/idea/resources/intentionDescriptions/ChopArgumentListIntention/before.kt.template @@ -0,0 +1,3 @@ +fun foo() { + bar(1, "abcd", false) +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ChopArgumentListIntention/description.html b/idea/resources/intentionDescriptions/ChopArgumentListIntention/description.html new file mode 100644 index 00000000000..3184f32dba8 --- /dev/null +++ b/idea/resources/intentionDescriptions/ChopArgumentListIntention/description.html @@ -0,0 +1,5 @@ + + +This intention formats a function call placing each argument on separate line + + \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ChopParameterListIntention/after.kt.template b/idea/resources/intentionDescriptions/ChopParameterListIntention/after.kt.template new file mode 100644 index 00000000000..190a75155f9 --- /dev/null +++ b/idea/resources/intentionDescriptions/ChopParameterListIntention/after.kt.template @@ -0,0 +1,7 @@ +fun foo( + param1: String, + param2: Int, + param3: Any +) { + bar() +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ChopParameterListIntention/before.kt.template b/idea/resources/intentionDescriptions/ChopParameterListIntention/before.kt.template new file mode 100644 index 00000000000..7750583e605 --- /dev/null +++ b/idea/resources/intentionDescriptions/ChopParameterListIntention/before.kt.template @@ -0,0 +1,3 @@ +fun foo(param1: String, param2: Int, param3: Any) { + bar() +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ChopParameterListIntention/description.html b/idea/resources/intentionDescriptions/ChopParameterListIntention/description.html new file mode 100644 index 00000000000..fd159fd946c --- /dev/null +++ b/idea/resources/intentionDescriptions/ChopParameterListIntention/description.html @@ -0,0 +1,5 @@ + + +This intention formats parameter list in a declaration placing each parameter on separate line + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 3554c8a6fa8..9760e57ae37 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1575,6 +1575,16 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.ChopParameterListIntention + Kotlin + + + + org.jetbrains.kotlin.idea.intentions.ChopArgumentListIntention + Kotlin + + ( + private val listClass: Class, + private val elementClass: Class, + text: String +) : SelfTargetingOffsetIndependentIntention(listClass, text), LowPriorityAction { + + override fun isApplicableTo(element: TList): Boolean { + val elements = element.elements() + if (elements.size <= 1) return false + if (elements.dropLast(1).all { hasLineBreakAfter(it) }) return false + return true + } + + override fun applyTo(list: TList, editor: Editor?) { + val project = list.project + val document = editor!!.document + val startOffset = list.startOffset + + val elements = list.elements() + if (!hasLineBreakAfter(elements.last())) { + val rpar = list.allChildren.lastOrNull { it.node.elementType == KtTokens.RPAR } + rpar?.startOffset?.let { document.insertString(it, "\n") } + } + + for (element in elements.asReversed()) { + if (!hasLineBreakBefore(element)) { + document.insertString(element.startOffset, "\n") + } + } + + val documentManager = PsiDocumentManager.getInstance(project) + documentManager.commitDocument(document) + val psiFile = documentManager.getPsiFile(document)!! + val newList = PsiTreeUtil.getParentOfType(psiFile.findElementAt(startOffset)!!, listClass)!! + CodeStyleManager.getInstance(project).adjustLineIndent(psiFile, newList.textRange) + } + + private fun hasLineBreakAfter(element: TElement): Boolean { + return element + .siblings(withItself = false) + .takeWhile { !elementClass.isInstance(it) } + .any { it is PsiWhiteSpace && it.textContains('\n') } + } + + private fun hasLineBreakBefore(element: TElement): Boolean { + return element + .siblings(withItself = false, forward = false) + .takeWhile { !elementClass.isInstance(it) } + .any { it is PsiWhiteSpace && it.textContains('\n') } + } + + private fun TList.elements(): List { + return allChildren + .filter { elementClass.isInstance(it) } + .map { + @Suppress("UNCHECKED_CAST") + it as TElement + } + .toList() + } +} + +class ChopParameterListIntention : AbstractChopListIntention( + KtParameterList::class.java, + KtParameter::class.java, + "Put parameters on separate lines" +) { + override fun isApplicableTo(element: KtParameterList): Boolean { + if (element.parent is KtFunctionLiteral) return false + return super.isApplicableTo(element) + } +} + +class ChopArgumentListIntention : AbstractChopListIntention( + KtValueArgumentList::class.java, + KtValueArgument::class.java, + "Put arguments on separate lines" +) \ No newline at end of file diff --git a/idea/testData/intentions/chop/argumentList/.intention b/idea/testData/intentions/chop/argumentList/.intention new file mode 100644 index 00000000000..3fa535c1b42 --- /dev/null +++ b/idea/testData/intentions/chop/argumentList/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ChopArgumentListIntention diff --git a/idea/testData/intentions/chop/argumentList/threeArgs.kt b/idea/testData/intentions/chop/argumentList/threeArgs.kt new file mode 100644 index 00000000000..1c14efec0ed --- /dev/null +++ b/idea/testData/intentions/chop/argumentList/threeArgs.kt @@ -0,0 +1,5 @@ +fun f() { + foo(1, "a", 2) +} + +fun foo(p1: Int, p2: String, p3: Int){} \ No newline at end of file diff --git a/idea/testData/intentions/chop/argumentList/threeArgs.kt.after b/idea/testData/intentions/chop/argumentList/threeArgs.kt.after new file mode 100644 index 00000000000..3d0f06d3032 --- /dev/null +++ b/idea/testData/intentions/chop/argumentList/threeArgs.kt.after @@ -0,0 +1,9 @@ +fun f() { + foo( + 1, + "a", + 2 + ) +} + +fun foo(p1: Int, p2: String, p3: Int){} \ No newline at end of file diff --git a/idea/testData/intentions/chop/parameterList/.intention b/idea/testData/intentions/chop/parameterList/.intention new file mode 100644 index 00000000000..e763f2df0f9 --- /dev/null +++ b/idea/testData/intentions/chop/parameterList/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ChopParameterListIntention diff --git a/idea/testData/intentions/chop/parameterList/hasAllLineBreaks.kt b/idea/testData/intentions/chop/parameterList/hasAllLineBreaks.kt new file mode 100644 index 00000000000..39e9f63251f --- /dev/null +++ b/idea/testData/intentions/chop/parameterList/hasAllLineBreaks.kt @@ -0,0 +1,7 @@ +// IS_APPLICABLE: false + +fun foo( + c: Char, + b: Boolean +) { +} \ No newline at end of file diff --git a/idea/testData/intentions/chop/parameterList/hasSomeLineBreaks1.kt b/idea/testData/intentions/chop/parameterList/hasSomeLineBreaks1.kt new file mode 100644 index 00000000000..3c52da5252f --- /dev/null +++ b/idea/testData/intentions/chop/parameterList/hasSomeLineBreaks1.kt @@ -0,0 +1,3 @@ +fun foo(p: Int, c: Char, + b: Boolean) { +} \ No newline at end of file diff --git a/idea/testData/intentions/chop/parameterList/hasSomeLineBreaks1.kt.after b/idea/testData/intentions/chop/parameterList/hasSomeLineBreaks1.kt.after new file mode 100644 index 00000000000..79ed12c3c76 --- /dev/null +++ b/idea/testData/intentions/chop/parameterList/hasSomeLineBreaks1.kt.after @@ -0,0 +1,6 @@ +fun foo( + p: Int, + c: Char, + b: Boolean +) { +} \ No newline at end of file diff --git a/idea/testData/intentions/chop/parameterList/hasSomeLineBreaks2.kt b/idea/testData/intentions/chop/parameterList/hasSomeLineBreaks2.kt new file mode 100644 index 00000000000..c1bcfc44a2e --- /dev/null +++ b/idea/testData/intentions/chop/parameterList/hasSomeLineBreaks2.kt @@ -0,0 +1,4 @@ +fun foo( + p: Int, c: Char, b: Boolean +) { +} \ No newline at end of file diff --git a/idea/testData/intentions/chop/parameterList/hasSomeLineBreaks2.kt.after b/idea/testData/intentions/chop/parameterList/hasSomeLineBreaks2.kt.after new file mode 100644 index 00000000000..79ed12c3c76 --- /dev/null +++ b/idea/testData/intentions/chop/parameterList/hasSomeLineBreaks2.kt.after @@ -0,0 +1,6 @@ +fun foo( + p: Int, + c: Char, + b: Boolean +) { +} \ No newline at end of file diff --git a/idea/testData/intentions/chop/parameterList/oneParameter.kt b/idea/testData/intentions/chop/parameterList/oneParameter.kt new file mode 100644 index 00000000000..81a9b56c947 --- /dev/null +++ b/idea/testData/intentions/chop/parameterList/oneParameter.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false + +fun foo(c: Char) { +} \ No newline at end of file diff --git a/idea/testData/intentions/chop/parameterList/threeParameters.kt b/idea/testData/intentions/chop/parameterList/threeParameters.kt new file mode 100644 index 00000000000..230344b216e --- /dev/null +++ b/idea/testData/intentions/chop/parameterList/threeParameters.kt @@ -0,0 +1,2 @@ +fun foo(p: Int, c: Char, b: Boolean) { +} \ No newline at end of file diff --git a/idea/testData/intentions/chop/parameterList/threeParameters.kt.after b/idea/testData/intentions/chop/parameterList/threeParameters.kt.after new file mode 100644 index 00000000000..79ed12c3c76 --- /dev/null +++ b/idea/testData/intentions/chop/parameterList/threeParameters.kt.after @@ -0,0 +1,6 @@ +fun foo( + p: Int, + c: Char, + b: Boolean +) { +} \ No newline at end of file diff --git a/idea/testData/intentions/chop/parameterList/twoParameters.kt b/idea/testData/intentions/chop/parameterList/twoParameters.kt new file mode 100644 index 00000000000..caf2a4172e4 --- /dev/null +++ b/idea/testData/intentions/chop/parameterList/twoParameters.kt @@ -0,0 +1,2 @@ +fun foo(p: Int, c: Char) { +} \ No newline at end of file diff --git a/idea/testData/intentions/chop/parameterList/twoParameters.kt.after b/idea/testData/intentions/chop/parameterList/twoParameters.kt.after new file mode 100644 index 00000000000..4bcadde1866 --- /dev/null +++ b/idea/testData/intentions/chop/parameterList/twoParameters.kt.after @@ -0,0 +1,5 @@ +fun foo( + p: Int, + c: Char +) { +} \ No newline at end of file diff --git a/idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithExtraArgs.kt b/idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithExtraArgs.kt index 85930497230..4b2a7eb468a 100644 --- a/idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithExtraArgs.kt +++ b/idea/testData/quickfix/createFromUsage/createClass/callExpression/callWithExtraArgs.kt @@ -3,6 +3,7 @@ // ACTION: Add parameter to constructor 'Foo' // ACTION: Create secondary constructor // ERROR: Too many arguments for public constructor Foo(a: Int) defined in Foo +// ACTION: Put arguments on separate lines // ACTION: To raw string literal class Foo(a: Int) diff --git a/idea/testData/quickfix/createFromUsage/createFunction/call/callInAnnotationEntry.kt b/idea/testData/quickfix/createFromUsage/createFunction/call/callInAnnotationEntry.kt index 9b85167109a..f089d479eaf 100644 --- a/idea/testData/quickfix/createFromUsage/createFunction/call/callInAnnotationEntry.kt +++ b/idea/testData/quickfix/createFromUsage/createFunction/call/callInAnnotationEntry.kt @@ -3,6 +3,7 @@ // ACTION: Make internal // ACTION: Make private // ACTION: Rename reference +// ACTION: Put arguments on separate lines // ACTION: Convert to expression body // ERROR: Unresolved reference: foo // ERROR: Unresolved reference: bar diff --git a/idea/testData/quickfix/renameToUnderscore/functionExpressionParameterNoRemoveParameter.kt b/idea/testData/quickfix/renameToUnderscore/functionExpressionParameterNoRemoveParameter.kt index 1ede27f0275..700a62bbd29 100644 --- a/idea/testData/quickfix/renameToUnderscore/functionExpressionParameterNoRemoveParameter.kt +++ b/idea/testData/quickfix/renameToUnderscore/functionExpressionParameterNoRemoveParameter.kt @@ -3,6 +3,7 @@ // ACTION: Convert parameter to receiver // ACTION: Rename to _ // ACTION: Specify return type explicitly +// ACTION: Put parameters on separate lines fun foo(block: (String, Int) -> Unit) { block("", 1) diff --git a/idea/testData/quickfix/typeMismatch/tooManyArgumentsException.kt b/idea/testData/quickfix/typeMismatch/tooManyArgumentsException.kt index 98c17195779..0a02e4927ea 100644 --- a/idea/testData/quickfix/typeMismatch/tooManyArgumentsException.kt +++ b/idea/testData/quickfix/typeMismatch/tooManyArgumentsException.kt @@ -4,6 +4,7 @@ // ACTION: Create function 'join' // ACTION: Flip ',' // ACTION: Introduce local variable +// ACTION: Put arguments on separate lines //this test checks that there is no ArrayIndexOutOfBoundsException when there are more arguments than parameters fun array1(vararg a : T) = a diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index e9213e9eebb..b3976218139 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -3144,6 +3144,75 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/chop") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Chop extends AbstractIntentionTest { + public void testAllFilesPresentInChop() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/chop"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("idea/testData/intentions/chop/argumentList") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ArgumentList extends AbstractIntentionTest { + public void testAllFilesPresentInArgumentList() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/chop/argumentList"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("threeArgs.kt") + public void testThreeArgs() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/argumentList/threeArgs.kt"); + doTest(fileName); + } + } + + @TestMetadata("idea/testData/intentions/chop/parameterList") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ParameterList extends AbstractIntentionTest { + public void testAllFilesPresentInParameterList() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/chop/parameterList"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("hasAllLineBreaks.kt") + public void testHasAllLineBreaks() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/parameterList/hasAllLineBreaks.kt"); + doTest(fileName); + } + + @TestMetadata("hasSomeLineBreaks1.kt") + public void testHasSomeLineBreaks1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/parameterList/hasSomeLineBreaks1.kt"); + doTest(fileName); + } + + @TestMetadata("hasSomeLineBreaks2.kt") + public void testHasSomeLineBreaks2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/parameterList/hasSomeLineBreaks2.kt"); + doTest(fileName); + } + + @TestMetadata("oneParameter.kt") + public void testOneParameter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/parameterList/oneParameter.kt"); + doTest(fileName); + } + + @TestMetadata("threeParameters.kt") + public void testThreeParameters() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/parameterList/threeParameters.kt"); + doTest(fileName); + } + + @TestMetadata("twoParameters.kt") + public void testTwoParameters() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/chop/parameterList/twoParameters.kt"); + doTest(fileName); + } + } + } + @TestMetadata("idea/testData/intentions/conventionNameCalls") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)