Formatter: support trailing comma in value arguments

#KT-34744
This commit is contained in:
Dmitry Gridin
2019-12-25 18:31:30 +07:00
parent 13ae4a28a8
commit c195cd46f2
70 changed files with 9829 additions and 1575 deletions
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.formatter
import com.intellij.formatting.*
import com.intellij.lang.ASTNode
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.TokenType
@@ -19,7 +20,7 @@ import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.KtNodeTypes.*
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
import org.jetbrains.kotlin.idea.formatter.NodeIndentStrategy.Companion.strategy
import org.jetbrains.kotlin.idea.util.getLineCount
import org.jetbrains.kotlin.idea.util.isMultiline
import org.jetbrains.kotlin.idea.util.requireNode
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
import org.jetbrains.kotlin.kdoc.parser.KDocElementTypes
@@ -585,29 +586,37 @@ abstract class KotlinCommonBlock(
when {
elementType === VALUE_ARGUMENT_LIST -> {
val wrapSetting = commonSettings.CALL_PARAMETERS_WRAP
if ((wrapSetting == CommonCodeStyleSettings.WRAP_AS_NEEDED || wrapSetting == CommonCodeStyleSettings.WRAP_ON_EVERY_ITEM) &&
if (!node.withTrailingComma && (wrapSetting == CommonCodeStyleSettings.WRAP_AS_NEEDED || wrapSetting == CommonCodeStyleSettings.WRAP_ON_EVERY_ITEM) &&
!needWrapArgumentList(nodePsi)
) {
return ::noWrapping
}
return getWrappingStrategyForItemList(wrapSetting, VALUE_ARGUMENT)
return getWrappingStrategyForItemList(
wrapSetting,
VALUE_ARGUMENT,
node.withTrailingComma,
additionalWrap = trailingCommaWrappingStrategy(
LPAR,
RPAR,
thisOrPrevIsMultiLineElement(VALUE_ARGUMENT, COMMA, LPAR, RPAR)
)
)
}
elementType === VALUE_PARAMETER_LIST -> {
if (parentElementType === FUN ||
parentElementType === PRIMARY_CONSTRUCTOR ||
parentElementType === SECONDARY_CONSTRUCTOR
) {
val wrap = Wrap.createWrap(commonSettings.METHOD_PARAMETERS_WRAP, false)
val withTrailingComma = node.withTrailingComma
return { childElement ->
val childElementType = childElement.elementType
if (withTrailingComma && (childElementType === RPAR || getPrevWithoutWhitespaceAndComments(childElement)?.elementType === LPAR))
Wrap.createWrap(WrapType.ALWAYS, true)
else
wrap.takeIf { childElementType === VALUE_PARAMETER }
}
}
) return getWrappingStrategyForItemList(
commonSettings.METHOD_PARAMETERS_WRAP,
VALUE_PARAMETER,
node.withTrailingComma,
additionalWrap = trailingCommaWrappingStrategy(
LPAR,
RPAR,
thisOrPrevIsMultiLineElement(VALUE_PARAMETER, COMMA, LPAR, RPAR)
)
)
}
elementType === SUPER_TYPE_LIST -> {
@@ -710,9 +719,74 @@ abstract class KotlinCommonBlock(
private val ASTNode.withTrailingComma: Boolean
get() = when {
lastChildNode?.let { getPrevWithoutWhitespaceAndComments(it) }?.elementType === COMMA -> true
settings.kotlinCustomSettings.ALLOW_TRAILING_COMMA -> psi?.getLineCount()?.let { it > 1 } == true
settings.kotlinCustomSettings.ALLOW_TRAILING_COMMA -> psi?.let(PsiElement::isMultiline) == true
else -> false
}
private fun ASTNode.getSiblingNodeInSequence(
forward: Boolean,
withBreakElement: Boolean,
breakType: IElementType,
barrier: IElementType
): ASTNode? {
var sibling: ASTNode? = null
for (element in siblings(forward).filter { it.elementType != WHITE_SPACE }.takeWhile { it.elementType != barrier }) {
if (withBreakElement) {
sibling = element
if (element.elementType == breakType) break
} else {
if (element.elementType == breakType) break
sibling = element
}
}
return sibling
}
private fun thisOrPrevIsMultiLineElement(
type: IElementType,
delimiterType: IElementType,
leftBarrier: IElementType,
rightBarrier: IElementType
) = fun(childElement: ASTNode): Boolean {
if (childElement.elementType != type) return false
val psi = childElement.psi ?: return false
if (psi.isMultiline()) return true
val startOffset = childElement.getSiblingNodeInSequence(
forward = false,
withBreakElement = true,
breakType = type,
barrier = leftBarrier
)?.startOffset ?: psi.startOffset
val endOffset = childElement.getSiblingNodeInSequence(
forward = true,
withBreakElement = false,
breakType = delimiterType,
barrier = rightBarrier
)?.psi?.endOffset ?: psi.endOffset
val treeParent = childElement.treeParent
val textRange = TextRange.create(startOffset, endOffset).shiftLeft(treeParent.startOffset)
return StringUtil.containsLineBreak(textRange.subSequence(treeParent.text))
}
private fun trailingCommaWrappingStrategy(
leftAnchor: IElementType,
rightAnchor: IElementType,
additionalCheck: (ASTNode) -> Boolean
): WrappingStrategy = { childElement ->
val childElementType = childElement.elementType
if (childElement.treeParent.withTrailingComma && (childElementType === rightAnchor ||
getPrevWithoutWhitespaceAndComments(childElement)?.elementType === leftAnchor ||
additionalCheck(childElement)
)
)
Wrap.createWrap(WrapType.ALWAYS, true)
else
null
}
}
private fun ASTNode.isFirstParameter(): Boolean = treePrev?.elementType == LPAR
@@ -1028,9 +1102,16 @@ private fun getPrevWithoutWhitespaceAndComments(pNode: ASTNode): ASTNode? {
}
}
private fun getWrappingStrategyForItemList(wrapType: Int, itemType: IElementType, wrapFirstElement: Boolean = false): WrappingStrategy {
private fun getWrappingStrategyForItemList(
wrapType: Int,
itemType: IElementType,
wrapFirstElement: Boolean = false,
additionalWrap: WrappingStrategy? = null
): WrappingStrategy {
val itemWrap = Wrap.createWrap(wrapType, wrapFirstElement)
return { childElement -> if (childElement.elementType === itemType) itemWrap else null }
return { childElement ->
additionalWrap?.invoke(childElement) ?: if (childElement.elementType === itemType) itemWrap else null
}
}
private fun getWrappingStrategyForItemList(wrapType: Int, itemTypes: TokenSet, wrapFirstElement: Boolean = false): WrappingStrategy {
@@ -190,32 +190,34 @@ fun createSpacingBuilder(settings: CodeStyleSettings, builderUtil: KotlinSpacing
)
}
inPosition(parent = VALUE_ARGUMENT_LIST, left = LPAR).customRule { parent, _, _ ->
if (kotlinCommonSettings.CALL_PARAMETERS_LPAREN_ON_NEXT_LINE && needWrapArgumentList(parent.requireNode().psi)) {
Spacing.createDependentLFSpacing(
0, 0,
excludeLambdasAndObjects(parent),
commonCodeStyleSettings.KEEP_LINE_BREAKS,
commonCodeStyleSettings.KEEP_BLANK_LINES_IN_CODE
)
} else {
createSpacing(0)
}
}
inPosition(parent = VALUE_ARGUMENT_LIST, right = RPAR).customRule { parent, left, _ ->
when {
kotlinCommonSettings.CALL_PARAMETERS_RPAREN_ON_NEXT_LINE ->
if (!kotlinCustomSettings.ALLOW_TRAILING_COMMA) {
inPosition(parent = VALUE_ARGUMENT_LIST, left = LPAR).customRule { parent, _, _ ->
if (kotlinCommonSettings.CALL_PARAMETERS_LPAREN_ON_NEXT_LINE && needWrapArgumentList(parent.requireNode().psi)) {
Spacing.createDependentLFSpacing(
0, 0,
excludeLambdasAndObjects(parent),
commonCodeStyleSettings.KEEP_LINE_BREAKS,
commonCodeStyleSettings.KEEP_BLANK_LINES_IN_CODE
)
left.requireNode().elementType == COMMA -> // incomplete call being edited
createSpacing(1)
else ->
} else {
createSpacing(0)
}
}
inPosition(parent = VALUE_ARGUMENT_LIST, right = RPAR).customRule { parent, left, _ ->
when {
kotlinCommonSettings.CALL_PARAMETERS_RPAREN_ON_NEXT_LINE ->
Spacing.createDependentLFSpacing(
0, 0,
excludeLambdasAndObjects(parent),
commonCodeStyleSettings.KEEP_LINE_BREAKS,
commonCodeStyleSettings.KEEP_BLANK_LINES_IN_CODE
)
left.requireNode().elementType == COMMA -> // incomplete call being edited
createSpacing(1)
else ->
createSpacing(0)
}
}
}
@@ -36,4 +36,6 @@ fun PsiElement.getLineCount(): Int {
}
return StringUtil.getLineBreakCount(text ?: "") + 1
}
}
fun PsiElement.isMultiline() = getLineCount() > 1