Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split TrailingCommaRule #1555

Merged
merged 8 commits into from Aug 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 0 additions & 3 deletions .editorconfig
Expand Up @@ -15,9 +15,6 @@ indent_size = 4

[*.{kt,kts}]
ij_kotlin_imports_layout=*
# Ideally, no experimental rule should be disabled. Ktlint should follow the dogfooding principle. This means that an
# experimental rule should only be added to the master branch no sooner than that this rule has been applied on the
# ktlint code base itself.
paul-dingemans marked this conversation as resolved.
Show resolved Hide resolved
paul-dingemans marked this conversation as resolved.
Show resolved Hide resolved
ij_kotlin_allow_trailing_comma=false
ij_kotlin_allow_trailing_comma_on_call_site=false

Expand Down
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -67,7 +67,8 @@ It's also [easy to create your own](#creating-a-reporter).
- `package-name`: No underscores in package names
- `parameter-list-wrapping`: When class/function signature doesn't fit on a single line, each parameter must be on a separate line
- `string-template`: Consistent string templates (`$v` instead of `${v}`, `${p.v}` instead of `${p.v.toString()}`)
- `trailing-comma`: Consistent removal (default) or adding of trailing comma's (both on call and declaration site)
- `trailing-comma-on-declaration-site`: Consistent removal (default) or adding of trailing comma's (on declaration site)
- `trailing-comma-on-call-site`: Consistent removal (default) or adding of trailing comma's (on call site)
chao2zhang marked this conversation as resolved.
Show resolved Hide resolved

### Spacing
- `annotation-spacing`: Annotations should be separated by a single line break
Expand Down
Expand Up @@ -2,7 +2,6 @@ package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.RuleSetProvider
import com.pinterest.ktlint.ruleset.experimental.trailingcomma.TrailingCommaRule

public class StandardRuleSetProvider : RuleSetProvider {

Expand Down Expand Up @@ -51,7 +50,8 @@ public class StandardRuleSetProvider : RuleSetProvider {
SpacingBetweenDeclarationsWithAnnotationsRule(),
SpacingBetweenDeclarationsWithCommentsRule(),
StringTemplateRule(),
TrailingCommaRule(),
TrailingCommaOnDeclarationSiteRule(),
TrailingCommaOnCallSiteRule(),
chao2zhang marked this conversation as resolved.
Show resolved Hide resolved
WrappingRule()
)
}
@@ -0,0 +1,160 @@
package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.api.EditorConfigProperties
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
import com.pinterest.ktlint.core.ast.ElementType
import com.pinterest.ktlint.core.ast.children
import com.pinterest.ktlint.ruleset.standard.internal.trailingcomma.reportAndCorrectTrailingCommaNodeBefore
import kotlin.properties.Delegates
import org.ec4j.core.model.PropertyType
import org.ec4j.core.model.PropertyType.PropertyValueParser
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet

/**
* Linting trailing comma for call site.
*
* @see [Kotlin Style Guide](https://kotlinlang.org/docs/coding-conventions.html#trailing-commas)
*/
public class TrailingCommaOnCallSiteRule :
Rule(
id = "trailing-comma-on-call-site",
visitorModifiers = setOf(
VisitorModifier.RunAfterRule(
ruleId = "standard:indent",
loadOnlyWhenOtherRuleIsLoaded = true,
runOnlyWhenOtherRuleIsEnabled = true
),
VisitorModifier.RunAsLateAsPossible
)
),
UsesEditorConfigProperties {

override val editorConfigProperties: List<UsesEditorConfigProperties.EditorConfigProperty<*>> = listOf(
allowTrailingCommaOnCallSiteProperty
)

private var allowTrailingCommaOnCallSite by Delegates.notNull<Boolean>()

private fun ASTNode.isTrailingCommaAllowed() =
elementType in TYPES_ON_CALL_SITE && allowTrailingCommaOnCallSite

override fun beforeFirstNode(editorConfigProperties: EditorConfigProperties) {
allowTrailingCommaOnCallSite = editorConfigProperties.getEditorConfigValue(allowTrailingCommaOnCallSiteProperty)
}

override fun beforeVisitChildNodes(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
// Keep processing of element types in sync with Intellij Kotlin formatting settings.
// https://github.com/JetBrains/intellij-kotlin/blob/master/formatter/src/org/jetbrains/kotlin/idea/formatter/trailingComma/util.kt
when (node.elementType) {
ElementType.COLLECTION_LITERAL_EXPRESSION -> visitCollectionLiteralExpression(node, autoCorrect, emit)
ElementType.INDICES -> visitIndices(node, autoCorrect, emit)
ElementType.TYPE_ARGUMENT_LIST -> visitTypeList(node, autoCorrect, emit)
ElementType.VALUE_ARGUMENT_LIST -> visitValueList(node, autoCorrect, emit)
else -> Unit
}
}

private fun visitCollectionLiteralExpression(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
val inspectNode = node
.children()
.last { it.elementType == ElementType.RBRACKET }
node.reportAndCorrectTrailingCommaNodeBefore(
inspectNode = inspectNode,
emit = emit,
isTrailingCommaAllowed = node.isTrailingCommaAllowed(),
autoCorrect = autoCorrect
)
}

private fun visitIndices(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
val inspectNode = node
.children()
.last { it.elementType == ElementType.RBRACKET }
node.reportAndCorrectTrailingCommaNodeBefore(
inspectNode = inspectNode,
emit = emit,
isTrailingCommaAllowed = node.isTrailingCommaAllowed(),
autoCorrect = autoCorrect
)
}

private fun visitValueList(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.treeParent.elementType != ElementType.FUNCTION_LITERAL) {
node
.children()
.lastOrNull { it.elementType == ElementType.RPAR }
?.let { inspectNode ->
node.reportAndCorrectTrailingCommaNodeBefore(
inspectNode = inspectNode,
emit = emit,
isTrailingCommaAllowed = node.isTrailingCommaAllowed(),
autoCorrect = autoCorrect
)
}
}
}

private fun visitTypeList(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
val inspectNode = node
.children()
.first { it.elementType == ElementType.GT }
node.reportAndCorrectTrailingCommaNodeBefore(
inspectNode = inspectNode,
emit = emit,
isTrailingCommaAllowed = node.isTrailingCommaAllowed(),
autoCorrect = autoCorrect
)
}

public companion object {

chao2zhang marked this conversation as resolved.
Show resolved Hide resolved
internal const val ALLOW_TRAILING_COMMA_ON_CALL_SITE_NAME = "ij_kotlin_allow_trailing_comma_on_call_site"
private const val ALLOW_TRAILING_COMMA_ON_CALL_SITE_DESCRIPTION =
"Defines whether a trailing comma (or no trailing comma)" +
"should be enforced on the calling side," +
"e.g. argument-list, when-entries, lambda-arguments, indices, etc."
private val BOOLEAN_VALUES_SET = setOf("true", "false")

// TODO: Rename property to trailingCommaOnCallSite. The word 'allow' is misleading as the comma is
// enforced when the property is enabled and prohibited when disabled.
public val allowTrailingCommaOnCallSiteProperty: UsesEditorConfigProperties.EditorConfigProperty<Boolean> =
UsesEditorConfigProperties.EditorConfigProperty(
type = PropertyType.LowerCasingPropertyType(
ALLOW_TRAILING_COMMA_ON_CALL_SITE_NAME,
ALLOW_TRAILING_COMMA_ON_CALL_SITE_DESCRIPTION,
PropertyValueParser.BOOLEAN_VALUE_PARSER,
BOOLEAN_VALUES_SET
),
defaultValue = false
)

private val TYPES_ON_CALL_SITE = TokenSet.create(
ElementType.COLLECTION_LITERAL_EXPRESSION,
ElementType.INDICES,
ElementType.TYPE_ARGUMENT_LIST,
ElementType.VALUE_ARGUMENT_LIST
)
}
}
@@ -0,0 +1,191 @@
package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.api.EditorConfigProperties
import com.pinterest.ktlint.core.api.UsesEditorConfigProperties
import com.pinterest.ktlint.core.ast.ElementType
import com.pinterest.ktlint.core.ast.children
import com.pinterest.ktlint.ruleset.standard.internal.trailingcomma.reportAndCorrectTrailingCommaNodeBefore
import kotlin.properties.Delegates
import org.ec4j.core.model.PropertyType
import org.ec4j.core.model.PropertyType.PropertyValueParser
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.psi.KtWhenEntry
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.utils.addToStdlib.cast

/**
* Linting trailing comma for declaration site.
*
* @see [Kotlin Style Guide](https://kotlinlang.org/docs/coding-conventions.html#trailing-commas)
*/
public class TrailingCommaOnDeclarationSiteRule :
Rule(
id = "trailing-comma-on-declaration-site",
visitorModifiers = setOf(
VisitorModifier.RunAfterRule(
ruleId = "standard:indent",
loadOnlyWhenOtherRuleIsLoaded = true,
runOnlyWhenOtherRuleIsEnabled = true
),
VisitorModifier.RunAsLateAsPossible
)
),
UsesEditorConfigProperties {

override val editorConfigProperties: List<UsesEditorConfigProperties.EditorConfigProperty<*>> = listOf(
allowTrailingCommaProperty
)

private var allowTrailingComma by Delegates.notNull<Boolean>()

private fun ASTNode.isTrailingCommaAllowed() =
elementType in TYPES_ON_DECLARATION_SITE && allowTrailingComma

override fun beforeFirstNode(editorConfigProperties: EditorConfigProperties) {
allowTrailingComma = editorConfigProperties.getEditorConfigValue(allowTrailingCommaProperty)
}

override fun beforeVisitChildNodes(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
// Keep processing of element types in sync with Intellij Kotlin formatting settings.
// https://github.com/JetBrains/intellij-kotlin/blob/master/formatter/src/org/jetbrains/kotlin/idea/formatter/trailingComma/util.kt
when (node.elementType) {
ElementType.DESTRUCTURING_DECLARATION -> visitDestructuringDeclaration(node, autoCorrect, emit)
ElementType.FUNCTION_LITERAL -> visitFunctionLiteral(node, autoCorrect, emit)
ElementType.TYPE_PARAMETER_LIST -> visitTypeList(node, autoCorrect, emit)
ElementType.VALUE_PARAMETER_LIST -> visitValueList(node, autoCorrect, emit)
ElementType.WHEN_ENTRY -> visitWhenEntry(node, autoCorrect, emit)
else -> Unit
}
}

private fun visitDestructuringDeclaration(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
val inspectNode = node
.children()
.last { it.elementType == ElementType.RPAR }
node.reportAndCorrectTrailingCommaNodeBefore(
inspectNode = inspectNode,
isTrailingCommaAllowed = node.isTrailingCommaAllowed(),
autoCorrect = autoCorrect,
emit = emit
)
}

private fun visitFunctionLiteral(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
val inspectNode = node
.children()
.lastOrNull { it.elementType == ElementType.ARROW }
?: // lambda w/o an arrow -> no arguments -> no commas
return
node.reportAndCorrectTrailingCommaNodeBefore(
inspectNode = inspectNode,
isTrailingCommaAllowed = node.isTrailingCommaAllowed(),
autoCorrect = autoCorrect,
emit = emit
)
}

private fun visitValueList(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.treeParent.elementType != ElementType.FUNCTION_LITERAL) {
node
.children()
.lastOrNull { it.elementType == ElementType.RPAR }
?.let { inspectNode ->
node.reportAndCorrectTrailingCommaNodeBefore(
inspectNode = inspectNode,
isTrailingCommaAllowed = node.isTrailingCommaAllowed(),
autoCorrect = autoCorrect,
emit = emit
)
}
}
}

private fun visitTypeList(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
val inspectNode = node
.children()
.first { it.elementType == ElementType.GT }
node.reportAndCorrectTrailingCommaNodeBefore(
inspectNode = inspectNode,
isTrailingCommaAllowed = node.isTrailingCommaAllowed(),
autoCorrect = autoCorrect,
emit = emit
)
}

private fun visitWhenEntry(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
val psi = node.psi
require(psi is KtWhenEntry)
if (psi.isElse || psi.parent.cast<KtWhenExpression>().leftParenthesis == null) {
// no commas for "else" or when there are no opening parenthesis for the when-expression
return
}

val inspectNode = node
.children()
.first { it.elementType == ElementType.ARROW }
node.reportAndCorrectTrailingCommaNodeBefore(
inspectNode = inspectNode,
isTrailingCommaAllowed = node.isTrailingCommaAllowed(),
autoCorrect = autoCorrect,
emit = emit
)
}

public companion object {

chao2zhang marked this conversation as resolved.
Show resolved Hide resolved
internal const val ALLOW_TRAILING_COMMA_NAME = "ij_kotlin_allow_trailing_comma"
private const val ALLOW_TRAILING_COMMA_DESCRIPTION = "Defines whether a trailing comma (or no trailing comma)" +
"should be enforced on the defining side," +
"e.g. parameter-list, type-argument-list, lambda-value-parameters, enum-entries, etc."

private val BOOLEAN_VALUES_SET = setOf("true", "false")

// TODO: Rename property to trailingCommaOnDeclarationSite. The word 'allow' is misleading as the comma is
// enforced when the property is enabled and prohibited when disabled.
paul-dingemans marked this conversation as resolved.
Show resolved Hide resolved
public val allowTrailingCommaProperty: UsesEditorConfigProperties.EditorConfigProperty<Boolean> =
UsesEditorConfigProperties.EditorConfigProperty(
type = PropertyType.LowerCasingPropertyType(
ALLOW_TRAILING_COMMA_NAME,
ALLOW_TRAILING_COMMA_DESCRIPTION,
PropertyValueParser.BOOLEAN_VALUE_PARSER,
BOOLEAN_VALUES_SET
),
defaultValue = false
)

private val TYPES_ON_DECLARATION_SITE = TokenSet.create(
ElementType.DESTRUCTURING_DECLARATION,
ElementType.FUNCTION_LITERAL,
ElementType.FUNCTION_TYPE,
ElementType.TYPE_PARAMETER_LIST,
ElementType.VALUE_PARAMETER_LIST,
ElementType.WHEN_ENTRY
)
}
}