From 900ec8261417852b3006f537f55a7a7ba6f42d8d Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 11 Dec 2017 11:57:38 +0100 Subject: [PATCH] Fix indent of expressions following elvis operator --- .../idea/formatter/KotlinCommonBlock.kt | 79 +++++++++++++------ idea/testData/formatter/ElvisIndent.after.kt | 14 ++++ idea/testData/formatter/ElvisIndent.kt | 14 ++++ .../FunctionLiteralsInChainCalls.after.inv.kt | 8 ++ .../FunctionLiteralsInChainCalls.after.kt | 8 ++ .../formatter/FunctionLiteralsInChainCalls.kt | 8 ++ .../formatter/FormatterTestGenerated.java | 6 ++ 7 files changed, 112 insertions(+), 25 deletions(-) create mode 100644 idea/testData/formatter/ElvisIndent.after.kt create mode 100644 idea/testData/formatter/ElvisIndent.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 877968ef3c0..fccbb99d772 100644 --- a/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt +++ b/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/KotlinCommonBlock.kt @@ -39,6 +39,8 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings private val QUALIFIED_OPERATION = TokenSet.create(DOT, SAFE_ACCESS) private val QUALIFIED_EXPRESSIONS = TokenSet.create(KtNodeTypes.DOT_QUALIFIED_EXPRESSION, KtNodeTypes.SAFE_ACCESS_EXPRESSION) +private val ELVIS_SET = TokenSet.create(KtTokens.ELVIS) + private val KDOC_COMMENT_INDENT = 1 private val BINARY_EXPRESSIONS = TokenSet.create(KtNodeTypes.BINARY_EXPRESSION, KtNodeTypes.BINARY_WITH_TYPE, KtNodeTypes.IS_EXPRESSION) @@ -95,29 +97,12 @@ abstract class KotlinCommonBlock( var nodeSubBlocks = buildSubBlocks() if (node.elementType in QUALIFIED_EXPRESSIONS) { - val operationBlockIndex = findNodeBlockIndex(nodeSubBlocks, 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 operationBlock = nodeSubBlocks[operationBlockIndex] - val indent = if (settings.kotlinCustomSettings.CONTINUATION_INDENT_FOR_CHAINED_CALLS) - Indent.getContinuationWithoutFirstIndent() - else - Indent.getNormalIndent() - val isNonFirstChainedCall = operationBlockIndex > 0 && isCallBlock(nodeSubBlocks[operationBlockIndex - 1]) - 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 - - val operationSyntheticBlock = SyntheticKotlinBlock( - operationBlock.node, - nodeSubBlocks.subList(operationBlockIndex, nodeSubBlocks.size), - null, indent, wrap, spacingBuilder) { createSyntheticSpacingNodeBlock(it) } - - nodeSubBlocks = nodeSubBlocks.subList(0, operationBlockIndex) + operationSyntheticBlock + nodeSubBlocks = splitSubBlocksOnDot(nodeSubBlocks) + } + else { + val psi = node.psi + if (psi is KtBinaryExpression && psi.operationToken == KtTokens.ELVIS) { + nodeSubBlocks = splitSubBlocksOnElvis(nodeSubBlocks) } } @@ -126,6 +111,38 @@ 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 indent = if (settings.kotlinCustomSettings.CONTINUATION_INDENT_FOR_CHAINED_CALLS) + Indent.getContinuationWithoutFirstIndent() + else + Indent.getNormalIndent() + val isNonFirstChainedCall = operationBlockIndex > 0 && isCallBlock(nodeSubBlocks[operationBlockIndex - 1]) + 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) + } + return nodeSubBlocks + } + + private fun List.splitAtIndex(index: Int, indent: Indent?, wrap: Wrap?): List { + val operationBlock = this[index] + val operationSyntheticBlock = SyntheticKotlinBlock( + operationBlock.node, + subList(index, size), + null, indent, wrap, spacingBuilder) { createSyntheticSpacingNodeBlock(it) } + + return subList(0, index) + operationSyntheticBlock + } + private fun isCallBlock(astBlock: ASTBlock): Boolean { val node = astBlock.node return node.elementType in QUALIFIED_EXPRESSIONS && node.lastChildNode?.elementType == KtNodeTypes.CALL_EXPRESSION @@ -140,6 +157,18 @@ abstract class KotlinCommonBlock( callChainParent.elementType == KtNodeTypes.RETURN } + private fun splitSubBlocksOnElvis(nodeSubBlocks: List): List { + val elvisIndex = nodeSubBlocks.indexOfBlockWithType(ELVIS_SET) + if (elvisIndex >= 0) { + return nodeSubBlocks.splitAtIndex( + elvisIndex, + Indent.getContinuationIndent(), + null + ) + } + return nodeSubBlocks + } + fun createChildIndent(child: ASTNode): Indent? { val childParent = child.treeParent val childType = child.elementType @@ -703,6 +732,6 @@ private fun getWrappingStrategyForItemList(wrapType: Int, itemTypes: TokenSet, w } } -private fun findNodeBlockIndex(blocks: List, tokenSet: TokenSet): Int { - return blocks.indexOfFirst { block -> block.node?.elementType in tokenSet } +private fun List.indexOfBlockWithType(tokenSet: TokenSet): Int { + return indexOfFirst { block -> block.node?.elementType in tokenSet } } diff --git a/idea/testData/formatter/ElvisIndent.after.kt b/idea/testData/formatter/ElvisIndent.after.kt new file mode 100644 index 00000000000..eceec5730d8 --- /dev/null +++ b/idea/testData/formatter/ElvisIndent.after.kt @@ -0,0 +1,14 @@ +fun foo() { + val topLevelDeclaration = + DescriptorUtils.getParentOfType(referencedDescriptor, PropertyDescriptor::class.java, false) as DeclarationDescriptor? + ?: DescriptorUtils.getParentOfType( + referencedDescriptor, + TypeAliasConstructorDescriptor::class.java, + false + )?.typeAliasDescriptor + ?: DescriptorUtils.getParentOfType(referencedDescriptor, FunctionDescriptor::class.java, false) + ?: return emptyList() +} + +// SET_FALSE: CONTINUATION_INDENT_IN_ARGUMENT_LISTS +// SET_FALSE: CONTINUATION_INDENT_FOR_EXPRESSION_BODIES diff --git a/idea/testData/formatter/ElvisIndent.kt b/idea/testData/formatter/ElvisIndent.kt new file mode 100644 index 00000000000..0fb86418d97 --- /dev/null +++ b/idea/testData/formatter/ElvisIndent.kt @@ -0,0 +1,14 @@ +fun foo() { + val topLevelDeclaration = + DescriptorUtils.getParentOfType(referencedDescriptor, PropertyDescriptor::class.java, false) as DeclarationDescriptor? + ?: DescriptorUtils.getParentOfType( + referencedDescriptor, + TypeAliasConstructorDescriptor::class.java, + false + )?.typeAliasDescriptor + ?: DescriptorUtils.getParentOfType(referencedDescriptor, FunctionDescriptor::class.java, false) + ?: return emptyList() +} + +// SET_FALSE: CONTINUATION_INDENT_IN_ARGUMENT_LISTS +// SET_FALSE: CONTINUATION_INDENT_FOR_EXPRESSION_BODIES diff --git a/idea/testData/formatter/FunctionLiteralsInChainCalls.after.inv.kt b/idea/testData/formatter/FunctionLiteralsInChainCalls.after.inv.kt index 7fe6d68c9e8..145b8864965 100644 --- a/idea/testData/formatter/FunctionLiteralsInChainCalls.after.inv.kt +++ b/idea/testData/formatter/FunctionLiteralsInChainCalls.after.inv.kt @@ -33,6 +33,14 @@ fun test3() { } } +fun test4() { + val abc = ArrayList().mapTo( + LinkedHashSet() + ) { + it * 2 + } +} + fun testWithComments() { val abc = ArrayList() // .map { diff --git a/idea/testData/formatter/FunctionLiteralsInChainCalls.after.kt b/idea/testData/formatter/FunctionLiteralsInChainCalls.after.kt index 5ec4362b440..10c25ceabfe 100644 --- a/idea/testData/formatter/FunctionLiteralsInChainCalls.after.kt +++ b/idea/testData/formatter/FunctionLiteralsInChainCalls.after.kt @@ -33,6 +33,14 @@ fun test3() { } } +fun test4() { + val abc = ArrayList().mapTo( + LinkedHashSet() + ) { + it * 2 + } +} + fun testWithComments() { val abc = ArrayList() // .map { diff --git a/idea/testData/formatter/FunctionLiteralsInChainCalls.kt b/idea/testData/formatter/FunctionLiteralsInChainCalls.kt index 39f7c662201..1f7f5f9aafc 100644 --- a/idea/testData/formatter/FunctionLiteralsInChainCalls.kt +++ b/idea/testData/formatter/FunctionLiteralsInChainCalls.kt @@ -33,6 +33,14 @@ val abc = ArrayList().map { } } +fun test4() { + val abc = ArrayList().mapTo( + LinkedHashSet() + ) { + it * 2 + } +} + fun testWithComments() { val abc = ArrayList() // .map { diff --git a/idea/tests/org/jetbrains/kotlin/formatter/FormatterTestGenerated.java b/idea/tests/org/jetbrains/kotlin/formatter/FormatterTestGenerated.java index d4733a74b46..809d0182f59 100644 --- a/idea/tests/org/jetbrains/kotlin/formatter/FormatterTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/formatter/FormatterTestGenerated.java @@ -284,6 +284,12 @@ public class FormatterTestGenerated extends AbstractFormatterTest { doTest(fileName); } + @TestMetadata("ElvisIndent.after.kt") + public void testElvisIndent() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/formatter/ElvisIndent.after.kt"); + doTest(fileName); + } + @TestMetadata("ElvisWrap.after.kt") public void testElvisWrap() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/formatter/ElvisWrap.after.kt");