Formatter: support Chop down if long for Chained method calls

#KT-23929 Fixed
#KT-33553 Fixed
This commit is contained in:
Dmitry Gridin
2019-11-11 14:33:04 +07:00
parent 351fef5628
commit b6b755506c
13 changed files with 726 additions and 54 deletions
@@ -25,10 +25,12 @@ import org.jetbrains.kotlin.kdoc.parser.KDocElementTypes
import org.jetbrains.kotlin.lexer.KtTokens.* import org.jetbrains.kotlin.lexer.KtTokens.*
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.* 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_OPERATION = TokenSet.create(DOT, SAFE_ACCESS)
private val QUALIFIED_EXPRESSIONS = TokenSet.create(DOT_QUALIFIED_EXPRESSION, SAFE_ACCESS_EXPRESSION) private val QUALIFIED_EXPRESSIONS = TokenSet.create(DOT_QUALIFIED_EXPRESSION, SAFE_ACCESS_EXPRESSION)
private val ELVIS_SET = TokenSet.create(ELVIS) 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 private const val KDOC_COMMENT_INDENT = 1
@@ -120,55 +122,122 @@ abstract class KotlinCommonBlock(
return nodeSubBlocks return nodeSubBlocks
} }
private fun splitSubBlocksOnDot(nodeSubBlocks: List<ASTBlock>): List<ASTBlock> { private fun ASTNode.isQualifiedNode(): Boolean {
val operationBlockIndex = nodeSubBlocks.indexOfBlockWithType(QUALIFIED_OPERATION) var currentNode: ASTNode? = this
if (operationBlockIndex != -1) { while (currentNode != null) {
// Create fake ".something" or "?.something" block here, so child indentation will be if (currentNode.elementType in QUALIFIED_EXPRESSIONS) return true
// relative to it when it starts from new line (see Indent javadoc). if (currentNode.elementType != PARENTHESIZED && currentNode.psi?.safeAs<KtPostfixExpression>()?.operationToken != EXCLEXCL) return false
currentNode = currentNode.treeParent
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)
} }
return nodeSubBlocks
return false
} }
private fun List<ASTBlock>.splitAtIndex(index: Int, indent: Indent?, wrap: Wrap?): List<ASTBlock> { private fun splitSubBlocksOnDot(nodeSubBlocks: List<ASTBlock>): List<ASTBlock> {
val operationBlock = this[index] if (node.treeParent?.isQualifiedNode() == true) return nodeSubBlocks
val operationSyntheticBlock = SyntheticKotlinBlock( if (!canWrapCallChain(node)) return nodeSubBlocks
operationBlock.requireNode(),
subList(index, size), val operationBlockIndex = nodeSubBlocks.indexOfBlockWithType(QUALIFIED_OPERATION)
null, indent, wrap, spacingBuilder if (operationBlockIndex == -1) return nodeSubBlocks
) {
val parent = it.treeParent ?: node val block = nodeSubBlocks.first()
val skipOperationNodeParent = if (parent.elementType === OPERATION_REFERENCE) { val wrap = createWrapForQualifiedExpression(block.requireNode())
parent.treeParent ?: parent val indent = createIndentForQualifiedExpression(block)
} else { val newBlock = block.processBlock(indent, wrap)
parent 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<ASTBlock>
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<ASTBlock>.replaceBlock(
block: ASTBlock,
index: Int = 0
): List<ASTBlock> = 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<ASTBlock>.wrapToBlock(
anchor: ASTNode?,
parentBlock: ASTBlock?
): ASTBlock = splitAtIndex(0, null, null, anchor, parentBlock).single()
private fun List<ASTBlock>.splitAtIndex(
index: Int,
indent: Indent?,
wrap: Wrap?,
anchor: ASTNode? = null,
parentBlock: ASTBlock? = null
): List<ASTBlock> {
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 return subList(0, index) + operationSyntheticBlock
} }
@@ -186,16 +255,13 @@ abstract class KotlinCommonBlock(
private fun isCallBlock(astBlock: ASTBlock): Boolean { private fun isCallBlock(astBlock: ASTBlock): Boolean {
val node = astBlock.requireNode() 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 { private fun canWrapCallChain(node: ASTNode): Boolean {
val callChainParent = node.parents().firstOrNull { it.elementType !in QUALIFIED_EXPRESSIONS } ?: return true val callChainParent = node.parents().firstOrNull { !it.isQualifiedNode() } ?: return true
return callChainParent.elementType in CODE_BLOCKS || return callChainParent.elementType !in QUALIFIED_EXPRESSIONS_WITHOUT_WRAP
callChainParent.elementType == PROPERTY ||
(callChainParent.elementType == BINARY_EXPRESSION &&
(callChainParent.psi as KtBinaryExpression).operationToken in ALL_ASSIGNMENTS) ||
callChainParent.elementType == RETURN
} }
private fun splitSubBlocksOnElvis(nodeSubBlocks: List<ASTBlock>): List<ASTBlock> { private fun splitSubBlocksOnElvis(nodeSubBlocks: List<ASTBlock>): List<ASTBlock> {
@@ -4,7 +4,9 @@ interface Test {
fun test(t: Test) { fun test(t: Test) {
t.foo() t.foo()
t().foo()
t()!!.foo().foo()
((t()!!).foo().foo().foo()).foo()
t.foo() t.foo()
t.foo() t.foo()
+3 -1
View File
@@ -4,7 +4,9 @@ interface Test {
fun test(t: Test) { fun test(t: Test) {
t . foo() t . foo()
t() . foo()
t() !! . foo() .foo()
( ( t() !!) . foo() .foo() .foo()) .foo()
t. t.
foo() foo()
@@ -3,7 +3,10 @@ val x = foo
.baz() .baz()
.quux() .quux()
val y = xyzzy(foo.bar().baz().quux()) val y = xyzzy(foo
.bar()
.baz()
.quux())
fun foo() { fun foo() {
foo foo
@@ -27,5 +30,25 @@ fun foo() {
.quux() .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_INT: METHOD_CALL_CHAIN_WRAP = 2
// SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN // SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN
@@ -2,7 +2,9 @@ val x = foo.bar()
.baz() .baz()
.quux() .quux()
val y = xyzzy(foo.bar().baz().quux()) val y = xyzzy(foo.bar()
.baz()
.quux())
fun foo() { fun foo() {
foo.bar() foo.bar()
@@ -22,5 +24,21 @@ fun foo() {
.quux() .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_INT: METHOD_CALL_CHAIN_WRAP = 2
// SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN // SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN
@@ -12,5 +12,14 @@ fun foo() {
return foo.bar().baz().quux() 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_INT: METHOD_CALL_CHAIN_WRAP = 2
// SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN // SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,12 @@
fun f(list: List<Int>) {
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
@@ -0,0 +1,10 @@
fun f(list: List<Int>) {
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
@@ -0,0 +1,8 @@
fun f(list: List<Int>) {
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
@@ -852,6 +852,16 @@ public class FormatterTestGenerated extends AbstractFormatterTest {
runTest("idea/testData/formatter/callChain/CallChainWrapping.after.kt"); 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") @TestMetadata("ConsecutiveCalls.after.kt")
public void testConsecutiveCalls() throws Exception { public void testConsecutiveCalls() throws Exception {
runTest("idea/testData/formatter/callChain/ConsecutiveCalls.after.kt"); 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"); 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") @TestMetadata("ConsecutiveSafeCallsIndent.after.inv.kt")
public void testConsecutiveSafeCallsIndent() throws Exception { public void testConsecutiveSafeCallsIndent() throws Exception {
runTest("idea/testData/formatter/callChain/ConsecutiveSafeCallsIndent.after.inv.kt"); runTest("idea/testData/formatter/callChain/ConsecutiveSafeCallsIndent.after.inv.kt");