Introduce TrailingCommaState
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.formatter
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiErrorElement
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettings
|
||||
import com.intellij.psi.tree.TokenSet
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import org.jetbrains.kotlin.idea.util.*
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
|
||||
import org.jetbrains.kotlin.psi.psiUtil.prevLeaf
|
||||
import org.jetbrains.kotlin.psi.psiUtil.siblings
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
|
||||
object TrailingCommaHelper {
|
||||
fun findInvalidCommas(commaOwner: KtElement): List<PsiElement> = commaOwner.firstChild
|
||||
?.siblings(withItself = false)
|
||||
?.filter { it.isComma }
|
||||
?.filter {
|
||||
it.prevLeaf(true)?.isLineBreak() == true || it.leafIgnoringWhitespace(false) != it.leafIgnoringWhitespaceAndComments(false)
|
||||
}?.toList().orEmpty()
|
||||
|
||||
fun needComma(
|
||||
commaOwner: KtElement,
|
||||
settings: CodeStyleSettings?,
|
||||
checkExistingTrailingComma: Boolean = true,
|
||||
): Boolean = when {
|
||||
commaOwner is KtWhenEntry ->
|
||||
commaOwner.needTrailingComma(settings, checkExistingTrailingComma)
|
||||
|
||||
commaOwner.parent is KtFunctionLiteral ->
|
||||
commaOwner.parent.cast<KtFunctionLiteral>().needTrailingComma(settings, checkExistingTrailingComma)
|
||||
|
||||
commaOwner is KtDestructuringDeclaration ->
|
||||
commaOwner.needTrailingComma(settings, checkExistingTrailingComma)
|
||||
|
||||
else -> (checkExistingTrailingComma &&
|
||||
trailingCommaOrLastElement(commaOwner)?.isComma == true ||
|
||||
settings?.kotlinCustomSettings?.addTrailingCommaIsAllowedFor(commaOwner) != false) &&
|
||||
commaOwner.isMultiline()
|
||||
}
|
||||
|
||||
fun trailingCommaExists(commaOwner: KtElement): Boolean = when (commaOwner) {
|
||||
is KtFunctionLiteral -> commaOwner.valueParameterList?.trailingComma != null
|
||||
is KtWhenEntry -> commaOwner.trailingComma != null
|
||||
is KtDestructuringDeclaration -> commaOwner.trailingComma != null
|
||||
else -> trailingCommaOrLastElement(commaOwner)?.isComma == true
|
||||
}
|
||||
|
||||
fun trailingCommaOrLastElement(commaOwner: KtElement): PsiElement? {
|
||||
val lastChild = commaOwner.lastSignificantChild ?: return null
|
||||
val withSelf = when (PsiUtil.getElementType(lastChild)) {
|
||||
KtTokens.COMMA -> return lastChild
|
||||
in RIGHT_BARRIERS -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
return lastChild.getPrevSiblingIgnoringWhitespaceAndComments(withSelf)?.takeIf {
|
||||
PsiUtil.getElementType(it) !in LEFT_BARRIERS
|
||||
}?.takeIfIsNotError()
|
||||
}
|
||||
|
||||
fun elementBeforeFirstElement(commaOwner: KtElement): PsiElement? = when (commaOwner) {
|
||||
is KtParameterList -> {
|
||||
val parent = commaOwner.parent
|
||||
if (parent is KtFunctionLiteral) parent.lBrace else commaOwner.leftParenthesis
|
||||
}
|
||||
is KtWhenEntry -> commaOwner.parent.cast<KtWhenExpression>().openBrace
|
||||
is KtDestructuringDeclaration -> commaOwner.lPar
|
||||
else -> commaOwner.firstChild?.takeIfIsNotError()
|
||||
}
|
||||
|
||||
fun elementAfterLastElement(commaOwner: KtElement): PsiElement? = when (commaOwner) {
|
||||
is KtParameterList -> {
|
||||
val parent = commaOwner.parent
|
||||
if (parent is KtFunctionLiteral) parent.arrow else commaOwner.rightParenthesis
|
||||
}
|
||||
is KtWhenEntry -> commaOwner.arrow
|
||||
is KtDestructuringDeclaration -> commaOwner.rPar
|
||||
else -> commaOwner.lastChild?.takeIfIsNotError()
|
||||
}
|
||||
|
||||
private fun PsiElement.takeIfIsNotError(): PsiElement? = takeIf { it !is PsiErrorElement }
|
||||
|
||||
private val RIGHT_BARRIERS = TokenSet.create(KtTokens.RBRACKET, KtTokens.RPAR, KtTokens.RBRACE, KtTokens.GT, KtTokens.ARROW)
|
||||
private val LEFT_BARRIERS = TokenSet.create(KtTokens.LBRACKET, KtTokens.LPAR, KtTokens.LBRACE, KtTokens.LT)
|
||||
private val PsiElement.lastSignificantChild: PsiElement?
|
||||
get() = when (this) {
|
||||
is KtWhenEntry -> arrow
|
||||
is KtDestructuringDeclaration -> rPar
|
||||
else -> lastChild
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.formatter
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.idea.util.canAddTrailingComma
|
||||
import org.jetbrains.kotlin.idea.util.containsLineBreakInThis
|
||||
import org.jetbrains.kotlin.idea.util.isMultiline
|
||||
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFunctionLiteral
|
||||
import org.jetbrains.kotlin.psi.KtWhenEntry
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
|
||||
enum class TrailingCommaState {
|
||||
/**
|
||||
* The trailing comma is needed and exists
|
||||
*/
|
||||
EXISTS,
|
||||
|
||||
/**
|
||||
* The trailing comma is needed and doesn't exists
|
||||
*/
|
||||
MISSING,
|
||||
|
||||
/**
|
||||
* The trailing comma isn't needed and doesn't exists
|
||||
*/
|
||||
NOT_EXISTS,
|
||||
|
||||
/**
|
||||
* The trailing comma isn't needed, but exists
|
||||
*/
|
||||
REDUNDANT,
|
||||
|
||||
/**
|
||||
* The trailing comma isn't applicable for this element
|
||||
*/
|
||||
NOT_APPLICABLE,
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun stateForElement(element: PsiElement): TrailingCommaState = when {
|
||||
element !is KtElement || !element.canAddTrailingComma() -> NOT_APPLICABLE
|
||||
isMultiline(element) ->
|
||||
if (TrailingCommaHelper.trailingCommaExists(element))
|
||||
EXISTS
|
||||
else
|
||||
MISSING
|
||||
else ->
|
||||
if (TrailingCommaHelper.trailingCommaExists(element))
|
||||
REDUNDANT
|
||||
else
|
||||
NOT_EXISTS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isMultiline(ktElement: KtElement): Boolean = when {
|
||||
ktElement.parent is KtFunctionLiteral -> isMultiline(ktElement.parent as KtElement)
|
||||
|
||||
ktElement is KtFunctionLiteral -> ktElement.isMultiline(
|
||||
startOffsetGetter = { valueParameterList?.startOffset },
|
||||
endOffsetGetter = { arrow?.endOffset },
|
||||
)
|
||||
|
||||
ktElement is KtWhenEntry -> ktElement.isMultiline(
|
||||
startOffsetGetter = { startOffset },
|
||||
endOffsetGetter = { arrow?.endOffset },
|
||||
)
|
||||
|
||||
ktElement is KtDestructuringDeclaration -> ktElement.isMultiline(
|
||||
startOffsetGetter = { lPar?.startOffset },
|
||||
endOffsetGetter = { rPar?.endOffset },
|
||||
)
|
||||
|
||||
else -> ktElement.isMultiline()
|
||||
}
|
||||
|
||||
private fun <T : PsiElement> T.isMultiline(
|
||||
startOffsetGetter: T.() -> Int?,
|
||||
endOffsetGetter: T.() -> Int?,
|
||||
): Boolean {
|
||||
val startOffset = startOffsetGetter() ?: startOffset
|
||||
val endOffset = endOffsetGetter() ?: endOffset
|
||||
return containsLineBreakInThis(startOffset, endOffset)
|
||||
}
|
||||
@@ -11,20 +11,26 @@ import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettings
|
||||
import com.intellij.psi.tree.IElementType
|
||||
import com.intellij.psi.tree.TokenSet
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import com.intellij.psi.util.PsiUtilCore
|
||||
import org.jetbrains.kotlin.KtNodeTypes
|
||||
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
|
||||
import org.jetbrains.kotlin.idea.formatter.kotlinCustomSettings
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtFunctionLiteral
|
||||
import org.jetbrains.kotlin.psi.KtWhenEntry
|
||||
import org.jetbrains.kotlin.psi.KtWhenExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.nextLeaf
|
||||
import org.jetbrains.kotlin.psi.psiUtil.prevLeaf
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
|
||||
@@ -56,6 +62,20 @@ fun PsiElement.getLineCount(): Int {
|
||||
|
||||
fun PsiElement.isMultiline() = getLineCount() > 1
|
||||
|
||||
fun PsiElement?.isLineBreak() = this is PsiWhiteSpace && StringUtil.containsLineBreak(text)
|
||||
|
||||
fun PsiElement.leafIgnoringWhitespace(forward: Boolean = true, skipEmptyElements: Boolean = true) =
|
||||
leaf(forward) { (!skipEmptyElements || it.textLength != 0) && it !is PsiWhiteSpace }
|
||||
|
||||
fun PsiElement.leafIgnoringWhitespaceAndComments(forward: Boolean = true, skipEmptyElements: Boolean = true) =
|
||||
leaf(forward) { (!skipEmptyElements || it.textLength != 0) && it !is PsiWhiteSpace && it !is PsiComment }
|
||||
|
||||
fun PsiElement.leaf(forward: Boolean = true, filter: (PsiElement) -> Boolean): PsiElement? =
|
||||
if (forward) nextLeaf(filter)
|
||||
else prevLeaf(filter)
|
||||
|
||||
val PsiElement.isComma: Boolean get() = PsiUtil.getElementType(this) == KtTokens.COMMA
|
||||
|
||||
fun KtFunctionLiteral.needTrailingComma(settings: CodeStyleSettings?, checkExistingTrailingComma: Boolean = true): Boolean =
|
||||
needTrailingComma(
|
||||
settings = settings,
|
||||
@@ -79,7 +99,7 @@ fun KtDestructuringDeclaration.needTrailingComma(settings: CodeStyleSettings?, c
|
||||
globalEndOffset = { rPar?.endOffset },
|
||||
)
|
||||
|
||||
fun <T : PsiElement> T.needTrailingComma(
|
||||
private fun <T : PsiElement> T.needTrailingComma(
|
||||
settings: CodeStyleSettings?,
|
||||
trailingComma: T.() -> PsiElement?,
|
||||
additionalCheck: () -> Boolean = { true },
|
||||
@@ -136,9 +156,12 @@ private fun elementType(userDataHolder: UserDataHolder): IElementType? = when (u
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun PsiElement.canAddTrailingComma(): Boolean = canAddTrailingComma(this)
|
||||
fun ASTNode.canAddTrailingComma(): Boolean = psi?.canAddTrailingComma() == true
|
||||
|
||||
fun ASTNode.canAddTrailingComma(): Boolean = canAddTrailingComma(this)
|
||||
|
||||
private fun canAddTrailingComma(userDataHolder: UserDataHolder): Boolean = elementType(userDataHolder) in TYPES_WITH_TRAILING_COMMA
|
||||
fun PsiElement.canAddTrailingComma(): Boolean =
|
||||
if (this is KtWhenEntry && (isElse || parent.cast<KtWhenExpression>().leftParenthesis == null))
|
||||
false
|
||||
else
|
||||
canAddTrailingComma(this)
|
||||
|
||||
private fun canAddTrailingComma(userDataHolder: UserDataHolder): Boolean = elementType(userDataHolder) in TYPES_WITH_TRAILING_COMMA
|
||||
Reference in New Issue
Block a user