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 KtModifierList.hasActualModifier() = hasModifier(KtTokens.IMPL_KEYWORD) || hasModifier(KtTokens.ACTUAL_KEYWORD)
|
||||||
|
|
||||||
fun ASTNode.children() = generateSequence(firstChildNode) { node -> node.treeNext }
|
fun ASTNode.children() = generateSequence(firstChildNode) { node -> node.treeNext }
|
||||||
|
fun ASTNode.parents() = generateSequence(treeParent) { node -> node.treeParent }
|
||||||
|
|
||||||
fun ASTNode.siblings(forward: Boolean = true): Sequence<ASTNode> {
|
fun ASTNode.siblings(forward: Boolean = true): Sequence<ASTNode> {
|
||||||
if (forward) {
|
if (forward) {
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ import org.jetbrains.kotlin.lexer.KtTokens
|
|||||||
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.children
|
import org.jetbrains.kotlin.psi.psiUtil.children
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.siblings
|
import org.jetbrains.kotlin.psi.psiUtil.siblings
|
||||||
|
|
||||||
private val QUALIFIED_OPERATION = TokenSet.create(DOT, SAFE_ACCESS)
|
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 KDOC_COMMENT_INDENT = 1
|
||||||
|
|
||||||
private val BINARY_EXPRESSIONS = TokenSet.create(KtNodeTypes.BINARY_EXPRESSION, KtNodeTypes.BINARY_WITH_TYPE, KtNodeTypes.IS_EXPRESSION)
|
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()
|
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)
|
val operationBlockIndex = findNodeBlockIndex(nodeSubBlocks, QUALIFIED_OPERATION)
|
||||||
if (operationBlockIndex != -1) {
|
if (operationBlockIndex != -1) {
|
||||||
// Create fake ".something" or "?.something" block here, so child indentation will be
|
// Create fake ".something" or "?.something" block here, so child indentation will be
|
||||||
@@ -103,10 +105,17 @@ abstract class KotlinCommonBlock(
|
|||||||
Indent.getContinuationWithoutFirstIndent()
|
Indent.getContinuationWithoutFirstIndent()
|
||||||
else
|
else
|
||||||
Indent.getNormalIndent()
|
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(
|
val operationSyntheticBlock = SyntheticKotlinBlock(
|
||||||
operationBlock.node,
|
operationBlock.node,
|
||||||
nodeSubBlocks.subList(operationBlockIndex, nodeSubBlocks.size),
|
nodeSubBlocks.subList(operationBlockIndex, nodeSubBlocks.size),
|
||||||
null, indent, null, spacingBuilder) { createSyntheticSpacingNodeBlock(it) }
|
null, indent, wrap, spacingBuilder) { createSyntheticSpacingNodeBlock(it) }
|
||||||
|
|
||||||
nodeSubBlocks = nodeSubBlocks.subList(0, operationBlockIndex) + operationSyntheticBlock
|
nodeSubBlocks = nodeSubBlocks.subList(0, operationBlockIndex) + operationSyntheticBlock
|
||||||
}
|
}
|
||||||
@@ -117,6 +126,20 @@ abstract class KotlinCommonBlock(
|
|||||||
return nodeSubBlocks
|
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? {
|
fun createChildIndent(child: ASTNode): Indent? {
|
||||||
val childParent = child.treeParent
|
val childParent = child.treeParent
|
||||||
val childType = child.elementType
|
val childType = child.elementType
|
||||||
@@ -182,7 +205,7 @@ abstract class KotlinCommonBlock(
|
|||||||
|
|
||||||
KtNodeTypes.TRY -> ChildAttributes(Indent.getNoneIndent(), null)
|
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 -> {
|
KtNodeTypes.VALUE_PARAMETER_LIST, KtNodeTypes.VALUE_ARGUMENT_LIST -> {
|
||||||
val subBlocks = getSubBlocks()
|
val subBlocks = getSubBlocks()
|
||||||
@@ -463,7 +486,7 @@ private val INDENT_RULES = arrayOf<NodeIndentStrategy>(
|
|||||||
.set(Indent.getContinuationWithoutFirstIndent()),
|
.set(Indent.getContinuationWithoutFirstIndent()),
|
||||||
|
|
||||||
strategy("Chained calls")
|
strategy("Chained calls")
|
||||||
.within(KtNodeTypes.DOT_QUALIFIED_EXPRESSION, KtNodeTypes.SAFE_ACCESS_EXPRESSION)
|
.within(QUALIFIED_EXPRESSIONS)
|
||||||
.notForType(KtTokens.DOT, KtTokens.SAFE_ACCESS)
|
.notForType(KtTokens.DOT, KtTokens.SAFE_ACCESS)
|
||||||
.forElement { it.treeParent.firstChildNode != it }
|
.forElement { it.treeParent.firstChildNode != it }
|
||||||
.set { settings ->
|
.set { settings ->
|
||||||
|
|||||||
+11
-1
@@ -51,6 +51,14 @@ class KotlinLanguageCodeStyleSettingsProvider : LanguageCodeStyleSettingsProvide
|
|||||||
param2: String) {
|
param2: String) {
|
||||||
@Deprecated val foo = 1
|
@Deprecated val foo = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun multilineMethod(
|
||||||
|
foo: String,
|
||||||
|
bar: String
|
||||||
|
) {
|
||||||
|
foo.toUpperCase().trim()
|
||||||
|
.length
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated val bar = 1
|
@Deprecated val bar = 1
|
||||||
@@ -247,7 +255,9 @@ class KotlinLanguageCodeStyleSettingsProvider : LanguageCodeStyleSettingsProvide
|
|||||||
"METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE",
|
"METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE",
|
||||||
"CALL_PARAMETERS_LPAREN_ON_NEXT_LINE",
|
"CALL_PARAMETERS_LPAREN_ON_NEXT_LINE",
|
||||||
"CALL_PARAMETERS_RPAREN_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(CodeStyleSettingsCustomizable.WRAPPING_SWITCH_STATEMENT, "'when' statements")
|
||||||
consumer.renameStandardOption("FIELD_ANNOTATION_WRAP", "Property annotations")
|
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);
|
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")
|
@TestMetadata("CallLParenthOnNextLine.after.kt")
|
||||||
public void testCallLParenthOnNextLine() throws Exception {
|
public void testCallLParenthOnNextLine() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/formatter/CallLParenthOnNextLine.after.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/formatter/CallLParenthOnNextLine.after.kt");
|
||||||
@@ -1148,6 +1154,12 @@ public class FormatterTestGenerated extends AbstractFormatterTest {
|
|||||||
doTestInverted(fileName);
|
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")
|
@TestMetadata("CallLParenthOnNextLine.after.inv.kt")
|
||||||
public void testCallLParenthOnNextLine() throws Exception {
|
public void testCallLParenthOnNextLine() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/formatter/CallLParenthOnNextLine.after.inv.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/formatter/CallLParenthOnNextLine.after.inv.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user