Initial support for chained call wrapping options

#KT-21529 Fixed
This commit is contained in:
Dmitry Jemerov
2017-11-30 16:17:09 +01:00
parent 1a93d10697
commit dc4e673fb1
7 changed files with 124 additions and 5 deletions
@@ -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 ->
@@ -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")
+31
View File
@@ -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
+26
View File
@@ -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
View File
@@ -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");