Formatter: shouldn't format property chains
#KT-36387 Fixed #KT-36466 Fixed
This commit is contained in:
@@ -123,31 +123,15 @@ abstract class KotlinCommonBlock(
|
||||
return nodeSubBlocks
|
||||
}
|
||||
|
||||
private fun ASTNode.isQualifiedNode(strict: Boolean = false): Boolean {
|
||||
if (strict) return elementType in QUALIFIED_EXPRESSIONS
|
||||
|
||||
var currentNode: ASTNode? = this
|
||||
while (currentNode != null) {
|
||||
if (currentNode.elementType in QUALIFIED_EXPRESSIONS) return true
|
||||
if (currentNode.elementType != PARENTHESIZED && currentNode.psi?.safeAs<KtPostfixExpression>()
|
||||
?.operationToken != EXCLEXCL
|
||||
) return false
|
||||
currentNode = currentNode.treeParent
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun splitSubBlocksOnDot(nodeSubBlocks: List<ASTBlock>): List<ASTBlock> {
|
||||
if (node.treeParent?.isQualifiedNode() == true) return nodeSubBlocks
|
||||
if (!canWrapCallChain(node)) return nodeSubBlocks
|
||||
if (node.treeParent?.isQualifier == true || node.isCallChainWithoutWrap) return nodeSubBlocks
|
||||
|
||||
val operationBlockIndex = nodeSubBlocks.indexOfBlockWithType(QUALIFIED_OPERATION)
|
||||
if (operationBlockIndex == -1) return nodeSubBlocks
|
||||
|
||||
val block = nodeSubBlocks.first()
|
||||
val wrap = createWrapForQualifiedExpression(node)
|
||||
val indent = createIndentForQualifiedExpression(block)
|
||||
val wrap = createWrapForQualifierExpression(node)
|
||||
val indent = createIndentForQualifierExpression(node)
|
||||
val newBlock = block.processBlock(indent, wrap)
|
||||
return nodeSubBlocks.replaceBlock(newBlock, 0).splitAtIndex(operationBlockIndex, indent, wrap)
|
||||
}
|
||||
@@ -158,21 +142,16 @@ abstract class KotlinCommonBlock(
|
||||
@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
|
||||
}
|
||||
POSTFIX_EXPRESSION, in QUALIFIED_EXPRESSIONS -> 0
|
||||
else -> return this
|
||||
}
|
||||
if (elementType != POSTFIX_EXPRESSION && elementType !in QUALIFIED_EXPRESSIONS) return this
|
||||
|
||||
val index = 0
|
||||
val resultWrap = if (currentNode.wrapForFirstCallInChainIsAllowed)
|
||||
wrap.takeIf { elementType != PARENTHESIZED } ?: createWrapForQualifiedExpression(currentNode)
|
||||
wrap ?: createWrapForQualifierExpression(currentNode)
|
||||
else
|
||||
null
|
||||
|
||||
val newBlock = subBlocks.elementAt(index).processBlock(indent, resultWrap)
|
||||
|
||||
return subBlocks.replaceBlock(newBlock, index).let {
|
||||
val operationIndex = subBlocks.indexOfBlockWithType(QUALIFIED_OPERATION)
|
||||
if (operationIndex != -1)
|
||||
@@ -184,23 +163,25 @@ abstract class KotlinCommonBlock(
|
||||
|
||||
private fun List<ASTBlock>.replaceBlock(block: ASTBlock, index: Int = 0): List<ASTBlock> = toMutableList().apply { this[index] = block }
|
||||
|
||||
private val ASTNode.wrapForFirstCallInChainIsAllowed
|
||||
get() = settings.kotlinCommonSettings.WRAP_FIRST_METHOD_IN_CALL_CHAIN ||
|
||||
settings.kotlinCommonSettings.METHOD_CALL_CHAIN_WRAP != CommonCodeStyleSettings.WRAP_AS_NEEDED ||
|
||||
firstChildNode?.isQualifiedNode(true) == true
|
||||
private val ASTNode.wrapForFirstCallInChainIsAllowed: Boolean
|
||||
get() {
|
||||
if (unwrapQualifier()?.isCall != true) return false
|
||||
return settings.kotlinCommonSettings.WRAP_FIRST_METHOD_IN_CALL_CHAIN || receiverIsCall()
|
||||
}
|
||||
|
||||
private fun createWrapForQualifiedExpression(node: ASTNode): Wrap? = if (node.wrapForFirstCallInChainIsAllowed)
|
||||
Wrap.createWrap(
|
||||
settings.kotlinCommonSettings.METHOD_CALL_CHAIN_WRAP,
|
||||
settings.kotlinCommonSettings.WRAP_FIRST_METHOD_IN_CALL_CHAIN,
|
||||
)
|
||||
else
|
||||
null
|
||||
private fun createWrapForQualifierExpression(node: ASTNode): Wrap? =
|
||||
if (node.wrapForFirstCallInChainIsAllowed && node.receiverIsCall())
|
||||
Wrap.createWrap(
|
||||
settings.kotlinCommonSettings.METHOD_CALL_CHAIN_WRAP,
|
||||
true /* wrapFirstElement */,
|
||||
)
|
||||
else
|
||||
null
|
||||
|
||||
private fun createIndentForQualifiedExpression(block: ASTBlock): Indent {
|
||||
private fun createIndentForQualifierExpression(node: ASTNode): 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 enforceIndentToChildren = anyCallInCallChainIsWrapped(node)
|
||||
|
||||
val indentType = if (settings.kotlinCustomSettings.CONTINUATION_INDENT_FOR_CHAINED_CALLS) {
|
||||
if (enforceIndentToChildren) Indent.Type.CONTINUATION else Indent.Type.CONTINUATION_WITHOUT_FIRST
|
||||
@@ -249,29 +230,6 @@ abstract class KotlinCommonBlock(
|
||||
return subList(0, index) + operationSyntheticBlock
|
||||
}
|
||||
|
||||
private fun anyCallInCallChainIsWrapped(astBlock: ASTBlock): Boolean {
|
||||
var result: ASTBlock? = astBlock
|
||||
while (true) {
|
||||
if (result == null || !isCallBlock(result)) return false
|
||||
val dot = result.node?.findChildByType(QUALIFIED_OPERATION)
|
||||
if (dot != null && hasLineBreakBefore(dot)) {
|
||||
return true
|
||||
}
|
||||
result = result.subBlocks.firstOrNull() as? ASTBlock?
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCallBlock(astBlock: ASTBlock): Boolean {
|
||||
val node = astBlock.requireNode()
|
||||
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.isQualifiedNode() } ?: return true
|
||||
return callChainParent.elementType !in QUALIFIED_EXPRESSIONS_WITHOUT_WRAP
|
||||
}
|
||||
|
||||
private fun splitSubBlocksOnElvis(nodeSubBlocks: List<ASTBlock>): List<ASTBlock> {
|
||||
val elvisIndex = nodeSubBlocks.indexOfBlockWithType(ELVIS_SET)
|
||||
if (elvisIndex >= 0) {
|
||||
@@ -827,6 +785,54 @@ abstract class KotlinCommonBlock(
|
||||
}
|
||||
}
|
||||
|
||||
private fun ASTNode.qualifierReceiver(): ASTNode? = unwrapQualifier()?.psi
|
||||
?.safeAs<KtQualifiedExpression>()
|
||||
?.receiverExpression
|
||||
?.node
|
||||
?.unwrapQualifier()
|
||||
|
||||
private tailrec fun ASTNode.unwrapQualifier(): ASTNode? {
|
||||
if (elementType in QUALIFIED_EXPRESSIONS) return this
|
||||
|
||||
val psi = psi as? KtPostfixExpression ?: return null
|
||||
if (psi.operationToken != EXCLEXCL) return null
|
||||
|
||||
return psi.baseExpression?.node?.unwrapQualifier()
|
||||
}
|
||||
|
||||
private fun ASTNode.receiverIsCall(): Boolean = qualifierReceiver()?.isCall == true
|
||||
|
||||
private val ASTNode.isCallChainWithoutWrap: Boolean
|
||||
get() {
|
||||
val callChainParent = parents().firstOrNull { !it.isQualifier } ?: return true
|
||||
return callChainParent.elementType in QUALIFIED_EXPRESSIONS_WITHOUT_WRAP
|
||||
}
|
||||
|
||||
private val ASTNode.isQualifier: Boolean
|
||||
get() {
|
||||
var currentNode: ASTNode? = this
|
||||
while (currentNode != null) {
|
||||
if (currentNode.elementType in QUALIFIED_EXPRESSIONS) return true
|
||||
if (currentNode.psi?.safeAs<KtPostfixExpression>()?.operationToken != EXCLEXCL) return false
|
||||
|
||||
currentNode = currentNode.treeParent
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private val ASTNode.isCall: Boolean
|
||||
get() = unwrapQualifier()?.lastChildNode?.elementType == CALL_EXPRESSION
|
||||
|
||||
private fun anyCallInCallChainIsWrapped(node: ASTNode): Boolean {
|
||||
val sequentialCalls = generateSequence(node) { it.firstChildNode }.takeWhile { it.isCall }
|
||||
|
||||
return sequentialCalls.any { call ->
|
||||
val qualifier = call.findChildByType(QUALIFIED_OPERATION)
|
||||
qualifier != null && hasLineBreakBefore(qualifier)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ASTNode.isFirstParameter(): Boolean = treePrev?.elementType == LPAR
|
||||
|
||||
private fun wrapAfterAnnotation(wrapType: Int): WrappingStrategy {
|
||||
@@ -983,8 +989,7 @@ private val INDENT_RULES = arrayOf(
|
||||
|
||||
strategy("Chained calls")
|
||||
.within(QUALIFIED_EXPRESSIONS)
|
||||
.notForType(DOT, SAFE_ACCESS)
|
||||
.forElement { it.treeParent.firstChildNode != it }
|
||||
.forType(EOL_COMMENT, BLOCK_COMMENT, DOC_COMMENT, SHEBANG_COMMENT)
|
||||
.continuationIf(KotlinCodeStyleSettings::CONTINUATION_INDENT_FOR_CHAINED_CALLS),
|
||||
|
||||
strategy("Colon of delegation list")
|
||||
|
||||
Reference in New Issue
Block a user