Initial support for chained call wrapping options
#KT-21529 Fixed
This commit is contained in:
@@ -391,6 +391,7 @@ fun KtModifierListOwner.hasActualModifier() = hasModifier(KtTokens.IMPL_KEYWORD)
|
||||
fun KtModifierList.hasActualModifier() = hasModifier(KtTokens.IMPL_KEYWORD) || hasModifier(KtTokens.ACTUAL_KEYWORD)
|
||||
|
||||
fun ASTNode.children() = generateSequence(firstChildNode) { node -> node.treeNext }
|
||||
fun ASTNode.parents() = generateSequence(treeParent) { node -> node.treeParent }
|
||||
|
||||
fun ASTNode.siblings(forward: Boolean = true): Sequence<ASTNode> {
|
||||
if (forward) {
|
||||
|
||||
@@ -34,9 +34,11 @@ import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.lexer.KtTokens.*
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.children
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
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 KDOC_COMMENT_INDENT = 1
|
||||
|
||||
private val BINARY_EXPRESSIONS = TokenSet.create(KtNodeTypes.BINARY_EXPRESSION, KtNodeTypes.BINARY_WITH_TYPE, KtNodeTypes.IS_EXPRESSION)
|
||||
@@ -92,7 +94,7 @@ abstract class KotlinCommonBlock(
|
||||
|
||||
var nodeSubBlocks = buildSubBlocks()
|
||||
|
||||
if (node.elementType === KtNodeTypes.DOT_QUALIFIED_EXPRESSION || node.elementType === KtNodeTypes.SAFE_ACCESS_EXPRESSION) {
|
||||
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
|
||||
@@ -103,10 +105,17 @@ abstract class KotlinCommonBlock(
|
||||
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, null, spacingBuilder) { createSyntheticSpacingNodeBlock(it) }
|
||||
null, indent, wrap, spacingBuilder) { createSyntheticSpacingNodeBlock(it) }
|
||||
|
||||
nodeSubBlocks = nodeSubBlocks.subList(0, operationBlockIndex) + operationSyntheticBlock
|
||||
}
|
||||
@@ -117,6 +126,20 @@ abstract class KotlinCommonBlock(
|
||||
return nodeSubBlocks
|
||||
}
|
||||
|
||||
private fun isCallBlock(astBlock: ASTBlock): Boolean {
|
||||
val node = astBlock.node
|
||||
return node.elementType in QUALIFIED_EXPRESSIONS && node.lastChildNode?.elementType == KtNodeTypes.CALL_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 == KtNodeTypes.PROPERTY ||
|
||||
(callChainParent.elementType == KtNodeTypes.BINARY_EXPRESSION &&
|
||||
(callChainParent.psi as KtBinaryExpression).operationToken in KtTokens.ALL_ASSIGNMENTS) ||
|
||||
callChainParent.elementType == KtNodeTypes.RETURN
|
||||
}
|
||||
|
||||
fun createChildIndent(child: ASTNode): Indent? {
|
||||
val childParent = child.treeParent
|
||||
val childType = child.elementType
|
||||
@@ -182,7 +205,7 @@ abstract class KotlinCommonBlock(
|
||||
|
||||
KtNodeTypes.TRY -> ChildAttributes(Indent.getNoneIndent(), null)
|
||||
|
||||
KtNodeTypes.DOT_QUALIFIED_EXPRESSION, KtNodeTypes.SAFE_ACCESS_EXPRESSION -> ChildAttributes(Indent.getContinuationWithoutFirstIndent(), null)
|
||||
in QUALIFIED_EXPRESSIONS -> ChildAttributes(Indent.getContinuationWithoutFirstIndent(), null)
|
||||
|
||||
KtNodeTypes.VALUE_PARAMETER_LIST, KtNodeTypes.VALUE_ARGUMENT_LIST -> {
|
||||
val subBlocks = getSubBlocks()
|
||||
@@ -463,7 +486,7 @@ private val INDENT_RULES = arrayOf<NodeIndentStrategy>(
|
||||
.set(Indent.getContinuationWithoutFirstIndent()),
|
||||
|
||||
strategy("Chained calls")
|
||||
.within(KtNodeTypes.DOT_QUALIFIED_EXPRESSION, KtNodeTypes.SAFE_ACCESS_EXPRESSION)
|
||||
.within(QUALIFIED_EXPRESSIONS)
|
||||
.notForType(KtTokens.DOT, KtTokens.SAFE_ACCESS)
|
||||
.forElement { it.treeParent.firstChildNode != it }
|
||||
.set { settings ->
|
||||
|
||||
+11
-1
@@ -51,6 +51,14 @@ class KotlinLanguageCodeStyleSettingsProvider : LanguageCodeStyleSettingsProvide
|
||||
param2: String) {
|
||||
@Deprecated val foo = 1
|
||||
}
|
||||
|
||||
fun multilineMethod(
|
||||
foo: String,
|
||||
bar: String
|
||||
) {
|
||||
foo.toUpperCase().trim()
|
||||
.length
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated val bar = 1
|
||||
@@ -247,7 +255,9 @@ class KotlinLanguageCodeStyleSettingsProvider : LanguageCodeStyleSettingsProvide
|
||||
"METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE",
|
||||
"CALL_PARAMETERS_LPAREN_ON_NEXT_LINE",
|
||||
"CALL_PARAMETERS_RPAREN_ON_NEXT_LINE",
|
||||
"ENUM_CONSTANTS_WRAP"
|
||||
"ENUM_CONSTANTS_WRAP",
|
||||
"METHOD_CALL_CHAIN_WRAP",
|
||||
"WRAP_FIRST_METHOD_IN_CALL_CHAIN"
|
||||
)
|
||||
consumer.renameStandardOption(CodeStyleSettingsCustomizable.WRAPPING_SWITCH_STATEMENT, "'when' statements")
|
||||
consumer.renameStandardOption("FIELD_ANNOTATION_WRAP", "Property annotations")
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
val x = foo
|
||||
.bar()
|
||||
.baz()
|
||||
.quux()
|
||||
|
||||
val y = xyzzy(foo.bar().baz().quux())
|
||||
|
||||
fun foo() {
|
||||
foo
|
||||
.bar()
|
||||
.baz()
|
||||
.quux()
|
||||
|
||||
z = foo
|
||||
.bar()
|
||||
.baz()
|
||||
.quux()
|
||||
|
||||
z += foo
|
||||
.bar()
|
||||
.baz()
|
||||
.quux()
|
||||
|
||||
return foo
|
||||
.bar()
|
||||
.baz()
|
||||
.quux()
|
||||
}
|
||||
|
||||
// SET_INT: METHOD_CALL_CHAIN_WRAP = 2
|
||||
// SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN
|
||||
@@ -0,0 +1,26 @@
|
||||
val x = foo.bar()
|
||||
.baz()
|
||||
.quux()
|
||||
|
||||
val y = xyzzy(foo.bar().baz().quux())
|
||||
|
||||
fun foo() {
|
||||
foo.bar()
|
||||
.baz()
|
||||
.quux()
|
||||
|
||||
z = foo.bar()
|
||||
.baz()
|
||||
.quux()
|
||||
|
||||
z += foo.bar()
|
||||
.baz()
|
||||
.quux()
|
||||
|
||||
return foo.bar()
|
||||
.baz()
|
||||
.quux()
|
||||
}
|
||||
|
||||
// SET_INT: METHOD_CALL_CHAIN_WRAP = 2
|
||||
// SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
val x = foo.bar().baz().quux()
|
||||
|
||||
val y = xyzzy(foo.bar().baz().quux())
|
||||
|
||||
fun foo() {
|
||||
foo.bar().baz().quux()
|
||||
|
||||
z = foo.bar().baz().quux()
|
||||
|
||||
z += foo.bar().baz().quux()
|
||||
|
||||
return foo.bar().baz().quux()
|
||||
}
|
||||
|
||||
// SET_INT: METHOD_CALL_CHAIN_WRAP = 2
|
||||
// SET_FALSE: WRAP_FIRST_METHOD_IN_CALL_CHAIN
|
||||
@@ -128,6 +128,12 @@ public class FormatterTestGenerated extends AbstractFormatterTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("CallChainWrapping.after.kt")
|
||||
public void testCallChainWrapping() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/formatter/CallChainWrapping.after.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("CallLParenthOnNextLine.after.kt")
|
||||
public void testCallLParenthOnNextLine() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/formatter/CallLParenthOnNextLine.after.kt");
|
||||
@@ -1148,6 +1154,12 @@ public class FormatterTestGenerated extends AbstractFormatterTest {
|
||||
doTestInverted(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("CallChainWrapping.after.inv.kt")
|
||||
public void testCallChainWrapping() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/formatter/CallChainWrapping.after.inv.kt");
|
||||
doTestInverted(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("CallLParenthOnNextLine.after.inv.kt")
|
||||
public void testCallLParenthOnNextLine() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/formatter/CallLParenthOnNextLine.after.inv.kt");
|
||||
|
||||
Reference in New Issue
Block a user