mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[kotlin] implement "Put calls on separate lines" intention
^KTIJ-24022 Fixed GitOrigin-RevId: f487e6878fa180d123890987251396ead63ddeff
This commit is contained in:
committed by
intellij-monorepo-bot
parent
79d667b484
commit
ce7d3767a0
@@ -1687,6 +1687,7 @@ convert.to.lambda.expression=Convert to a lambda expression
|
||||
put.arguments.on.separate.lines=Put arguments on separate lines
|
||||
put.parameters.on.separate.lines=Put parameters on separate lines
|
||||
put.expressions.on.separate.lines=Put expressions on separate lines
|
||||
put.calls.on.separate.lines=Put calls on separate lines
|
||||
demorgan.law=DeMorgan Law
|
||||
replace.with.end.of.line.comment=Replace with end of line comment
|
||||
convert.collection.constructor.to.function=Convert Collection constructor to function
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
fun calculate(): Int {
|
||||
return call1().call2()
|
||||
.call3()
|
||||
.call5()
|
||||
.call6()
|
||||
.call7()
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun calculate(): Int {
|
||||
return call1().call2().call3()<spot>.</spot>call5().call6().call7()
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
Formats call chain by placing each call on a separate line.
|
||||
</body>
|
||||
</html>
|
||||
+7
@@ -98,6 +98,13 @@
|
||||
<categoryKey>group.names.kotlin</categoryKey>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<language>kotlin</language>
|
||||
<className>org.jetbrains.kotlin.idea.codeInsight.intentions.shared.PutCallsOnSeparateLinesIntention</className>
|
||||
<bundleName>messages.KotlinBundle</bundleName>
|
||||
<categoryKey>group.names.kotlin</categoryKey>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<language>kotlin</language>
|
||||
<className>org.jetbrains.kotlin.idea.codeInsight.intentions.shared.JoinArgumentListIntention</className>
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.kotlin.idea.codeInsight.intentions.shared
|
||||
|
||||
import com.intellij.application.options.CodeStyle
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||
import com.intellij.psi.impl.source.PostprocessReformattingAspect
|
||||
import com.intellij.psi.util.descendants
|
||||
import com.intellij.psi.util.parents
|
||||
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.codeinsight.api.applicable.intentions.AbstractKotlinApplicableIntention
|
||||
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.KotlinApplicabilityRange
|
||||
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.applicabilityRange
|
||||
import org.jetbrains.kotlin.idea.formatter.kotlinCommonSettings
|
||||
import org.jetbrains.kotlin.psi.KtImportDirective
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtQualifiedExpression
|
||||
import org.jetbrains.kotlin.util.takeWhileIsInstance
|
||||
|
||||
internal class PutCallsOnSeparateLinesIntention :
|
||||
AbstractKotlinApplicableIntention<KtQualifiedExpression>(KtQualifiedExpression::class) {
|
||||
override fun getActionName(element: KtQualifiedExpression): String = familyName
|
||||
override fun getFamilyName(): String = KotlinBundle.message("put.calls.on.separate.lines")
|
||||
|
||||
override fun apply(element: KtQualifiedExpression, project: Project, editor: Editor?) {
|
||||
val rootQualifierExpression = element.topmostQualifierExpression() ?: return
|
||||
PostprocessReformattingAspect.getInstance(project).disablePostprocessFormattingInside {
|
||||
val psiFactory = KtPsiFactory(project)
|
||||
rootQualifierExpression.visitOperations(transformation = callChainTransformation(element)) { qualifierExpression ->
|
||||
val operationReference = qualifierExpression.operationTokenNode as? PsiElement ?: return@visitOperations
|
||||
val whiteSpace = operationReference.prevSibling as? PsiWhiteSpace
|
||||
when {
|
||||
whiteSpace == null -> qualifierExpression.addBefore(psiFactory.createNewLine(), operationReference)
|
||||
!whiteSpace.textContains('\n') -> whiteSpace.replace(psiFactory.createWhiteSpace("\n${whiteSpace.text}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CodeStyleManager.getInstance(project).reformat(/* element = */ rootQualifierExpression, /* canChangeWhiteSpacesOnly = */ true)
|
||||
}
|
||||
|
||||
override fun getApplicabilityRange(): KotlinApplicabilityRange<KtQualifiedExpression> = applicabilityRange {
|
||||
(it.operationTokenNode as? PsiElement)?.textRangeInParent
|
||||
}
|
||||
|
||||
override fun isApplicableByPsi(element: KtQualifiedExpression): Boolean {
|
||||
val topmostQualifierExpression = element.topmostQualifierExpression() ?: return false
|
||||
if (topmostQualifierExpression.parent is KtImportDirective) return false
|
||||
|
||||
topmostQualifierExpression.visitOperations(transformation = callChainTransformation(element)) {
|
||||
val nextSibling = it.operationTokenNode.treePrev as? PsiWhiteSpace ?: return true
|
||||
if (!nextSibling.textContains('\n')) return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun callChainTransformation(element: PsiElement): Sequence<KtQualifiedExpression>.() -> Sequence<KtQualifiedExpression> {
|
||||
val wrapFirstCall = CodeStyle.getSettings(element.containingFile).kotlinCommonSettings.WRAP_FIRST_METHOD_IN_CALL_CHAIN
|
||||
return { if (wrapFirstCall) this else drop(1) }
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun KtQualifiedExpression.visitOperations(
|
||||
transformation: Sequence<KtQualifiedExpression>.() -> Sequence<KtQualifiedExpression>,
|
||||
action: (KtQualifiedExpression) -> Unit,
|
||||
) {
|
||||
descendants(childrenFirst = true) { it is KtQualifiedExpression }
|
||||
.filterIsInstance<KtQualifiedExpression>()
|
||||
.transformation()
|
||||
.forEach(action)
|
||||
}
|
||||
|
||||
private fun KtQualifiedExpression.topmostQualifierExpression(): KtQualifiedExpression? =
|
||||
parents(withSelf = true).takeWhileIsInstance<KtQualifiedExpression>().lastOrNull()
|
||||
+63
@@ -445,6 +445,69 @@ public abstract class SharedK1IntentionTestGenerated extends AbstractSharedK1Int
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
@TestMetadata("../testData/intentions/putCallsOnSeparateLines")
|
||||
public static class PutCallsOnSeparateLines extends AbstractSharedK1IntentionTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
@TestMetadata("doNotWrapFirstElement.kt")
|
||||
public void testDoNotWrapFirstElement() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/doNotWrapFirstElement.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("doNotWrapFirstElement2.kt")
|
||||
public void testDoNotWrapFirstElement2() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/doNotWrapFirstElement2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("doNotWrapFirstElement3.kt")
|
||||
public void testDoNotWrapFirstElement3() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/doNotWrapFirstElement3.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("dotChain.kt")
|
||||
public void testDotChain() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/dotChain.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("dotChainWithParenthesis.kt")
|
||||
public void testDotChainWithParenthesis() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/dotChainWithParenthesis.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("import.kt")
|
||||
public void testImport() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/import.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mixWithMultilineBody.kt")
|
||||
public void testMixWithMultilineBody() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/mixWithMultilineBody.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mixWithMultilineBodyInFirstPosition.kt")
|
||||
public void testMixWithMultilineBodyInFirstPosition() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/mixWithMultilineBodyInFirstPosition.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("safeChain.kt")
|
||||
public void testSafeChain() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/safeChain.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("safeChainWithLineBreaks.kt")
|
||||
public void testSafeChainWithLineBreaks() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/safeChainWithLineBreaks.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("safeChainWithOneLineBreak.kt")
|
||||
public void testSafeChainWithOneLineBreak() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/safeChainWithOneLineBreak.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
@TestMetadata("../testData/intentions/putExpressionsOnSeparateLines")
|
||||
public static class PutExpressionsOnSeparateLines extends AbstractSharedK1IntentionTest {
|
||||
|
||||
+63
@@ -445,6 +445,69 @@ public abstract class SharedK2IntentionTestGenerated extends AbstractSharedK2Int
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
@TestMetadata("../testData/intentions/putCallsOnSeparateLines")
|
||||
public static class PutCallsOnSeparateLines extends AbstractSharedK2IntentionTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
@TestMetadata("doNotWrapFirstElement.kt")
|
||||
public void testDoNotWrapFirstElement() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/doNotWrapFirstElement.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("doNotWrapFirstElement2.kt")
|
||||
public void testDoNotWrapFirstElement2() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/doNotWrapFirstElement2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("doNotWrapFirstElement3.kt")
|
||||
public void testDoNotWrapFirstElement3() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/doNotWrapFirstElement3.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("dotChain.kt")
|
||||
public void testDotChain() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/dotChain.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("dotChainWithParenthesis.kt")
|
||||
public void testDotChainWithParenthesis() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/dotChainWithParenthesis.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("import.kt")
|
||||
public void testImport() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/import.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mixWithMultilineBody.kt")
|
||||
public void testMixWithMultilineBody() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/mixWithMultilineBody.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mixWithMultilineBodyInFirstPosition.kt")
|
||||
public void testMixWithMultilineBodyInFirstPosition() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/mixWithMultilineBodyInFirstPosition.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("safeChain.kt")
|
||||
public void testSafeChain() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/safeChain.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("safeChainWithLineBreaks.kt")
|
||||
public void testSafeChainWithLineBreaks() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/safeChainWithLineBreaks.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("safeChainWithOneLineBreak.kt")
|
||||
public void testSafeChainWithOneLineBreak() throws Exception {
|
||||
runTest("../testData/intentions/putCallsOnSeparateLines/safeChainWithOneLineBreak.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
@TestMetadata("../testData/intentions/putExpressionsOnSeparateLines")
|
||||
public static class PutExpressionsOnSeparateLines extends AbstractSharedK2IntentionTest {
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.codeInsight.intentions.shared.PutCallsOnSeparateLinesIntention
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1().call2().call3()<caret>.call5().ref6.call7()
|
||||
}
|
||||
|
||||
// SET_TRUE: WRAP_FIRST_METHOD_IN_CALL_CHAIN
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1()
|
||||
.call2()
|
||||
.call3()
|
||||
.call5()
|
||||
.ref6
|
||||
.call7()
|
||||
}
|
||||
|
||||
// SET_TRUE: WRAP_FIRST_METHOD_IN_CALL_CHAIN
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1()<caret>.call2()
|
||||
}
|
||||
|
||||
// SET_TRUE: WRAP_FIRST_METHOD_IN_CALL_CHAIN
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1()
|
||||
.call2()
|
||||
}
|
||||
|
||||
// SET_TRUE: WRAP_FIRST_METHOD_IN_CALL_CHAIN
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// IS_APPLICABLE: false
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1()
|
||||
<caret>.call2()
|
||||
}
|
||||
|
||||
// SET_TRUE: WRAP_FIRST_METHOD_IN_CALL_CHAIN
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1().call2().call3()<caret>.call5().ref6.call7()
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1().call2()
|
||||
.call3()
|
||||
.call5()
|
||||
.ref6
|
||||
.call7()
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return (call1().call2().call3()<caret>.call5()).ref6.call7()
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return (call1().call2()
|
||||
.call3()
|
||||
.call5()).ref6.call7()
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// IS_APPLICABLE: false
|
||||
// DISABLE-ERRORS
|
||||
|
||||
import a.n<caret>.c.d
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1().call2().call3()<caret>.call5 {
|
||||
doSmth()
|
||||
}?.ref6?.call7()
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1().call2()
|
||||
.call3()
|
||||
.call5 {
|
||||
doSmth()
|
||||
}
|
||||
?.ref6
|
||||
?.call7()
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1()<caret>.call5 {
|
||||
doSmth()
|
||||
}?.ref6?.call7()
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1().call5 {
|
||||
doSmth()
|
||||
}
|
||||
?.ref6
|
||||
?.call7()
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1()?.call2()?.call3()?<caret>.call5()?.ref6?.call7()
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1()?.call2()
|
||||
?.call3()
|
||||
?.call5()
|
||||
?.ref6
|
||||
?.call7()
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// IS_APPLICABLE: false
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1()?.call2()
|
||||
?.call3()
|
||||
?<caret>.call5()
|
||||
?.ref6
|
||||
?.call7()
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1()?.call2()
|
||||
?.call3()
|
||||
?<caret>.call5()?.ref6
|
||||
?.call7()
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// DISABLE-ERRORS
|
||||
fun calculate(): Int {
|
||||
return call1()?.call2()
|
||||
?.call3()
|
||||
?.call5()
|
||||
?.ref6
|
||||
?.call7()
|
||||
}
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
// "Create class 'A'" "false"
|
||||
// ACTION: Introduce local variable
|
||||
// ACTION: Put calls on separate lines
|
||||
// ACTION: Rename reference
|
||||
// ERROR: Unresolved reference: A
|
||||
fun foo() = J.<caret>A.B
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// "Replace with 'declaringJavaClass'" "true"
|
||||
// ACTION: Add method contract to 'getDeclaringClass()'
|
||||
// ACTION: Introduce local variable
|
||||
// ACTION: Put calls on separate lines
|
||||
// ACTION: Replace with 'declaringJavaClass'
|
||||
// API_VERSION: 1.7
|
||||
// WITH_STDLIB
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// "Replace with 'declaringJavaClass'" "true"
|
||||
// ACTION: Add method contract to 'getDeclaringClass()'
|
||||
// ACTION: Introduce local variable
|
||||
// ACTION: Put calls on separate lines
|
||||
// ACTION: Replace with 'declaringJavaClass'
|
||||
// API_VERSION: 1.7
|
||||
// WITH_STDLIB
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// "Introduce import alias" "false"
|
||||
// ACTION: Put calls on separate lines
|
||||
// WITH_STDLIB
|
||||
// ERROR: Overload resolution ambiguity: <br>public inline fun <T> Iterable<TypeVariable(T)>.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections<br>public inline fun <K, V> Map<out TypeVariable(K), TypeVariable(V)>.forEach(action: (Map.Entry<TypeVariable(K), TypeVariable(V)>) -> Unit): Unit defined in kotlin.collections
|
||||
// ERROR: Unresolved reference: a
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// "Surround with null check" "false"
|
||||
// ACTION: Add non-null asserted (!!) call
|
||||
// ACTION: Introduce local variable
|
||||
// ACTION: Put calls on separate lines
|
||||
// ACTION: Replace with safe (?.) call
|
||||
// ERROR: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// WITH_STDLIB
|
||||
// ACTION: Add non-null asserted (!!) call
|
||||
// ACTION: Introduce local variable
|
||||
// ACTION: Put calls on separate lines
|
||||
// ACTION: Replace with safe (?.) call
|
||||
// ERROR: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Int?
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// ACTION: Add non-null asserted (!!) call
|
||||
// ACTION: Convert to run
|
||||
// ACTION: Convert to with
|
||||
// ACTION: Put calls on separate lines
|
||||
// ACTION: Replace with safe (?.) call
|
||||
// ACTION: Surround with null check
|
||||
// ERROR: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type B?
|
||||
|
||||
Reference in New Issue
Block a user