From d64733ba1711c592fb78fe04d3472c5294004450 Mon Sep 17 00:00:00 2001 From: Chao Zhang Date: Sun, 7 Aug 2022 02:13:05 -0700 Subject: [PATCH] Split TrailingCommaRule (#1555) Split rule `trailing-comma` into `trailing-comma-on-call-site` and `trailing-comma-on-declaration-site` Co-authored-by: paul-dingemans --- CHANGELOG.md | 1 + docs/faq.md | 2 +- docs/rules/configuration.md | 19 +- docs/rules/standard.md | 14 +- .../ruleset/standard/MaxLineLengthRule.kt | 10 +- .../standard/StandardRuleSetProvider.kt | 4 +- .../standard/TrailingCommaOnCallSiteRule.kt | 316 ++++++++++ ... => TrailingCommaOnDeclarationSiteRule.kt} | 281 ++++----- .../TrailingCommaOnCallSiteRuleTest.kt | 473 ++++++++++++++ ...TrailingCommaOnDeclarationSiteRuleTest.kt} | 584 ++---------------- 10 files changed, 1005 insertions(+), 699 deletions(-) create mode 100644 ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnCallSiteRule.kt rename ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/{TrailingCommaRule.kt => TrailingCommaOnDeclarationSiteRule.kt} (67%) create mode 100644 ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnCallSiteRuleTest.kt rename ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/{TrailingCommaRuleTest.kt => TrailingCommaOnDeclarationSiteRuleTest.kt} (58%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85ee8a19a7..b45aa15d6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -131,6 +131,7 @@ The callback function provided as parameter to the format function is now called * Fix multiline if-statements `multiline-if-else` ([#828](https://github.com/pinterest/ktlint/issues/828)) * Prevent class cast exception on ".editorconfig" property `ktlint_code_style` ([#1559](https://github.com/pinterest/ktlint/issues/1559)) * Handle trailing comma in enums `trailing-comma` ([#1542](https://github.com/pinterest/ktlint/pull/1542)) +* Split rule `trailing-comma` into `trailing-comma-on-call-site` and `trailing-comma-on-declaration-site` ([#1555](https://github.com/pinterest/ktlint/pull/1555)) ### Changed diff --git a/docs/faq.md b/docs/faq.md index 0297244e3e..e1fe1fb731 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -91,7 +91,7 @@ import package.b.* An error for a specific rule on a specific line can be disabled with a `@Suppress` annotation: ```kotlin -@Suppress("ktlint:max-line-length","ktlint:experimental:trailing-comma") +@Suppress("ktlint:max-line-length","ktlint:experimental:trailing-comma-on-call-site") val foo = listOf( "some really looooooooooooooooong string exceeding the max line length", ) diff --git a/docs/rules/configuration.md b/docs/rules/configuration.md index 0c4c356ad6..ef8b6f0de6 100644 --- a/docs/rules/configuration.md +++ b/docs/rules/configuration.md @@ -176,18 +176,29 @@ max_line_length = -1 # Use "off" (or -1) to ignore max line length or a positive This setting is used by multiple rules of which rule `max-line-length` is the most important. -## Trailing comma +## Trailing comma on call site -Trailing comma's (both on call and declaration site) are disabled (e.g. not allowed) by. When enabling the properties, the trailing becomes mandatory where applicable. +By default, trailing comma's on call site are not allowed. When enabling the property, the trailing comma becomes mandatory where applicable. Example: ```ini [*.{kt,kts}] -ij_kotlin_allow_trailing_comma = false ij_kotlin_allow_trailing_comma_on_call_site = false ``` -This setting only takes effect when rule `trailing-comma` is enabled. +This setting only takes effect when rule `trailing-comma-on-call-site` is enabled. + +## Trailing comma on declaration site + +By default, trailing comma's on declaration site are not allowed. When enabling the property, the trailing comma becomes mandatory where applicable. + +Example: +```ini +[*.{kt,kts}] +ij_kotlin_allow_trailing_comma = false # Only used for declaration site +``` + +This setting only takes effect when rule `trailing-comma-on-declaration-site` is enabled. ## Overriding Editorconfig properties for specific directories diff --git a/docs/rules/standard.md b/docs/rules/standard.md index e751c02ffc..bf99d48a5b 100644 --- a/docs/rules/standard.md +++ b/docs/rules/standard.md @@ -106,7 +106,7 @@ When a line is broken at an assignment (`=`) operator the break comes after the Rule id: `no-line-break-before-assignment` -## Nu multi spaces +## No multi spaces Except in indentation and in KDoc's it is not allowed to have multiple consecutive spaces. @@ -166,11 +166,17 @@ Consistent string templates (`$v` instead of `${v}`, `${p.v}` instead of `${p.v. Rule id: `string-template` -## Trailing comma +## Trailing comma on call site -Consistent removal (default) or adding of trailing comma's (both on call and declaration site). +Consistent removal (default) or adding of trailing comma's on call site. -Rule id: `trailing-comma` +Rule id: `trailing-comma-on-call-site` + +## Trailing comma on declaration site + +Consistent removal (default) or adding of trailing comma's on declaration site. + +Rule id: `trailing-comma-on-declaration-site` ## Spacing diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/MaxLineLengthRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/MaxLineLengthRule.kt index 0ecdb55be5..2ff04d974c 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/MaxLineLengthRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/MaxLineLengthRule.kt @@ -28,7 +28,15 @@ class MaxLineLengthRule : // This rule should run after all other rules. Each time a rule visitor is modified with // RunAsLateAsPossible, it needs to be checked that this rule still runs after that new rule or that it // won't be affected by that rule. - ruleId = "trailing-comma", + ruleId = "trailing-comma-on-call-site", + loadOnlyWhenOtherRuleIsLoaded = false, + runOnlyWhenOtherRuleIsEnabled = false + ), + VisitorModifier.RunAfterRule( + // This rule should run after all other rules. Each time a rule visitor is modified with + // RunAsLateAsPossible, it needs to be checked that this rule still runs after that new rule or that it + // won't be affected by that rule. + ruleId = "trailing-comma-on-declaration-site", loadOnlyWhenOtherRuleIsLoaded = false, runOnlyWhenOtherRuleIsEnabled = false ), diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRuleSetProvider.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRuleSetProvider.kt index c652fe34f9..562383b960 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRuleSetProvider.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/StandardRuleSetProvider.kt @@ -2,7 +2,6 @@ package com.pinterest.ktlint.ruleset.standard import com.pinterest.ktlint.core.RuleProvider import com.pinterest.ktlint.core.RuleSetProviderV2 -import com.pinterest.ktlint.ruleset.experimental.trailingcomma.TrailingCommaRule public class StandardRuleSetProvider : RuleSetProviderV2( @@ -59,7 +58,8 @@ public class StandardRuleSetProvider : RuleProvider { SpacingBetweenDeclarationsWithAnnotationsRule() }, RuleProvider { SpacingBetweenDeclarationsWithCommentsRule() }, RuleProvider { StringTemplateRule() }, - RuleProvider { TrailingCommaRule() }, + RuleProvider { TrailingCommaOnCallSiteRule() }, + RuleProvider { TrailingCommaOnDeclarationSiteRule() }, RuleProvider { WrappingRule() } ) } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnCallSiteRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnCallSiteRule.kt new file mode 100644 index 0000000000..e9a2a779be --- /dev/null +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnCallSiteRule.kt @@ -0,0 +1,316 @@ +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.core.ast.containsLineBreakInRange +import com.pinterest.ktlint.core.ast.prevCodeLeaf +import com.pinterest.ktlint.core.ast.prevLeaf +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.PsiElement +import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace +import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet +import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression +import org.jetbrains.kotlin.psi.KtDestructuringDeclaration +import org.jetbrains.kotlin.psi.KtFunctionLiteral +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtValueArgumentList +import org.jetbrains.kotlin.psi.KtWhenEntry +import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType +import org.jetbrains.kotlin.psi.psiUtil.nextLeaf +import org.jetbrains.kotlin.psi.psiUtil.prevLeaf + +/** + * 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> = listOf( + allowTrailingCommaOnCallSiteProperty + ) + + private var allowTrailingCommaOnCallSite by Delegates.notNull() + + 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 + ) + } + + private fun ASTNode.reportAndCorrectTrailingCommaNodeBefore( + inspectNode: ASTNode, + isTrailingCommaAllowed: Boolean, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit + ) { + val prevLeaf = inspectNode.prevLeaf() + val trailingCommaNode = prevLeaf.findPreviousTrailingCommaNodeOrNull() + val trailingCommaState = when { + isMultiline(psi) -> if (trailingCommaNode != null) TrailingCommaState.EXISTS else TrailingCommaState.MISSING + else -> if (trailingCommaNode != null) TrailingCommaState.REDUNDANT else TrailingCommaState.NOT_EXISTS + } + when (trailingCommaState) { + TrailingCommaState.EXISTS -> if (!isTrailingCommaAllowed) { + emit( + trailingCommaNode!!.startOffset, + "Unnecessary trailing comma before \"${inspectNode.text}\"", + true + ) + if (autoCorrect) { + this.removeChild(trailingCommaNode) + } + } + TrailingCommaState.MISSING -> if (isTrailingCommaAllowed) { + val addNewLineBeforeArrowInWhenEntry = addNewLineBeforeArrowInWhen() + val prevNode = inspectNode.prevCodeLeaf()!! + if (addNewLineBeforeArrowInWhenEntry) { + emit( + prevNode.startOffset + prevNode.textLength, + "Missing trailing comma and newline before \"${inspectNode.text}\"", + true + ) + } else { + emit( + prevNode.startOffset + prevNode.textLength, + "Missing trailing comma before \"${inspectNode.text}\"", + true + ) + } + if (autoCorrect) { + if (addNewLineBeforeArrowInWhenEntry) { + val parentIndent = (prevNode.psi.parent.prevLeaf() as? PsiWhiteSpace)?.text ?: "\n" + val leafBeforeArrow = (psi as KtWhenEntry).arrow?.prevLeaf() + if (leafBeforeArrow != null && leafBeforeArrow is PsiWhiteSpace) { + val newLine = KtPsiFactory(prevNode.psi).createWhiteSpace(parentIndent) + leafBeforeArrow.replace(newLine) + } else { + val newLine = KtPsiFactory(prevNode.psi).createWhiteSpace(parentIndent) + prevNode.psi.parent.addAfter(newLine, prevNode.psi) + } + } + val comma = KtPsiFactory(prevNode.psi).createComma() + prevNode.psi.parent.addAfter(comma, prevNode.psi) + } + } + TrailingCommaState.REDUNDANT -> { + emit( + trailingCommaNode!!.startOffset, + "Unnecessary trailing comma before \"${inspectNode.text}\"", + true + ) + if (autoCorrect) { + this.removeChild(trailingCommaNode) + } + } + TrailingCommaState.NOT_EXISTS -> Unit + } + } + + private fun isMultiline(element: PsiElement): Boolean = when { + element.parent is KtFunctionLiteral -> isMultiline(element.parent) + element is KtFunctionLiteral -> containsLineBreakInRange(element.valueParameterList!!, element.arrow!!) + element is KtWhenEntry -> containsLineBreakInRange(element.firstChild, element.arrow!!) + element is KtDestructuringDeclaration -> containsLineBreakInRange(element.lPar!!, element.rPar!!) + element is KtValueArgumentList && element.children.size == 1 && element.anyDescendantOfType() -> { + // special handling for collection literal + // @Annotation([ + // "something", + // ]) + val lastChild = element.collectDescendantsOfType().last() + containsLineBreakInLeavesRange(lastChild.rightBracket!!, element.rightParenthesis!!) + } + else -> element.textContains('\n') + } + + private fun ASTNode.addNewLineBeforeArrowInWhen() = + if (psi is KtWhenEntry) { + val leafBeforeArrow = (psi as KtWhenEntry).arrow?.prevLeaf() + !(leafBeforeArrow is PsiWhiteSpace && leafBeforeArrow.textContains('\n')) + } else { + false + } + + private fun ASTNode?.findPreviousTrailingCommaNodeOrNull(): ASTNode? { + var node = this + while (node?.isIgnorable() == true) { + node = node.prevLeaf() + } + return if (node?.elementType == ElementType.COMMA) { + node + } else { + null + } + } + + private fun containsLineBreakInLeavesRange(from: PsiElement, to: PsiElement): Boolean { + var leaf: PsiElement? = from + while (leaf != null && !leaf.isEquivalentTo(to)) { + if (leaf.textContains('\n')) { + return true + } + leaf = leaf.nextLeaf(skipEmptyElements = false) + } + return leaf?.textContains('\n') ?: false + } + + private fun ASTNode.isIgnorable(): Boolean = + elementType == ElementType.WHITE_SPACE || + elementType == ElementType.EOL_COMMENT || + elementType == ElementType.BLOCK_COMMENT + + private enum class TrailingCommaState { + /** + * The trailing comma is needed and exists + */ + EXISTS, + + /** + * The trailing comma is needed and doesn't exist + */ + MISSING, + + /** + * The trailing comma isn't needed and doesn't exist + */ + NOT_EXISTS, + + /** + * The trailing comma isn't needed, but exists + */ + REDUNDANT + ; + } + + public companion object { + 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 = + 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 + ) + } +} diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnDeclarationSiteRule.kt similarity index 67% rename from ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaRule.kt rename to ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnDeclarationSiteRule.kt index 3bf276bf62..93b5f44e5f 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnDeclarationSiteRule.kt @@ -1,22 +1,9 @@ -package com.pinterest.ktlint.ruleset.experimental.trailingcomma +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.ElementType.CLASS -import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY -import com.pinterest.ktlint.core.ast.ElementType.COLLECTION_LITERAL_EXPRESSION -import com.pinterest.ktlint.core.ast.ElementType.DESTRUCTURING_DECLARATION -import com.pinterest.ktlint.core.ast.ElementType.ENUM_ENTRY -import com.pinterest.ktlint.core.ast.ElementType.FUNCTION_LITERAL -import com.pinterest.ktlint.core.ast.ElementType.INDICES -import com.pinterest.ktlint.core.ast.ElementType.SEMICOLON -import com.pinterest.ktlint.core.ast.ElementType.TYPE_ARGUMENT_LIST -import com.pinterest.ktlint.core.ast.ElementType.TYPE_PARAMETER_LIST -import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST -import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST -import com.pinterest.ktlint.core.ast.ElementType.WHEN_ENTRY import com.pinterest.ktlint.core.ast.children import com.pinterest.ktlint.core.ast.containsLineBreakInRange import com.pinterest.ktlint.core.ast.lineNumber @@ -44,32 +31,14 @@ import org.jetbrains.kotlin.psi.psiUtil.nextLeaf import org.jetbrains.kotlin.psi.psiUtil.prevLeaf import org.jetbrains.kotlin.utils.addToStdlib.cast -private 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 - ; -} - -public class TrailingCommaRule : +/** + * 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", + id = "trailing-comma-on-declaration-site", visitorModifiers = setOf( VisitorModifier.RunAfterRule( ruleId = "standard:indent", @@ -80,17 +49,18 @@ public class TrailingCommaRule : ) ), UsesEditorConfigProperties { + override val editorConfigProperties: List> = listOf( - allowTrailingCommaProperty, - allowTrailingCommaOnCallSiteProperty + allowTrailingCommaProperty ) private var allowTrailingComma by Delegates.notNull() - private var allowTrailingCommaOnCallSite by Delegates.notNull() + + private fun ASTNode.isTrailingCommaAllowed() = + elementType in TYPES_ON_DECLARATION_SITE && allowTrailingComma override fun beforeFirstNode(editorConfigProperties: EditorConfigProperties) { allowTrailingComma = editorConfigProperties.getEditorConfigValue(allowTrailingCommaProperty) - allowTrailingCommaOnCallSite = editorConfigProperties.getEditorConfigValue(allowTrailingCommaOnCallSiteProperty) } override fun beforeVisitChildNodes( @@ -101,96 +71,90 @@ public class TrailingCommaRule : // 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) { - CLASS -> visitClass(node, emit, autoCorrect) - COLLECTION_LITERAL_EXPRESSION -> visitCollectionLiteralExpression(node, emit, autoCorrect) - DESTRUCTURING_DECLARATION -> visitDestructuringDeclaration(node, emit, autoCorrect) - FUNCTION_LITERAL -> visitFunctionLiteral(node, emit, autoCorrect) - INDICES -> visitIndices(node, emit, autoCorrect) - TYPE_ARGUMENT_LIST -> visitTypeList(node, emit, autoCorrect) - TYPE_PARAMETER_LIST -> visitTypeList(node, emit, autoCorrect) - VALUE_ARGUMENT_LIST -> visitValueList(node, emit, autoCorrect) - VALUE_PARAMETER_LIST -> visitValueList(node, emit, autoCorrect) - WHEN_ENTRY -> visitWhenEntry(node, emit, autoCorrect) + ElementType.CLASS -> visitClass(node, emit, autoCorrect) + 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 visitCollectionLiteralExpression( - node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean - ) { - val inspectNode = node - .children() - .last { it.elementType == ElementType.RBRACKET } - node.reportAndCorrectTrailingCommaNodeBefore(inspectNode, emit, autoCorrect) - } - private fun visitDestructuringDeclaration( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { val inspectNode = node .children() .last { it.elementType == ElementType.RPAR } - node.reportAndCorrectTrailingCommaNodeBefore(inspectNode, emit, autoCorrect) + node.reportAndCorrectTrailingCommaNodeBefore( + inspectNode = inspectNode, + isTrailingCommaAllowed = node.isTrailingCommaAllowed(), + autoCorrect = autoCorrect, + emit = emit + ) } private fun visitFunctionLiteral( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean + 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, emit, autoCorrect) - } - - private fun visitIndices( - node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean - ) { - val inspectNode = node - .children() - .last { it.elementType == ElementType.RBRACKET } - node.reportAndCorrectTrailingCommaNodeBefore(inspectNode, emit, autoCorrect) + node.reportAndCorrectTrailingCommaNodeBefore( + inspectNode = inspectNode, + isTrailingCommaAllowed = node.isTrailingCommaAllowed(), + autoCorrect = autoCorrect, + emit = emit + ) } private fun visitValueList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { - if (node.treeParent.elementType != FUNCTION_LITERAL) { + if (node.treeParent.elementType != ElementType.FUNCTION_LITERAL) { node .children() .lastOrNull { it.elementType == ElementType.RPAR } - ?.let { - node.reportAndCorrectTrailingCommaNodeBefore(it, emit, autoCorrect) + ?.let { inspectNode -> + node.reportAndCorrectTrailingCommaNodeBefore( + inspectNode = inspectNode, + isTrailingCommaAllowed = node.isTrailingCommaAllowed(), + autoCorrect = autoCorrect, + emit = emit + ) } } } private fun visitTypeList( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { val inspectNode = node .children() .first { it.elementType == ElementType.GT } - node.reportAndCorrectTrailingCommaNodeBefore(inspectNode, emit, autoCorrect) + node.reportAndCorrectTrailingCommaNodeBefore( + inspectNode = inspectNode, + isTrailingCommaAllowed = node.isTrailingCommaAllowed(), + autoCorrect = autoCorrect, + emit = emit + ) } private fun visitWhenEntry( node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { val psi = node.psi require(psi is KtWhenEntry) @@ -202,9 +166,13 @@ public class TrailingCommaRule : val inspectNode = node .children() .first { it.elementType == ElementType.ARROW } - node.reportAndCorrectTrailingCommaNodeBefore(inspectNode, emit, autoCorrect) + node.reportAndCorrectTrailingCommaNodeBefore( + inspectNode = inspectNode, + isTrailingCommaAllowed = node.isTrailingCommaAllowed(), + autoCorrect = autoCorrect, + emit = emit + ) } - private fun visitClass( node: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, @@ -215,13 +183,18 @@ public class TrailingCommaRule : node .takeIf { psi.isEnum() } - ?.findChildByType(CLASS_BODY) + ?.findChildByType(ElementType.CLASS_BODY) ?.takeUnless { // Do nothing when last two entries are on same line as no trailing comma should be inserted it.lastTwoEnumEntriesAreOnSameLine() }?.let { classBody -> val nodeAfterTrailingCommaPosition = classBody.findNodeAfterTrailingCommaPosition() - node.reportAndCorrectTrailingCommaNodeBefore(nodeAfterTrailingCommaPosition, emit, autoCorrect) + node.reportAndCorrectTrailingCommaNodeBefore( + inspectNode = nodeAfterTrailingCommaPosition, + isTrailingCommaAllowed = node.isTrailingCommaAllowed(), + autoCorrect = autoCorrect, + emit = emit + ) } } @@ -236,7 +209,7 @@ public class TrailingCommaRule : val semicolonAfterLastEnumEntry = lastEnumEntry .children() - .singleOrNull { it.elementType == SEMICOLON } + .singleOrNull { it.elementType == ElementType.SEMICOLON } return semicolonAfterLastEnumEntry ?: lastChildNode } @@ -253,8 +226,9 @@ public class TrailingCommaRule : private fun ASTNode.reportAndCorrectTrailingCommaNodeBefore( inspectNode: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean + isTrailingCommaAllowed: Boolean, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { val prevLeaf = inspectNode.prevLeaf() val trailingCommaNode = prevLeaf.findPreviousTrailingCommaNodeOrNull() @@ -262,11 +236,6 @@ public class TrailingCommaRule : isMultiline(psi) -> if (trailingCommaNode != null) TrailingCommaState.EXISTS else TrailingCommaState.MISSING else -> if (trailingCommaNode != null) TrailingCommaState.REDUNDANT else TrailingCommaState.NOT_EXISTS } - val isTrailingCommaAllowed = when (elementType) { - in TYPES_ON_DECLARATION_SITE -> allowTrailingComma - in TYPES_ON_CALL_SITE -> allowTrailingCommaOnCallSite - else -> false - } when (trailingCommaState) { TrailingCommaState.EXISTS -> if (!isTrailingCommaAllowed) { emit( @@ -294,7 +263,6 @@ public class TrailingCommaRule : true ) } - if (autoCorrect) { if (addNewLineBeforeArrowInWhenEntry) { val parentIndent = (prevNode.psi.parent.prevLeaf() as? PsiWhiteSpace)?.text ?: "\n" @@ -308,7 +276,7 @@ public class TrailingCommaRule : } } - if (inspectNode.treeParent.elementType == ENUM_ENTRY) { + if (inspectNode.treeParent.elementType == ElementType.ENUM_ENTRY) { with(KtPsiFactory(prevNode.psi)) { val parentIndent = (prevNode.psi.parent.prevLeaf() as? PsiWhiteSpace)?.text ?: "\n" val newline = createWhiteSpace(parentIndent) @@ -339,6 +307,22 @@ public class TrailingCommaRule : } } + private fun isMultiline(element: PsiElement): Boolean = when { + element.parent is KtFunctionLiteral -> isMultiline(element.parent) + element is KtFunctionLiteral -> containsLineBreakInRange(element.valueParameterList!!, element.arrow!!) + element is KtWhenEntry -> containsLineBreakInRange(element.firstChild, element.arrow!!) + element is KtDestructuringDeclaration -> containsLineBreakInRange(element.lPar!!, element.rPar!!) + element is KtValueArgumentList && element.children.size == 1 && element.anyDescendantOfType() -> { + // special handling for collection literal + // @Annotation([ + // "something", + // ]) + val lastChild = element.collectDescendantsOfType().last() + containsLineBreakInLeavesRange(lastChild.rightBracket!!, element.rightParenthesis!!) + } + else -> element.textContains('\n') + } + private fun ASTNode.addNewLineBeforeArrowInWhen() = if (psi is KtWhenEntry) { val leafBeforeArrow = (psi as KtWhenEntry).arrow?.prevLeaf() @@ -359,23 +343,7 @@ public class TrailingCommaRule : } } - private fun isMultiline(element: PsiElement): Boolean = when { - element.parent is KtFunctionLiteral -> isMultiline(element.parent) - element is KtFunctionLiteral -> containsLineBreakInRange(element.valueParameterList!!, element.arrow!!) - element is KtWhenEntry -> containsLineBreakInRange(element.firstChild, element.arrow!!) - element is KtDestructuringDeclaration -> containsLineBreakInRange(element.lPar!!, element.rPar!!) - element is KtValueArgumentList && element.children.size == 1 && element.anyDescendantOfType() -> { - // special handling for collection literal - // @Annotation([ - // "something", - // ]) - val lastChild = element.collectDescendantsOfType().last() - containsLineBreakInLeafsRange(lastChild.rightBracket!!, element.rightParenthesis!!) - } - else -> element.textContains('\n') - } - - private fun containsLineBreakInLeafsRange(from: PsiElement, to: PsiElement): Boolean { + private fun containsLineBreakInLeavesRange(from: PsiElement, to: PsiElement): Boolean { var leaf: PsiElement? = from while (leaf != null && !leaf.isEquivalentTo(to)) { if (leaf.textContains('\n')) { @@ -391,24 +359,30 @@ public class TrailingCommaRule : elementType == ElementType.EOL_COMMENT || elementType == ElementType.BLOCK_COMMENT - public companion object { - - private val TYPES_ON_DECLARATION_SITE = TokenSet.create( - DESTRUCTURING_DECLARATION, - FUNCTION_LITERAL, - ElementType.FUNCTION_TYPE, - TYPE_PARAMETER_LIST, - VALUE_PARAMETER_LIST, - WHEN_ENTRY, - CLASS - ) + private enum class TrailingCommaState { + /** + * The trailing comma is needed and exists + */ + EXISTS, + + /** + * The trailing comma is needed and doesn't exist + */ + MISSING, + + /** + * The trailing comma isn't needed and doesn't exist + */ + NOT_EXISTS, + + /** + * The trailing comma isn't needed, but exists + */ + REDUNDANT + ; + } - private val TYPES_ON_CALL_SITE = TokenSet.create( - COLLECTION_LITERAL_EXPRESSION, - INDICES, - TYPE_ARGUMENT_LIST, - VALUE_ARGUMENT_LIST - ) + public companion object { 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)" + @@ -418,7 +392,7 @@ public class TrailingCommaRule : 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. + // enforced when the property is enabled and prohibited when disabled. public val allowTrailingCommaProperty: UsesEditorConfigProperties.EditorConfigProperty = UsesEditorConfigProperties.EditorConfigProperty( type = PropertyType.LowerCasingPropertyType( @@ -430,23 +404,14 @@ public class TrailingCommaRule : defaultValue = false ) - 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." - - // 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 = - 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_DECLARATION_SITE = TokenSet.create( + ElementType.CLASS, + ElementType.DESTRUCTURING_DECLARATION, + ElementType.FUNCTION_LITERAL, + ElementType.FUNCTION_TYPE, + ElementType.TYPE_PARAMETER_LIST, + ElementType.VALUE_PARAMETER_LIST, + ElementType.WHEN_ENTRY + ) } } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnCallSiteRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnCallSiteRuleTest.kt new file mode 100644 index 0000000000..909779444d --- /dev/null +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnCallSiteRuleTest.kt @@ -0,0 +1,473 @@ +package com.pinterest.ktlint.ruleset.standard + +import com.pinterest.ktlint.core.RuleProvider +import com.pinterest.ktlint.ruleset.standard.TrailingCommaOnCallSiteRule.Companion.allowTrailingCommaOnCallSiteProperty +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.test.LintViolation +import org.junit.jupiter.api.Test + +class TrailingCommaOnCallSiteRuleTest { + private val ruleAssertThat = + assertThatRule( + provider = { TrailingCommaOnCallSiteRule() }, + additionalRuleProviders = setOf( + // Apply the IndentationRule always as additional rule, so that the formattedCode in the unit test looks + // correct. + RuleProvider { IndentationRule() } + ) + ) + + @Test + fun `Given property allow trailing comma on call site is not set then remove trailing comma's`() { + val code = + """ + val foo1 = listOf("a", "b",) + + val foo2 = Pair(1, 2,) + + val foo3: List = emptyList() + + val foo4 = Array(2) { 42 } + val bar4 = foo4[1,] + + annotation class Foo5(val params: IntArray) + @Foo5([1, 2,]) + val foo5: Int = 0 + """.trimIndent() + val formattedCode = + """ + val foo1 = listOf("a", "b") + + val foo2 = Pair(1, 2) + + val foo3: List = emptyList() + + val foo4 = Array(2) { 42 } + val bar4 = foo4[1] + + annotation class Foo5(val params: IntArray) + @Foo5([1, 2]) + val foo5: Int = 0 + """.trimIndent() + ruleAssertThat(code) + .hasLintViolations( + LintViolation(1, 27, "Unnecessary trailing comma before \")\""), + LintViolation(3, 21, "Unnecessary trailing comma before \")\""), + LintViolation(5, 22, "Unnecessary trailing comma before \">\""), + LintViolation(8, 18, "Unnecessary trailing comma before \"]\""), + LintViolation(11, 12, "Unnecessary trailing comma before \"]\"") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given that the trailing comma is not allowed on call site then remove it from an argument list when present`() { + val code = + """ + val list1 = listOf("a", "b",) + val list2 = listOf( + "a", + "b", // The comma before the comment should be removed without removing the comment itself + ) + val list3 = listOf( + "a", + "b", /* The comma before the comment should be removed without removing the comment itself */ + ) + """.trimIndent() + val formattedCode = + """ + val list1 = listOf("a", "b") + val list2 = listOf( + "a", + "b" // The comma before the comment should be removed without removing the comment itself + ) + val list3 = listOf( + "a", + "b" /* The comma before the comment should be removed without removing the comment itself */ + ) + """.trimIndent() + ruleAssertThat(code) + .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to false) + .hasLintViolations( + LintViolation(1, 28, "Unnecessary trailing comma before \")\""), + LintViolation(4, 8, "Unnecessary trailing comma before \")\""), + LintViolation(8, 8, "Unnecessary trailing comma before \")\"") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given that the trailing comma is required on call site then add it to the argument list when missing`() { + val code = + """ + val list1 = listOf("a", "b") + val list2 = listOf( + "a", + "b" // The comma should be inserted before the comment + ) + val list3 = listOf( + "a", + "b" /* The comma should be inserted before the comment */ + ) + """.trimIndent() + val formattedCode = + """ + val list1 = listOf("a", "b") + val list2 = listOf( + "a", + "b", // The comma should be inserted before the comment + ) + val list3 = listOf( + "a", + "b", /* The comma should be inserted before the comment */ + ) + """.trimIndent() + ruleAssertThat(code) + .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) + .hasLintViolations( + LintViolation(4, 8, "Missing trailing comma before \")\""), + LintViolation(8, 8, "Missing trailing comma before \")\"") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given that the trailing comma is not allowed on call site then remove it from the type argument list when present`() { + val code = + """ + val list1: List = emptyList() + val list2: List< + String, // The comma before the comment should be removed without removing the comment itself + > = emptyList() + val list3: List< + String, /* The comma before the comment should be removed without removing the comment itself */ + > = emptyList() + """.trimIndent() + val formattedCode = + """ + val list1: List = emptyList() + val list2: List< + String // The comma before the comment should be removed without removing the comment itself + > = emptyList() + val list3: List< + String /* The comma before the comment should be removed without removing the comment itself */ + > = emptyList() + """.trimIndent() + ruleAssertThat(code) + .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to false) + .hasLintViolations( + LintViolation(1, 23, "Unnecessary trailing comma before \">\""), + LintViolation(3, 11, "Unnecessary trailing comma before \">\""), + LintViolation(6, 11, "Unnecessary trailing comma before \">\"") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given that the trailing comma is required on call site then add it to the type argument list when missing`() { + val code = + """ + val list1: List = emptyList() + val list2: List< + String // The comma should be inserted before the comment + > = emptyList() + val list3: List< + String /* The comma should be inserted before the comment */ + > = emptyList() + """.trimIndent() + val formattedCode = + """ + val list1: List = emptyList() + val list2: List< + String, // The comma should be inserted before the comment + > = emptyList() + val list3: List< + String, /* The comma should be inserted before the comment */ + > = emptyList() + """.trimIndent() + ruleAssertThat(code) + .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) + .hasLintViolations( + LintViolation(3, 11, "Missing trailing comma before \">\""), + LintViolation(6, 11, "Missing trailing comma before \">\"") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given that the trailing comma is not allowed on call site then remove it from the array index when present`() { + val code = + """ + val foo = Array(2) { 42 } + val bar1 = foo[1,] + val bar2 = foo[ + 1, // The comma before the comment should be removed without removing the comment itself + ] + val bar3 = foo[ + 1, /* The comma before the comment should be removed without removing the comment itself */ + ] + """.trimIndent() + val formattedCode = + """ + val foo = Array(2) { 42 } + val bar1 = foo[1] + val bar2 = foo[ + 1 // The comma before the comment should be removed without removing the comment itself + ] + val bar3 = foo[ + 1 /* The comma before the comment should be removed without removing the comment itself */ + ] + """.trimIndent() + ruleAssertThat(code) + .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to false) + .hasLintViolations( + LintViolation(2, 17, "Unnecessary trailing comma before \"]\""), + LintViolation(4, 6, "Unnecessary trailing comma before \"]\""), + LintViolation(7, 6, "Unnecessary trailing comma before \"]\"") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given that the trailing comma is required on call site then add it to the array index when missing`() { + val code = + """ + val foo = Array(2) { 42 } + val bar1 = foo[1] + val bar2 = foo[ + 1 // The comma should be inserted before the comment + ] + val bar3 = foo[ + 1 /* The comma should be inserted before the comment */ + ] + """.trimIndent() + val formattedCode = + """ + val foo = Array(2) { 42 } + val bar1 = foo[1] + val bar2 = foo[ + 1, // The comma should be inserted before the comment + ] + val bar3 = foo[ + 1, /* The comma should be inserted before the comment */ + ] + """.trimIndent() + ruleAssertThat(code) + .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) + .hasLintViolations( + LintViolation(4, 6, "Missing trailing comma before \"]\""), + LintViolation(7, 6, "Missing trailing comma before \"]\"") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given that the trailing comma is not allowed on call site then remove it from the collection literal when present`() { + val code = + """ + annotation class Annotation(val params: IntArray) + + @Annotation([1, 2,]) + val foo1: Int = 0 + + @Annotation([ + 1, + 2, // The comma before the comment should be removed without removing the comment itself + ]) + val foo2: Int = 0 + + @Annotation([ + 1, + 2, /* The comma before the comment should be removed without removing the comment itself */ + ]) + val foo3: Int = 0 + """.trimIndent() + val formattedCode = + """ + annotation class Annotation(val params: IntArray) + + @Annotation([1, 2]) + val foo1: Int = 0 + + @Annotation([ + 1, + 2 // The comma before the comment should be removed without removing the comment itself + ]) + val foo2: Int = 0 + + @Annotation([ + 1, + 2 /* The comma before the comment should be removed without removing the comment itself */ + ]) + val foo3: Int = 0 + """.trimIndent() + ruleAssertThat(code) + .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to false) + .hasLintViolations( + LintViolation(3, 18, "Unnecessary trailing comma before \"]\""), + LintViolation(8, 6, "Unnecessary trailing comma before \"]\""), + LintViolation(14, 6, "Unnecessary trailing comma before \"]\"") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given that the trailing comma is required on call site then add it to the collection literal when missing`() { + val code = + """ + annotation class Annotation(val params: IntArray) + + @Annotation([1, 2]) + val foo1: Int = 0 + + @Annotation([ + 1, + 2 // The comma should be inserted before the comment + ]) + val foo2: Int = 0 + + @Annotation([ + 1, + 2 /* The comma should be inserted before the comment */ + ]) + val foo3: Int = 0 + + @Annotation( + [ + 1, + 2 /* The comma should be inserted before the comment */ + ], + [ + 3, + 4 /* The comma should be inserted before the comment */ + ] + ) + val foo4: Int = 0 + """.trimIndent() + val formattedCode = + """ + annotation class Annotation(val params: IntArray) + + @Annotation([1, 2]) + val foo1: Int = 0 + + @Annotation([ + 1, + 2, // The comma should be inserted before the comment + ]) + val foo2: Int = 0 + + @Annotation([ + 1, + 2, /* The comma should be inserted before the comment */ + ]) + val foo3: Int = 0 + + @Annotation( + [ + 1, + 2, /* The comma should be inserted before the comment */ + ], + [ + 3, + 4, /* The comma should be inserted before the comment */ + ], + ) + val foo4: Int = 0 + """.trimIndent() + ruleAssertThat(code) + .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) + .hasLintViolations( + LintViolation(8, 6, "Missing trailing comma before \"]\""), + LintViolation(14, 6, "Missing trailing comma before \"]\""), + LintViolation(21, 10, "Missing trailing comma before \"]\""), + LintViolation(25, 10, "Missing trailing comma before \"]\""), + LintViolation(26, 6, "Missing trailing comma before \")\"") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Given that a trailing comma is required on call site then still it should not be added to the setter`() { + val code = + """ + class Test { + var foo = Bar() + set(value) { + } + } + """.trimIndent() + ruleAssertThat(code) + .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) + .hasNoLintViolations() + } + + @Test + fun `1297 - Given that the trailing comma is required on call site the a trailing comma to collection literal when missing`() { + val code = + """ + annotation class FooBar( + val foo1: Array = [], + val foo2: Array = [], + val bar1: String = "" + ) + + @FooBar( + foo1 = [ + "foo-1" // Add trailing comma as the argument value list foo1 is a multiline statement + ], + foo2 = ["foo-2"], // Do not add trailing comma in the array as the argument value list foo2 is a single line statement + bar1 = "bar-1" // Add trailing comma as the outer argument value list of the annotation is a multiline statement + ) + val fooBar = null + """.trimIndent() + val formattedCode = + """ + annotation class FooBar( + val foo1: Array = [], + val foo2: Array = [], + val bar1: String = "" + ) + + @FooBar( + foo1 = [ + "foo-1", // Add trailing comma as the argument value list foo1 is a multiline statement + ], + foo2 = ["foo-2"], // Do not add trailing comma in the array as the argument value list foo2 is a single line statement + bar1 = "bar-1", // Add trailing comma as the outer argument value list of the annotation is a multiline statement + ) + val fooBar = null + """.trimIndent() + ruleAssertThat(code) + .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) + .hasLintViolations( + LintViolation(9, 16, "Missing trailing comma before \"]\""), + LintViolation(12, 19, "Missing trailing comma before \")\"") + ).isFormattedAs(formattedCode) + } + + @Test + fun `Issue 1379 - Given that a trailing comma is required on call site then add trailing comma after array in annotation when missing`() { + val code = + """ + import kotlin.reflect.KClass + + @Foo( + values = [ + Foo::class, + Foo::class + ] + ) + annotation class Foo(val values: Array>) + """.trimIndent() + val formattedCode = + """ + import kotlin.reflect.KClass + + @Foo( + values = [ + Foo::class, + Foo::class, + ], + ) + annotation class Foo(val values: Array>) + """.trimIndent() + ruleAssertThat(code) + .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) + .hasLintViolations( + LintViolation(6, 19, "Missing trailing comma before \"]\""), + LintViolation(7, 6, "Missing trailing comma before \")\"") + ).isFormattedAs(formattedCode) + } +} diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnDeclarationSiteRuleTest.kt similarity index 58% rename from ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaRuleTest.kt rename to ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnDeclarationSiteRuleTest.kt index f7e69b015d..50c705f749 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/TrailingCommaOnDeclarationSiteRuleTest.kt @@ -1,18 +1,16 @@ package com.pinterest.ktlint.ruleset.standard import com.pinterest.ktlint.core.RuleProvider -import com.pinterest.ktlint.ruleset.experimental.trailingcomma.TrailingCommaRule -import com.pinterest.ktlint.ruleset.experimental.trailingcomma.TrailingCommaRule.Companion.allowTrailingCommaOnCallSiteProperty -import com.pinterest.ktlint.ruleset.experimental.trailingcomma.TrailingCommaRule.Companion.allowTrailingCommaProperty -import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.ruleset.standard.TrailingCommaOnDeclarationSiteRule.Companion.allowTrailingCommaProperty +import com.pinterest.ktlint.test.KtLintAssertThat import com.pinterest.ktlint.test.LintViolation import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test -class TrailingCommaRuleTest { - private val trailingCommaRuleAssertThat = - assertThatRule( - provider = { TrailingCommaRule() }, +class TrailingCommaOnDeclarationSiteRuleTest { + private val ruleAssertThat = + KtLintAssertThat.assertThatRule( + provider = { TrailingCommaOnDeclarationSiteRule() }, additionalRuleProviders = setOf( // Apply the IndentationRule always as additional rule, so that the formattedCode in the unit test looks // correct. @@ -20,48 +18,6 @@ class TrailingCommaRuleTest { ) ) - @Test - fun `Given property allow trailing comma on call site is not set then remove trailing comma's`() { - val code = - """ - val foo1 = listOf("a", "b",) - - val foo2 = Pair(1, 2,) - - val foo3: List = emptyList() - - val foo4 = Array(2) { 42 } - val bar4 = foo4[1,] - - annotation class Foo5(val params: IntArray) - @Foo5([1, 2,]) - val foo5: Int = 0 - """.trimIndent() - val formattedCode = - """ - val foo1 = listOf("a", "b") - - val foo2 = Pair(1, 2) - - val foo3: List = emptyList() - - val foo4 = Array(2) { 42 } - val bar4 = foo4[1] - - annotation class Foo5(val params: IntArray) - @Foo5([1, 2]) - val foo5: Int = 0 - """.trimIndent() - trailingCommaRuleAssertThat(code) - .hasLintViolations( - LintViolation(1, 27, "Unnecessary trailing comma before \")\""), - LintViolation(3, 21, "Unnecessary trailing comma before \")\""), - LintViolation(5, 22, "Unnecessary trailing comma before \">\""), - LintViolation(8, 18, "Unnecessary trailing comma before \"]\""), - LintViolation(11, 12, "Unnecessary trailing comma before \"]\"") - ).isFormattedAs(formattedCode) - } - @Test fun `Given property allow trailing comma on declaration site is not set then remove trailing comma's`() { val code = @@ -106,7 +62,7 @@ class TrailingCommaRuleTest { val foo6: (Int, Int) -> Int = { foo, bar -> foo * bar } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .hasLintViolations( LintViolation(1, 29, "Unnecessary trailing comma before \")\""), LintViolation(3, 16, "Unnecessary trailing comma before \">\""), @@ -118,133 +74,6 @@ class TrailingCommaRuleTest { ).isFormattedAs(formattedCode) } - @Test - fun `Given that properties to force trailing comma's on call and declaration site have been enabled`() { - val code = - """ - fun test( - x: Int, - y: Int, - block: (Int, Int) -> Int - ): ( - Int, Int - ) -> Int = when (x) { - 1, 2 - -> { - foo, - bar /* The comma should be inserted before the comment */ - -> - block( - foo * bar, - foo + bar - ) - } - else -> { _, _ -> block(0, 0) } - } - """.trimIndent() - val formattedCode = - """ - fun test( - x: Int, - y: Int, - block: (Int, Int) -> Int, - ): ( - Int, Int, - ) -> Int = when (x) { - 1, 2, - -> { - foo, - bar, /* The comma should be inserted before the comment */ - -> - block( - foo * bar, - foo + bar, - ) - } - else -> { _, _ -> block(0, 0) } - } - """.trimIndent() - trailingCommaRuleAssertThat(code) - .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) - .withEditorConfigOverride(allowTrailingCommaProperty to true) - .hasLintViolations( - LintViolation(4, 29, "Missing trailing comma before \")\""), - LintViolation(6, 13, "Missing trailing comma before \")\""), - LintViolation(8, 9, "Missing trailing comma before \"->\""), - LintViolation(11, 16, "Missing trailing comma before \"->\""), - LintViolation(15, 22, "Missing trailing comma before \")\"") - ).isFormattedAs(formattedCode) - } - - @Test - fun `Given that the trailing comma is not allowed on call site then remove it from an argument list when present`() { - val code = - """ - val list1 = listOf("a", "b",) - val list2 = listOf( - "a", - "b", // The comma before the comment should be removed without removing the comment itself - ) - val list3 = listOf( - "a", - "b", /* The comma before the comment should be removed without removing the comment itself */ - ) - """.trimIndent() - val formattedCode = - """ - val list1 = listOf("a", "b") - val list2 = listOf( - "a", - "b" // The comma before the comment should be removed without removing the comment itself - ) - val list3 = listOf( - "a", - "b" /* The comma before the comment should be removed without removing the comment itself */ - ) - """.trimIndent() - trailingCommaRuleAssertThat(code) - .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to false) - .hasLintViolations( - LintViolation(1, 28, "Unnecessary trailing comma before \")\""), - LintViolation(4, 8, "Unnecessary trailing comma before \")\""), - LintViolation(8, 8, "Unnecessary trailing comma before \")\"") - ).isFormattedAs(formattedCode) - } - - @Test - fun `Given that the trailing comma is required on call site then add it to the argument list when missing`() { - val code = - """ - val list1 = listOf("a", "b") - val list2 = listOf( - "a", - "b" // The comma should be inserted before the comment - ) - val list3 = listOf( - "a", - "b" /* The comma should be inserted before the comment */ - ) - """.trimIndent() - val formattedCode = - """ - val list1 = listOf("a", "b") - val list2 = listOf( - "a", - "b", // The comma should be inserted before the comment - ) - val list3 = listOf( - "a", - "b", /* The comma should be inserted before the comment */ - ) - """.trimIndent() - trailingCommaRuleAssertThat(code) - .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) - .hasLintViolations( - LintViolation(4, 8, "Missing trailing comma before \")\""), - LintViolation(8, 8, "Missing trailing comma before \")\"") - ).isFormattedAs(formattedCode) - } - @Test fun `Given that the trailing comma is not allowed on declaration site then remove it from an parameter list when present`() { val code = @@ -267,7 +96,7 @@ class TrailingCommaRuleTest { val bar: Int /* The comma before the comment should be removed without removing the comment itself */ ) """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to false) .hasLintViolations( LintViolation(1, 29, "Unnecessary trailing comma before \")\""), @@ -298,7 +127,7 @@ class TrailingCommaRuleTest { val bar: Int, /* The comma should be inserted before the comment */ ) """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasLintViolations( LintViolation(3, 17, "Missing trailing comma before \")\""), @@ -332,7 +161,7 @@ class TrailingCommaRuleTest { B /* The comma before the comment should be removed without removing the comment itself */ > {} """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to false) .hasLintViolations( LintViolation(1, 16, "Unnecessary trailing comma before \">\""), @@ -367,7 +196,7 @@ class TrailingCommaRuleTest { B, /* The comma should be inserted before the comment */ > {} """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasLintViolations( LintViolation(4, 6, "Missing trailing comma before \">\""), @@ -401,7 +230,7 @@ class TrailingCommaRuleTest { else -> "b" } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to false) .hasLintViolations( LintViolation(2, 9, "Unnecessary trailing comma before \"->\""), @@ -436,7 +265,7 @@ class TrailingCommaRuleTest { else -> "b" } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasLintViolations( LintViolation(3, 9, "Missing trailing comma before \"->\""), @@ -478,7 +307,7 @@ class TrailingCommaRuleTest { ) = bar() } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to false) .hasLintViolations( LintViolation(4, 14, "Unnecessary trailing comma before \")\""), @@ -521,7 +350,7 @@ class TrailingCommaRuleTest { ) = bar() } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasLintViolations( LintViolation(7, 10, "Missing trailing comma before \")\""), @@ -559,7 +388,7 @@ class TrailingCommaRuleTest { -> foo * bar } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to false) .hasLintViolations( LintViolation(1, 44, "Unnecessary trailing comma before \"->\""), @@ -598,7 +427,7 @@ class TrailingCommaRuleTest { -> foo * bar } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasLintViolations( LintViolation(4, 12, "Missing trailing comma before \"->\""), @@ -607,301 +436,41 @@ class TrailingCommaRuleTest { } @Test - fun `Given that the trailing comma is not allowed on call site then remove it from the type argument list when present`() { + fun `Given that a trailing comma is required on declaration site then add it to function declaration`() { val code = """ - val list1: List = emptyList() - val list2: List< - String, // The comma before the comment should be removed without removing the comment itself - > = emptyList() - val list3: List< - String, /* The comma before the comment should be removed without removing the comment itself */ - > = emptyList() - """.trimIndent() - val formattedCode = - """ - val list1: List = emptyList() - val list2: List< - String // The comma before the comment should be removed without removing the comment itself - > = emptyList() - val list3: List< - String /* The comma before the comment should be removed without removing the comment itself */ - > = emptyList() - """.trimIndent() - trailingCommaRuleAssertThat(code) - .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to false) - .hasLintViolations( - LintViolation(1, 23, "Unnecessary trailing comma before \">\""), - LintViolation(3, 11, "Unnecessary trailing comma before \">\""), - LintViolation(6, 11, "Unnecessary trailing comma before \">\"") - ).isFormattedAs(formattedCode) - } - - @Test - fun `Given that the trailing comma is required on call site then add it to the type argument list when missing`() { - val code = - """ - val list1: List = emptyList() - val list2: List< - String // The comma should be inserted before the comment - > = emptyList() - val list3: List< - String /* The comma should be inserted before the comment */ - > = emptyList() - """.trimIndent() - val formattedCode = - """ - val list1: List = emptyList() - val list2: List< - String, // The comma should be inserted before the comment - > = emptyList() - val list3: List< - String, /* The comma should be inserted before the comment */ - > = emptyList() - """.trimIndent() - trailingCommaRuleAssertThat(code) - .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) - .hasLintViolations( - LintViolation(3, 11, "Missing trailing comma before \">\""), - LintViolation(6, 11, "Missing trailing comma before \">\"") - ).isFormattedAs(formattedCode) - } - - @Test - fun `Given that the trailing comma is not allowed on call site then remove it from the array index when present`() { - val code = - """ - val foo = Array(2) { 42 } - val bar1 = foo[1,] - val bar2 = foo[ - 1, // The comma before the comment should be removed without removing the comment itself - ] - val bar3 = foo[ - 1, /* The comma before the comment should be removed without removing the comment itself */ - ] - """.trimIndent() - val formattedCode = - """ - val foo = Array(2) { 42 } - val bar1 = foo[1] - val bar2 = foo[ - 1 // The comma before the comment should be removed without removing the comment itself - ] - val bar3 = foo[ - 1 /* The comma before the comment should be removed without removing the comment itself */ - ] - """.trimIndent() - trailingCommaRuleAssertThat(code) - .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to false) - .hasLintViolations( - LintViolation(2, 17, "Unnecessary trailing comma before \"]\""), - LintViolation(4, 6, "Unnecessary trailing comma before \"]\""), - LintViolation(7, 6, "Unnecessary trailing comma before \"]\"") - ).isFormattedAs(formattedCode) - } - - @Test - fun `Given that the trailing comma is required on call site then add it to the array index when missing`() { - val code = - """ - val foo = Array(2) { 42 } - val bar1 = foo[1] - val bar2 = foo[ - 1 // The comma should be inserted before the comment - ] - val bar3 = foo[ - 1 /* The comma should be inserted before the comment */ - ] - """.trimIndent() - val formattedCode = - """ - val foo = Array(2) { 42 } - val bar1 = foo[1] - val bar2 = foo[ - 1, // The comma should be inserted before the comment - ] - val bar3 = foo[ - 1, /* The comma should be inserted before the comment */ - ] - """.trimIndent() - trailingCommaRuleAssertThat(code) - .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) - .hasLintViolations( - LintViolation(4, 6, "Missing trailing comma before \"]\""), - LintViolation(7, 6, "Missing trailing comma before \"]\"") - ).isFormattedAs(formattedCode) - } - - @Test - fun `Given that the trailing comma is not allowed on call site then remove it from the collection literal when present`() { - val code = - """ - annotation class Annotation(val params: IntArray) - - @Annotation([1, 2,]) - val foo1: Int = 0 - - @Annotation([ - 1, - 2, // The comma before the comment should be removed without removing the comment itself - ]) - val foo2: Int = 0 - - @Annotation([ - 1, - 2, /* The comma before the comment should be removed without removing the comment itself */ - ]) - val foo3: Int = 0 - """.trimIndent() - val formattedCode = - """ - annotation class Annotation(val params: IntArray) - - @Annotation([1, 2]) - val foo1: Int = 0 - - @Annotation([ - 1, - 2 // The comma before the comment should be removed without removing the comment itself - ]) - val foo2: Int = 0 - - @Annotation([ - 1, - 2 /* The comma before the comment should be removed without removing the comment itself */ - ]) - val foo3: Int = 0 - """.trimIndent() - trailingCommaRuleAssertThat(code) - .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to false) - .hasLintViolations( - LintViolation(3, 18, "Unnecessary trailing comma before \"]\""), - LintViolation(8, 6, "Unnecessary trailing comma before \"]\""), - LintViolation(14, 6, "Unnecessary trailing comma before \"]\"") - ).isFormattedAs(formattedCode) - } - - @Test - fun `Given that the trailing comma is required on call site then add it to the collection literal when missing`() { - val code = - """ - annotation class Annotation(val params: IntArray) - - @Annotation([1, 2]) - val foo1: Int = 0 - - @Annotation([ - 1, - 2 // The comma should be inserted before the comment - ]) - val foo2: Int = 0 - - @Annotation([ - 1, - 2 /* The comma should be inserted before the comment */ - ]) - val foo3: Int = 0 - - @Annotation( - [ - 1, - 2 /* The comma should be inserted before the comment */ - ], - [ - 3, - 4 /* The comma should be inserted before the comment */ - ] - ) - val foo4: Int = 0 - """.trimIndent() - val formattedCode = - """ - annotation class Annotation(val params: IntArray) - - @Annotation([1, 2]) - val foo1: Int = 0 - - @Annotation([ - 1, - 2, // The comma should be inserted before the comment - ]) - val foo2: Int = 0 - - @Annotation([ - 1, - 2, /* The comma should be inserted before the comment */ - ]) - val foo3: Int = 0 - - @Annotation( - [ - 1, - 2, /* The comma should be inserted before the comment */ - ], - [ - 3, - 4, /* The comma should be inserted before the comment */ - ], - ) - val foo4: Int = 0 - """.trimIndent() - trailingCommaRuleAssertThat(code) - .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) - .hasLintViolations( - LintViolation(8, 6, "Missing trailing comma before \"]\""), - LintViolation(14, 6, "Missing trailing comma before \"]\""), - LintViolation(21, 10, "Missing trailing comma before \"]\""), - LintViolation(25, 10, "Missing trailing comma before \"]\""), - LintViolation(26, 6, "Missing trailing comma before \")\"") - ).isFormattedAs(formattedCode) - } - - @Test - fun `1297 - Given that the trailing comma is required on call site the a trailing comma to collection literal when missing`() { - val code = - """ - annotation class FooBar( - val foo1: Array = [], - val foo2: Array = [], - val bar1: String = "" - ) - - @FooBar( - foo1 = [ - "foo-1" // Add trailing comma as the argument value list foo1 is a multiline statement - ], - foo2 = ["foo-2"], // Do not add trailing comma in the array as the argument value list foo2 is a single line statement - bar1 = "bar-1" // Add trailing comma as the outer argument value list of the annotation is a multiline statement - ) - val fooBar = null + fun test( + x: Int, + y: Int, + block: (Int, Int) -> Int + ): ( + Int, Int + ) -> Int = { foo, bar -> + block(foo, bar) + } """.trimIndent() val formattedCode = """ - annotation class FooBar( - val foo1: Array = [], - val foo2: Array = [], - val bar1: String = "" - ) - - @FooBar( - foo1 = [ - "foo-1", // Add trailing comma as the argument value list foo1 is a multiline statement - ], - foo2 = ["foo-2"], // Do not add trailing comma in the array as the argument value list foo2 is a single line statement - bar1 = "bar-1", // Add trailing comma as the outer argument value list of the annotation is a multiline statement - ) - val fooBar = null + fun test( + x: Int, + y: Int, + block: (Int, Int) -> Int, + ): ( + Int, Int, + ) -> Int = { foo, bar -> + block(foo, bar) + } """.trimIndent() - trailingCommaRuleAssertThat(code) - .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) + ruleAssertThat(code) + .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasLintViolations( - LintViolation(9, 16, "Missing trailing comma before \"]\""), - LintViolation(12, 19, "Missing trailing comma before \")\"") + LintViolation(4, 29, "Missing trailing comma before \")\""), + LintViolation(6, 13, "Missing trailing comma before \")\"") ).isFormattedAs(formattedCode) } @Test - fun `Given that a trailing comma is required on call site and declaration site then still it should not be added to the setter`() { + fun `Given that a trailing comma is required on declaration site then still it should not be added to the setter`() { val code = """ class Test { @@ -910,9 +479,8 @@ class TrailingCommaRuleTest { } } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) - .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) .hasNoLintViolations() } @@ -942,7 +510,7 @@ class TrailingCommaRuleTest { else -> "d" } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasLintViolations( LintViolation(4, 6, "Missing trailing comma and newline before \"->\""), @@ -978,7 +546,7 @@ class TrailingCommaRuleTest { val bar3: EnumThree, ) """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) // When running format mode, the rules are first executed in parallel to find linting errors. In this // process, no unused import are found because the trailing comma is not yet added to variable "bar3". Then // in the next stage the rules are run consecutively. Now the trailing comma rule is adding a trailing comma @@ -992,40 +560,6 @@ class TrailingCommaRuleTest { .isFormattedAs(formattedCode) } - @Test - fun `Issue 1379 - Given that a trailing comma is required on call site then add trailing comma after array in annotation when missing`() { - val code = - """ - import kotlin.reflect.KClass - - @Foo( - values = [ - Foo::class, - Foo::class - ] - ) - annotation class Foo(val values: Array>) - """.trimIndent() - val formattedCode = - """ - import kotlin.reflect.KClass - - @Foo( - values = [ - Foo::class, - Foo::class, - ], - ) - annotation class Foo(val values: Array>) - """.trimIndent() - trailingCommaRuleAssertThat(code) - .withEditorConfigOverride(allowTrailingCommaOnCallSiteProperty to true) - .hasLintViolations( - LintViolation(6, 19, "Missing trailing comma before \"]\""), - LintViolation(7, 6, "Missing trailing comma before \")\"") - ).isFormattedAs(formattedCode) - } - @Test fun `Given that a trailing comma is is not allowed then remove comma after last enum member`() { val code = @@ -1042,7 +576,7 @@ class TrailingCommaRuleTest { TRIANGLE } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to false) .hasLintViolation(3, 13, "Unnecessary trailing comma before \"}\"") .isFormattedAs(formattedCode) @@ -1056,7 +590,6 @@ class TrailingCommaRuleTest { SQUARE, TRIANGLE, ; - fun print() = name() } """.trimIndent() @@ -1066,11 +599,10 @@ class TrailingCommaRuleTest { SQUARE, TRIANGLE ; - fun print() = name() } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to false) .hasLintViolation(3, 13, "Unnecessary trailing comma before \";\"") .isFormattedAs(formattedCode) @@ -1084,7 +616,7 @@ class TrailingCommaRuleTest { SQUARE, TRIANGLE, } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to false) .hasNoLintViolations() } @@ -1099,7 +631,7 @@ class TrailingCommaRuleTest { SQUARE, TRIANGLE } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasNoLintViolations() } @@ -1121,7 +653,7 @@ class TrailingCommaRuleTest { ; } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasLintViolation(3, 13, "Missing trailing comma before \";\"") .isFormattedAs(formattedCode) @@ -1144,7 +676,7 @@ class TrailingCommaRuleTest { ; } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasLintViolation(3, 13, "Missing trailing comma before \";\"") .isFormattedAs(formattedCode) @@ -1167,7 +699,7 @@ class TrailingCommaRuleTest { ; } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasLintViolation(3, 13, "Missing trailing comma before \";\"") .isFormattedAs(formattedCode) @@ -1180,7 +712,6 @@ class TrailingCommaRuleTest { enum class Shape { SQUARE, TRIANGLE; - fun print() = name() } """.trimIndent() @@ -1190,11 +721,10 @@ class TrailingCommaRuleTest { SQUARE, TRIANGLE, ; - fun print() = name() } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasLintViolation(3, 13, "Missing trailing comma before \";\"") .isFormattedAs(formattedCode) @@ -1207,12 +737,10 @@ class TrailingCommaRuleTest { interface Printable { fun print(): String } - enum class Shape : Printable { Square { override fun print() = "■" }, - Triangle { override fun print() = "▲" } @@ -1223,20 +751,18 @@ class TrailingCommaRuleTest { interface Printable { fun print(): String } - enum class Shape : Printable { Square { override fun print() = "■" }, - Triangle { override fun print() = "▲" }, } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) - .hasLintViolation(12, 6, "Missing trailing comma before \"}\"") + .hasLintViolation(10, 6, "Missing trailing comma before \"}\"") .isFormattedAs(formattedCode) } @@ -1256,7 +782,7 @@ class TrailingCommaRuleTest { TRIANGLE, } """.trimIndent() - trailingCommaRuleAssertThat(code) + ruleAssertThat(code) .withEditorConfigOverride(allowTrailingCommaProperty to true) .hasLintViolation(3, 13, "Missing trailing comma before \"}\"") .isFormattedAs(formattedCode)