From b6b755506cc7e84dc790e6aafedc221c134d9d4b Mon Sep 17 00:00:00 2001 From: Dmitry Gridin Date: Mon, 11 Nov 2019 14:33:04 +0700 Subject: [PATCH] Formatter: support `Chop down if long` for `Chained method calls` #KT-23929 Fixed #KT-33553 Fixed --- .../idea/formatter/KotlinCommonBlock.kt | 166 ++++++++---- .../SpacesInQualifiedExpressions.after.kt | 4 +- .../formatter/SpacesInQualifiedExpressions.kt | 4 +- .../callChain/CallChainWrapping.after.inv.kt | 25 +- .../callChain/CallChainWrapping.after.kt | 20 +- .../formatter/callChain/CallChainWrapping.kt | 9 + .../CallChainWrappingChopDown.after.inv.kt | 244 ++++++++++++++++++ .../CallChainWrappingChopDown.after.kt | 239 +++++++++++++++++ .../callChain/CallChainWrappingChopDown.kt | 19 ++ .../CallChainWrappingInElvis.after.inv.kt | 12 + .../CallChainWrappingInElvis.after.kt | 10 + .../callChain/CallChainWrappingInElvis.kt | 8 + .../formatter/FormatterTestGenerated.java | 20 ++ 13 files changed, 726 insertions(+), 54 deletions(-) create mode 100644 idea/testData/formatter/callChain/CallChainWrappingChopDown.after.inv.kt create mode 100644 idea/testData/formatter/callChain/CallChainWrappingChopDown.after.kt create mode 100644 idea/testData/formatter/callChain/CallChainWrappingChopDown.kt create mode 100644 idea/testData/formatter/callChain/CallChainWrappingInElvis.after.inv.kt create mode 100644 idea/testData/formatter/callChain/CallChainWrappingInElvis.after.kt create mode 100644 idea/testData/formatter/callChain/CallChainWrappingInElvis.kt diff --git a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt index b70e6962ccc..cfc9be3d252 100644 --- a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt +++ b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt @@ -25,10 +25,12 @@ import org.jetbrains.kotlin.kdoc.parser.KDocElementTypes import org.jetbrains.kotlin.lexer.KtTokens.* import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.utils.addToStdlib.safeAs private val QUALIFIED_OPERATION = TokenSet.create(DOT, SAFE_ACCESS) private val QUALIFIED_EXPRESSIONS = TokenSet.create(DOT_QUALIFIED_EXPRESSION, SAFE_ACCESS_EXPRESSION) private val ELVIS_SET = TokenSet.create(ELVIS) +private val QUALIFIED_EXPRESSIONS_WITHOUT_WRAP = TokenSet.create(IMPORT_DIRECTIVE, PACKAGE_DIRECTIVE) private const val KDOC_COMMENT_INDENT = 1 @@ -120,55 +122,122 @@ abstract class KotlinCommonBlock( return nodeSubBlocks } - private fun splitSubBlocksOnDot(nodeSubBlocks: List): List { - val operationBlockIndex = nodeSubBlocks.indexOfBlockWithType(QUALIFIED_OPERATION) - if (operationBlockIndex != -1) { - // Create fake ".something" or "?.something" block here, so child indentation will be - // relative to it when it starts from new line (see Indent javadoc). - - val isNonFirstChainedCall = operationBlockIndex > 0 && isCallBlock(nodeSubBlocks[operationBlockIndex - 1]) - - // enforce indent to children when there's a line break before the dot in any call in the chain (meaning that - // the call chain following that call is indented) - val enforceIndentToChildren = anyCallInCallChainIsWrapped(nodeSubBlocks[operationBlockIndex - 1]) - - val indentType = if (settings.kotlinCustomSettings.CONTINUATION_INDENT_FOR_CHAINED_CALLS) { - if (enforceIndentToChildren) Indent.Type.CONTINUATION else Indent.Type.CONTINUATION_WITHOUT_FIRST - } else { - Indent.Type.NORMAL - } - val indent = Indent.getIndent( - indentType, false, - enforceIndentToChildren - ) - val wrap = if ((settings.kotlinCommonSettings.WRAP_FIRST_METHOD_IN_CALL_CHAIN || isNonFirstChainedCall) && - canWrapCallChain(node) - ) - Wrap.createWrap(settings.kotlinCommonSettings.METHOD_CALL_CHAIN_WRAP, true) - else - null - - return nodeSubBlocks.splitAtIndex(operationBlockIndex, indent, wrap) + private fun ASTNode.isQualifiedNode(): Boolean { + var currentNode: ASTNode? = this + while (currentNode != null) { + if (currentNode.elementType in QUALIFIED_EXPRESSIONS) return true + if (currentNode.elementType != PARENTHESIZED && currentNode.psi?.safeAs()?.operationToken != EXCLEXCL) return false + currentNode = currentNode.treeParent } - return nodeSubBlocks + + return false } - private fun List.splitAtIndex(index: Int, indent: Indent?, wrap: Wrap?): List { - val operationBlock = this[index] - val operationSyntheticBlock = SyntheticKotlinBlock( - operationBlock.requireNode(), - subList(index, size), - null, indent, wrap, spacingBuilder - ) { - val parent = it.treeParent ?: node - val skipOperationNodeParent = if (parent.elementType === OPERATION_REFERENCE) { - parent.treeParent ?: parent - } else { - parent + private fun splitSubBlocksOnDot(nodeSubBlocks: List): List { + if (node.treeParent?.isQualifiedNode() == true) return nodeSubBlocks + if (!canWrapCallChain(node)) return nodeSubBlocks + + val operationBlockIndex = nodeSubBlocks.indexOfBlockWithType(QUALIFIED_OPERATION) + if (operationBlockIndex == -1) return nodeSubBlocks + + val block = nodeSubBlocks.first() + val wrap = createWrapForQualifiedExpression(block.requireNode()) + val indent = createIndentForQualifiedExpression(block) + val newBlock = block.processBlock(indent, wrap) + return nodeSubBlocks.replaceBlock(newBlock, 0).splitAtIndex(operationBlockIndex, indent, wrap) + } + + private fun ASTBlock.processBlock(indent: Indent, wrap: Wrap?): ASTBlock { + val currentNode = requireNode() + @Suppress("UNCHECKED_CAST") + val subBlocks = subBlocks as List + val elementType = currentNode.elementType + val index = when (elementType) { + PARENTHESIZED -> subBlocks.indexOfFirst { block -> + val type = block.requireNode().elementType + type != LPAR && type !in WHITE_SPACE_OR_COMMENT_BIT_SET } - createSyntheticSpacingNodeBlock(skipOperationNodeParent) + POSTFIX_EXPRESSION, in QUALIFIED_EXPRESSIONS -> 0 + else -> return this } + val resultWrap = wrap.takeIf { elementType != PARENTHESIZED } ?: createWrapForQualifiedExpression(currentNode) + val newBlock = subBlocks.elementAt(index).processBlock(indent, resultWrap) + return subBlocks.replaceBlock(newBlock, index).let { + val operationIndex = subBlocks.indexOfBlockWithType(QUALIFIED_OPERATION) + if (operationIndex != -1) + it.splitAtIndex(operationIndex, indent, resultWrap) + else + it + }.wrapToBlock(currentNode, this) + } + + private fun List.replaceBlock( + block: ASTBlock, + index: Int = 0 + ): List = subList(0, index) + block + (takeIf { index + 1 < it.size }?.subList(index + 1, size) ?: emptyList()) + + private fun createWrapForQualifiedExpression(node: ASTNode): Wrap? { + val isNonFirstChainedCall = (node.treeParent)?.isQualifiedNode() == true + + return if (isNonFirstChainedCall) + Wrap.createWrap( + settings.kotlinCommonSettings.METHOD_CALL_CHAIN_WRAP, + settings.kotlinCommonSettings.WRAP_FIRST_METHOD_IN_CALL_CHAIN + ) + else + null + } + + private fun createIndentForQualifiedExpression(block: ASTBlock): Indent { + // enforce indent to children when there's a line break before the dot in any call in the chain (meaning that + // the call chain following that call is indented) + val enforceIndentToChildren = anyCallInCallChainIsWrapped(block) + + val indentType = if (settings.kotlinCustomSettings.CONTINUATION_INDENT_FOR_CHAINED_CALLS) { + if (enforceIndentToChildren) Indent.Type.CONTINUATION else Indent.Type.CONTINUATION_WITHOUT_FIRST + } else { + Indent.Type.NORMAL + } + + return Indent.getIndent( + indentType, false, + enforceIndentToChildren + ) + } + + private fun List.wrapToBlock( + anchor: ASTNode?, + parentBlock: ASTBlock? + ): ASTBlock = splitAtIndex(0, null, null, anchor, parentBlock).single() + + private fun List.splitAtIndex( + index: Int, + indent: Indent?, + wrap: Wrap?, + anchor: ASTNode? = null, + parentBlock: ASTBlock? = null + ): List { + val operationBlock = this[index] + val createParentSyntheticSpacingBlock: (ASTNode) -> ASTBlock = if (parentBlock != null) { + { parentBlock } + } else { + { + val parent = it.treeParent ?: node + val skipOperationNodeParent = if (parent.elementType === OPERATION_REFERENCE) { + parent.treeParent ?: parent + } else { + parent + } + createSyntheticSpacingNodeBlock(skipOperationNodeParent) + } + } + val operationSyntheticBlock = SyntheticKotlinBlock( + anchor ?: operationBlock.requireNode(), + subList(index, size), + null, indent, wrap, spacingBuilder, createParentSyntheticSpacingBlock + ) + return subList(0, index) + operationSyntheticBlock } @@ -186,16 +255,13 @@ abstract class KotlinCommonBlock( private fun isCallBlock(astBlock: ASTBlock): Boolean { val node = astBlock.requireNode() - return node.elementType in QUALIFIED_EXPRESSIONS && node.lastChildNode?.elementType == CALL_EXPRESSION + val lastChildElementType = node.lastChildNode?.elementType + return node.isQualifiedNode() && (lastChildElementType == CALL_EXPRESSION || lastChildElementType == REFERENCE_EXPRESSION) } private fun canWrapCallChain(node: ASTNode): Boolean { - val callChainParent = node.parents().firstOrNull { it.elementType !in QUALIFIED_EXPRESSIONS } ?: return true - return callChainParent.elementType in CODE_BLOCKS || - callChainParent.elementType == PROPERTY || - (callChainParent.elementType == BINARY_EXPRESSION && - (callChainParent.psi as KtBinaryExpression).operationToken in ALL_ASSIGNMENTS) || - callChainParent.elementType == RETURN + val callChainParent = node.parents().firstOrNull { !it.isQualifiedNode() } ?: return true + return callChainParent.elementType !in QUALIFIED_EXPRESSIONS_WITHOUT_WRAP } private fun splitSubBlocksOnElvis(nodeSubBlocks: List): List { diff --git a/idea/testData/formatter/SpacesInQualifiedExpressions.after.kt b/idea/testData/formatter/SpacesInQualifiedExpressions.after.kt index 7a3c5280c58..49201ea262f 100644 --- a/idea/testData/formatter/SpacesInQualifiedExpressions.after.kt +++ b/idea/testData/formatter/SpacesInQualifiedExpressions.after.kt @@ -4,7 +4,9 @@ interface Test { fun test(t: Test) { t.foo() - + t().foo() + t()!!.foo().foo() + ((t()!!).foo().foo().foo()).foo() t.foo() t.foo() diff --git a/idea/testData/formatter/SpacesInQualifiedExpressions.kt b/idea/testData/formatter/SpacesInQualifiedExpressions.kt index 665a0f94cc7..db254e306c2 100644 --- a/idea/testData/formatter/SpacesInQualifiedExpressions.kt +++ b/idea/testData/formatter/SpacesInQualifiedExpressions.kt @@ -4,7 +4,9 @@ interface Test { fun test(t: Test) { t . foo() - + t() . foo() + t() !! . foo() .foo() + ( ( t() !!) . foo() .foo() .foo()) .foo() t. foo() diff --git a/idea/testData/formatter/callChain/CallChainWrapping.after.inv.kt b/idea/testData/formatter/callChain/CallChainWrapping.after.inv.kt index 8cd2dad6e21..e4520931d8e 100644 --- a/idea/testData/formatter/callChain/CallChainWrapping.after.inv.kt +++ b/idea/testData/formatter/callChain/CallChainWrapping.after.inv.kt @@ -3,7 +3,10 @@ val x = foo .baz() .quux() -val y = xyzzy(foo.bar().baz().quux()) +val y = xyzzy(foo + .bar() + .baz() + .quux()) fun foo() { foo @@ -27,5 +30,25 @@ fun foo() { .quux() } +fun top() = "" + .plus("") + .plus("") + +class C { + fun member() = "" + .plus("") + .plus("") +} + +fun foo() { + fun local() = "" + .plus("") + .plus("") + + val anonymous = fun() = "" + .plus("") + .plus("") +} + // SET_INT: METHOD_CALL_CHAIN_WRAP = 2 // SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN \ No newline at end of file diff --git a/idea/testData/formatter/callChain/CallChainWrapping.after.kt b/idea/testData/formatter/callChain/CallChainWrapping.after.kt index b8390fa4764..7b1154ad96f 100644 --- a/idea/testData/formatter/callChain/CallChainWrapping.after.kt +++ b/idea/testData/formatter/callChain/CallChainWrapping.after.kt @@ -2,7 +2,9 @@ val x = foo.bar() .baz() .quux() -val y = xyzzy(foo.bar().baz().quux()) +val y = xyzzy(foo.bar() + .baz() + .quux()) fun foo() { foo.bar() @@ -22,5 +24,21 @@ fun foo() { .quux() } +fun top() = "".plus("") + .plus("") + +class C { + fun member() = "".plus("") + .plus("") +} + +fun foo() { + fun local() = "".plus("") + .plus("") + + val anonymous = fun() = "".plus("") + .plus("") +} + // SET_INT: METHOD_CALL_CHAIN_WRAP = 2 // SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN \ No newline at end of file diff --git a/idea/testData/formatter/callChain/CallChainWrapping.kt b/idea/testData/formatter/callChain/CallChainWrapping.kt index 3d81b16bf5f..9dc20893a67 100644 --- a/idea/testData/formatter/callChain/CallChainWrapping.kt +++ b/idea/testData/formatter/callChain/CallChainWrapping.kt @@ -12,5 +12,14 @@ fun foo() { return foo.bar().baz().quux() } +fun top() = "".plus("").plus("") +class C { + fun member() = "".plus("").plus("") +} +fun foo() { + fun local() = "".plus("").plus("") + val anonymous = fun() = "".plus("").plus("") +} + // SET_INT: METHOD_CALL_CHAIN_WRAP = 2 // SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN \ No newline at end of file diff --git a/idea/testData/formatter/callChain/CallChainWrappingChopDown.after.inv.kt b/idea/testData/formatter/callChain/CallChainWrappingChopDown.after.inv.kt new file mode 100644 index 00000000000..a5ffca3df97 --- /dev/null +++ b/idea/testData/formatter/callChain/CallChainWrappingChopDown.after.inv.kt @@ -0,0 +1,244 @@ +val x = foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + +val y = xyzzy(foo.bar().baz().quux()) + +fun foo() { + foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + + z = (((foo()))!!).bar() + + z += (foo.bar().baz().quux().foo.bar().baz().quux().foo.bar()!!) + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + + return ((foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux()) + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar()) + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() +} + +// SET_INT: METHOD_CALL_CHAIN_WRAP = 5 +// SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN +// SET_FALSE: CONTINUATION_INDENT_FOR_CHAINED_CALLS diff --git a/idea/testData/formatter/callChain/CallChainWrappingChopDown.after.kt b/idea/testData/formatter/callChain/CallChainWrappingChopDown.after.kt new file mode 100644 index 00000000000..50b3a5810f4 --- /dev/null +++ b/idea/testData/formatter/callChain/CallChainWrappingChopDown.after.kt @@ -0,0 +1,239 @@ +val x = foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + +val y = xyzzy(foo.bar().baz().quux()) + +fun foo() { + foo.bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + + z = (((foo()))!!).bar() + + z += (foo.bar().baz().quux().foo.bar().baz().quux().foo.bar()!!).baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + + return ((foo.bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux()).foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar()).baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() + .foo + .bar() + .baz() + .quux() +} + +// SET_INT: METHOD_CALL_CHAIN_WRAP = 5 +// SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN +// SET_FALSE: CONTINUATION_INDENT_FOR_CHAINED_CALLS diff --git a/idea/testData/formatter/callChain/CallChainWrappingChopDown.kt b/idea/testData/formatter/callChain/CallChainWrappingChopDown.kt new file mode 100644 index 00000000000..d722eddc8ed --- /dev/null +++ b/idea/testData/formatter/callChain/CallChainWrappingChopDown.kt @@ -0,0 +1,19 @@ +val x = foo + .bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux() + +val y = xyzzy(foo.bar().baz().quux()) + +fun foo() { + foo.bar().baz().quux() + .foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux() + + z = (( ( foo()) )!! ).bar() + + z += (foo.bar().baz().quux().foo.bar().baz().quux().foo.bar() !!) .baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux() + + return ((foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux()).foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar()).baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux().foo.bar().baz().quux() +} + +// SET_INT: METHOD_CALL_CHAIN_WRAP = 5 +// SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN +// SET_FALSE: CONTINUATION_INDENT_FOR_CHAINED_CALLS diff --git a/idea/testData/formatter/callChain/CallChainWrappingInElvis.after.inv.kt b/idea/testData/formatter/callChain/CallChainWrappingInElvis.after.inv.kt new file mode 100644 index 00000000000..c0d1ac680d0 --- /dev/null +++ b/idea/testData/formatter/callChain/CallChainWrappingInElvis.after.inv.kt @@ -0,0 +1,12 @@ +fun f(list: List) { + val result = list + .filter { it % 2 == 0 } + .max() ?: -1 + list + .filter { it % 2 == 0 } + .max() + ?: -1 +} + +// SET_INT: METHOD_CALL_CHAIN_WRAP = 2 +// SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN \ No newline at end of file diff --git a/idea/testData/formatter/callChain/CallChainWrappingInElvis.after.kt b/idea/testData/formatter/callChain/CallChainWrappingInElvis.after.kt new file mode 100644 index 00000000000..e4ed295acbb --- /dev/null +++ b/idea/testData/formatter/callChain/CallChainWrappingInElvis.after.kt @@ -0,0 +1,10 @@ +fun f(list: List) { + val result = list.filter { it % 2 == 0 } + .max() ?: -1 + list.filter { it % 2 == 0 } + .max() + ?: -1 +} + +// SET_INT: METHOD_CALL_CHAIN_WRAP = 2 +// SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN \ No newline at end of file diff --git a/idea/testData/formatter/callChain/CallChainWrappingInElvis.kt b/idea/testData/formatter/callChain/CallChainWrappingInElvis.kt new file mode 100644 index 00000000000..06cc4c42061 --- /dev/null +++ b/idea/testData/formatter/callChain/CallChainWrappingInElvis.kt @@ -0,0 +1,8 @@ +fun f(list: List) { + val result = list.filter { it % 2 == 0 }.max() ?: -1 + list.filter { it % 2 == 0 }.max() + ?: -1 +} + +// SET_INT: METHOD_CALL_CHAIN_WRAP = 2 +// SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/formatter/FormatterTestGenerated.java b/idea/tests/org/jetbrains/kotlin/formatter/FormatterTestGenerated.java index c7d8837d599..f8a91551037 100644 --- a/idea/tests/org/jetbrains/kotlin/formatter/FormatterTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/formatter/FormatterTestGenerated.java @@ -852,6 +852,16 @@ public class FormatterTestGenerated extends AbstractFormatterTest { runTest("idea/testData/formatter/callChain/CallChainWrapping.after.kt"); } + @TestMetadata("CallChainWrappingChopDown.after.kt") + public void testCallChainWrappingChopDown() throws Exception { + runTest("idea/testData/formatter/callChain/CallChainWrappingChopDown.after.kt"); + } + + @TestMetadata("CallChainWrappingInElvis.after.kt") + public void testCallChainWrappingInElvis() throws Exception { + runTest("idea/testData/formatter/callChain/CallChainWrappingInElvis.after.kt"); + } + @TestMetadata("ConsecutiveCalls.after.kt") public void testConsecutiveCalls() throws Exception { runTest("idea/testData/formatter/callChain/ConsecutiveCalls.after.kt"); @@ -1397,6 +1407,16 @@ public class FormatterTestGenerated extends AbstractFormatterTest { runTest("idea/testData/formatter/callChain/CallChainWrapping.after.inv.kt"); } + @TestMetadata("CallChainWrappingChopDown.after.inv.kt") + public void testCallChainWrappingChopDown() throws Exception { + runTest("idea/testData/formatter/callChain/CallChainWrappingChopDown.after.inv.kt"); + } + + @TestMetadata("CallChainWrappingInElvis.after.inv.kt") + public void testCallChainWrappingInElvis() throws Exception { + runTest("idea/testData/formatter/callChain/CallChainWrappingInElvis.after.inv.kt"); + } + @TestMetadata("ConsecutiveSafeCallsIndent.after.inv.kt") public void testConsecutiveSafeCallsIndent() throws Exception { runTest("idea/testData/formatter/callChain/ConsecutiveSafeCallsIndent.after.inv.kt");