Fix indent of expressions following elvis operator

This commit is contained in:
Dmitry Jemerov
2017-12-11 11:57:38 +01:00
parent 87d2d16cda
commit 900ec82614
7 changed files with 112 additions and 25 deletions
@@ -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<ASTBlock>): List<ASTBlock> {
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<ASTBlock>.splitAtIndex(index: Int, indent: Indent?, wrap: Wrap?): List<ASTBlock> {
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<ASTBlock>): List<ASTBlock> {
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<ASTBlock>, tokenSet: TokenSet): Int {
return blocks.indexOfFirst { block -> block.node?.elementType in tokenSet }
private fun List<ASTBlock>.indexOfBlockWithType(tokenSet: TokenSet): Int {
return indexOfFirst { block -> block.node?.elementType in tokenSet }
}
+14
View File
@@ -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
+14
View File
@@ -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
@@ -33,6 +33,14 @@ fun test3() {
}
}
fun test4() {
val abc = ArrayList<Int>().mapTo(
LinkedHashSet()
) {
it * 2
}
}
fun testWithComments() {
val abc = ArrayList<Int>()
// .map {
@@ -33,6 +33,14 @@ fun test3() {
}
}
fun test4() {
val abc = ArrayList<Int>().mapTo(
LinkedHashSet()
) {
it * 2
}
}
fun testWithComments() {
val abc = ArrayList<Int>()
// .map {
@@ -33,6 +33,14 @@ val abc = ArrayList<Int>().map {
}
}
fun test4() {
val abc = ArrayList<Int>().mapTo(
LinkedHashSet()
) {
it * 2
}
}
fun testWithComments() {
val abc = ArrayList<Int>()
// .map {
@@ -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");