From 37a281966360aa9134104a8258944ff86e611c22 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Sun, 19 Nov 2023 16:26:56 +0100 Subject: [PATCH 1/7] Break dependency between `chain-method-continuation` and `discouraged-comment-location` --- .../rules/ChainMethodContinuationRule.kt | 39 ++++++++---- .../rules/DiscouragedCommentLocationRule.kt | 1 + .../rules/ChainMethodContinuationRuleTest.kt | 63 +++++++++++++++---- .../standard/rules/FunctionLiteralRuleTest.kt | 1 - 4 files changed, 78 insertions(+), 26 deletions(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule.kt index 42499f7567..0730fc3c16 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRule.kt @@ -71,11 +71,7 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet public class ChainMethodContinuationRule : StandardRule( id = "chain-method-continuation", - visitorModifiers = - setOf( - RunAfterRule(DISCOURAGED_COMMENT_LOCATION_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED), - RunAfterRule(ARGUMENT_LIST_WRAPPING_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED), - ), + visitorModifiers = setOf(RunAfterRule(ARGUMENT_LIST_WRAPPING_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED)), usesEditorConfigProperties = setOf( CODE_STYLE_PROPERTY, @@ -129,6 +125,7 @@ public class ChainMethodContinuationRule : ?.takeUnless { it.rootASTNode.treeParent.elementType == LONG_STRING_TEMPLATE_ENTRY } ?.let { chainedExpression -> fixWhitespaceBeforeChainOperators(chainedExpression, emit, autoCorrect) + disallowCommentBetweenDotAndCallExpression(chainedExpression, emit) fixWhiteSpaceAfterChainOperators(chainedExpression, emit, autoCorrect) } } @@ -145,14 +142,14 @@ public class ChainMethodContinuationRule : .chainOperators .filterNot { it.isJavaClassReferenceExpression() } .forEach { chainOperator -> - if (chainOperator.shouldBeOnSameLineAsClosingElementOfPreviousExpressionInMethodChain()) { - removeWhiteSpaceBeforeChainOperator(chainOperator, emit, autoCorrect) - } else if ( - wrapBeforeEachChainOperator || - exceedsMaxLineLength || - chainOperator.isPrecededByComment() - ) { - insertWhiteSpaceBeforeChainOperator(chainOperator, emit, autoCorrect) + when { + chainOperator.shouldBeOnSameLineAsClosingElementOfPreviousExpressionInMethodChain() -> { + removeWhiteSpaceBeforeChainOperator(chainOperator, emit, autoCorrect) + } + + wrapBeforeEachChainOperator || exceedsMaxLineLength || chainOperator.isPrecededByComment() -> { + insertWhiteSpaceBeforeChainOperator(chainOperator, emit, autoCorrect) + } } } } @@ -248,6 +245,8 @@ public class ChainMethodContinuationRule : private fun ASTNode.isPrecededByComment() = treeParent.children().any { it.isPartOfComment() } + private fun ASTNode.isSucceededByComment() = nextSibling()?.isPartOfComment() ?: false + private fun insertWhiteSpaceBeforeChainOperator( chainOperator: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, @@ -332,6 +331,20 @@ public class ChainMethodContinuationRule : } } + private fun disallowCommentBetweenDotAndCallExpression( + chainedExpression: ChainedExpression, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + ) { + chainedExpression + .chainOperators + .forEach { chainOperator -> + chainOperator + .nextSibling { !it.isWhiteSpace() } + ?.takeIf { it.isPartOfComment() } + ?.let { emit(it.startOffset, "No comment expected at this location in method chain", false) } + } + } + private fun fixWhiteSpaceAfterChainOperators( chainedExpression: ChainedExpression, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt index 314a10d99a..59908d21ae 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt @@ -53,6 +53,7 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType */ @SinceKtlint("0.45", EXPERIMENTAL) @SinceKtlint("1.0", STABLE) +@Deprecated("Marked for removal in ktlint 2.0. See https://github.com/pinterest/ktlint/issues/2367") public class DiscouragedCommentLocationRule : StandardRule("discouraged-comment-location") { override fun beforeVisitChildNodes( node: ASTNode, diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRuleTest.kt index 54271d675e..88acf3afc1 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRuleTest.kt @@ -19,7 +19,6 @@ class ChainMethodContinuationRuleTest { provider = { ChainMethodContinuationRule() }, additionalRuleProviders = setOf( - RuleProvider { DiscouragedCommentLocationRule() }, RuleProvider { ArgumentListWrappingRule() }, ), ) @@ -698,7 +697,7 @@ class ChainMethodContinuationRuleTest { @Nested inner class `Given chain with comments` { @Test - fun `Comments between a chain operator and the next chained method are disallowed`() { + fun `Comments between a DOT chain operator and the next chained method are disallowed`() { val code = """ val foo1 = listOf(1, 2, 3) @@ -723,17 +722,57 @@ class ChainMethodContinuationRuleTest { .// some comment filter { it > 2 } """.trimIndent() - assertThatRule { DiscouragedCommentLocationRule() }(code) + chainMethodContinuationRuleAssertThat(code) + .hasLintViolations( + LintViolation(2, 6, "No comment expected at this location in method chain", false), + LintViolation(4, 6, "No comment expected at this location in method chain", false), + LintViolation(6, 6, "No comment expected at this location in method chain", false), + LintViolation(8, 6, "No comment expected at this location in method chain", false), + LintViolation(10, 6, "No comment expected at this location in method chain", false), + LintViolation(12, 6, "No comment expected at this location in method chain", false), + LintViolation(14, 6, "No comment expected at this location in method chain", false), + LintViolation(17, 6, "No comment expected at this location in method chain", false), + LintViolation(20, 6, "No comment expected at this location in method chain", false), + ).hasNoLintViolationsForRuleId(CHAIN_WRAPPING_RULE_ID) + } + + @Test + fun `Comments between a SAFE ACCESS chain operator and the next chained method are disallowed`() { + val code = + """ + val foo1 = listOf(1, 2, 3) + ?./** some comment */size + val foo2 = listOf(1, 2, 3) + ?./** some comment */single() + val foo3 = listOf(1, 2, 3) + ?./** some comment */filter { it > 2 } + val foo4 = listOf(1, 2, 3) + ?./* some comment */size + val foo5 = listOf(1, 2, 3) + ?./* some comment */single() + val foo6 = listOf(1, 2, 3) + ?./* some comment */filter { it > 2 } + val foo7 = listOf(1, 2, 3) + ?.// some comment + size + val foo8 = listOf(1, 2, 3) + ?.// some comment + single() + val foo9 = listOf(1, 2, 3) + ?.// some comment + filter { it > 2 } + """.trimIndent() + chainMethodContinuationRuleAssertThat(code) .hasLintViolations( - LintViolation(2, 6, "No comment expected at this location", false), - LintViolation(4, 6, "No comment expected at this location", false), - LintViolation(6, 6, "No comment expected at this location", false), - LintViolation(8, 6, "No comment expected at this location", false), - LintViolation(10, 6, "No comment expected at this location", false), - LintViolation(12, 6, "No comment expected at this location", false), - LintViolation(14, 6, "No comment expected at this location", false), - LintViolation(17, 6, "No comment expected at this location", false), - LintViolation(20, 6, "No comment expected at this location", false), + LintViolation(2, 7, "No comment expected at this location in method chain", false), + LintViolation(4, 7, "No comment expected at this location in method chain", false), + LintViolation(6, 7, "No comment expected at this location in method chain", false), + LintViolation(8, 7, "No comment expected at this location in method chain", false), + LintViolation(10, 7, "No comment expected at this location in method chain", false), + LintViolation(12, 7, "No comment expected at this location in method chain", false), + LintViolation(14, 7, "No comment expected at this location in method chain", false), + LintViolation(17, 7, "No comment expected at this location in method chain", false), + LintViolation(20, 7, "No comment expected at this location in method chain", false), ).hasNoLintViolationsForRuleId(CHAIN_WRAPPING_RULE_ID) } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleTest.kt index 3ed2f88938..817dcd729a 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleTest.kt @@ -435,7 +435,6 @@ class FunctionLiteralRuleTest { .addAdditionalRuleProvider { MultilineExpressionWrappingRule() } .addAdditionalRuleProvider { ChainMethodContinuationRule() } .addAdditionalRuleProvider { ArgumentListWrappingRule() } - .addAdditionalRuleProvider { DiscouragedCommentLocationRule() } .addAdditionalRuleProvider { IndentationRule() } .setMaxLineLength() .hasLintViolations( From 8d740f915199a154adde66cee464210050574a44 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Sun, 19 Nov 2023 17:32:13 +0100 Subject: [PATCH 2/7] - Add utility methods `afterCodeSibling`, `beforeCodeSibling` and `betweenCodeSiblings` to ASTNodeExtensions - Break dependency between `if-else-wrapping` and `discouraged-comment-location` --- .../api/ktlint-rule-engine-core.api | 3 + .../rule/engine/core/api/ASTNodeExtension.kt | 9 + .../engine/core/api/ASTNodeExtensionTest.kt | 64 ++++ .../rules/DiscouragedCommentLocationRule.kt | 26 +- .../standard/rules/IfElseWrappingRule.kt | 23 +- .../standard/rules/IfElseWrappingRuleTest.kt | 284 +++++++++++++++++- 6 files changed, 384 insertions(+), 25 deletions(-) diff --git a/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api b/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api index caf8fc8ca9..d22c531279 100644 --- a/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api +++ b/ktlint-rule-engine-core/api/ktlint-rule-engine-core.api @@ -1,4 +1,7 @@ public final class com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionKt { + public static final fun afterCodeSibling (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;)Z + public static final fun beforeCodeSibling (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;)Z + public static final fun betweenCodeSiblings (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;)Z public static final fun children (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Lkotlin/sequences/Sequence; public static final fun findCompositeParentElementOfType (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lorg/jetbrains/kotlin/com/intellij/psi/tree/IElementType;)Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode; public static final fun firstChildLeafOrSelf (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;)Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode; diff --git a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt index 1d81df333e..31e800fca2 100644 --- a/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt +++ b/ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtension.kt @@ -470,3 +470,12 @@ public fun Sequence.lineLengthWithoutNewlinePrefix(): Int { .takeWhile { it != '\n' } .length } + +public fun ASTNode.afterCodeSibling(afterElementType: IElementType): Boolean = prevCodeSibling()?.elementType == afterElementType + +public fun ASTNode.beforeCodeSibling(beforeElementType: IElementType): Boolean = nextCodeSibling()?.elementType == beforeElementType + +public fun ASTNode.betweenCodeSiblings( + afterElementType: IElementType, + beforeElementType: IElementType, +): Boolean = afterCodeSibling(afterElementType) && beforeCodeSibling(beforeElementType) diff --git a/ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt b/ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt index 27f15a3728..8cd902a307 100644 --- a/ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt +++ b/ktlint-rule-engine-core/src/test/kotlin/com/pinterest/ktlint/rule/engine/core/api/ASTNodeExtensionTest.kt @@ -7,6 +7,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS_BODY import com.pinterest.ktlint.rule.engine.core.api.ElementType.ENUM_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN +import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN_KEYWORD import com.pinterest.ktlint.rule.engine.core.api.ElementType.IDENTIFIER import com.pinterest.ktlint.rule.engine.core.api.ElementType.LPAR import com.pinterest.ktlint.rule.engine.core.api.ElementType.MODIFIER_LIST @@ -22,6 +23,8 @@ import org.jetbrains.kotlin.com.intellij.lang.FileASTNode import org.jetbrains.kotlin.psi.psiUtil.leaves import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource import kotlin.reflect.KFunction1 class ASTNodeExtensionTest { @@ -722,6 +725,67 @@ class ASTNodeExtensionTest { ) } + @ParameterizedTest(name = "Text between FUN_KEYWORD and IDENTIFIER: {0}") + @ValueSource( + strings = [ + " ", + "\n", + "/* some comment*/", + "// some EOL comment\n", + ], + ) + fun `Given a function declaration then the IDENTIFIER should be after the FUN_KEYWORD element type`(separator: String) { + val code = + """ + fun${separator}foo() = 42 + """.trimIndent() + val actual = + transformCodeToAST(code) + .findChildByType(FUN) + ?.findChildByType(IDENTIFIER) + ?.afterCodeSibling(FUN_KEYWORD) + + assertThat(actual).isTrue() + } + + @ParameterizedTest(name = "Text between FUN_KEYWORD and IDENTIFIER: {0}") + @ValueSource( + strings = [ + " ", + "\n", + "/* some comment*/", + "// some EOL comment\n", + ], + ) + fun `Given a function declaration then the FUN_KEYWORD should be before the IDENTIFIER element type`(separator: String) { + val code = + """ + fun${separator}foo() = 42 + """.trimIndent() + val actual = + transformCodeToAST(code) + .findChildByType(FUN) + ?.findChildByType(FUN_KEYWORD) + ?.beforeCodeSibling(IDENTIFIER) + + assertThat(actual).isTrue() + } + + @Test + fun `Given a function declaration then the IDENTIFIER should be between the FUN_KEYWORD and the VALUE_PARAMETER_LIST element type`() { + val code = + """ + fun foo() = 42 + """.trimIndent() + val actual = + transformCodeToAST(code) + .findChildByType(FUN) + ?.findChildByType(IDENTIFIER) + ?.betweenCodeSiblings(FUN_KEYWORD, VALUE_PARAMETER_LIST) + + assertThat(actual).isTrue() + } + private inline fun String.transformAst(block: FileASTNode.() -> Unit): FileASTNode = transformCodeToAST(this) .apply(block) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt index 59908d21ae..c4424bcee6 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt @@ -21,14 +21,13 @@ import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.afterCodeSibling +import com.pinterest.ktlint.rule.engine.core.api.betweenCodeSiblings import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline -import com.pinterest.ktlint.rule.engine.core.api.nextCodeSibling -import com.pinterest.ktlint.rule.engine.core.api.prevCodeSibling import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType /** * The AST allows comments to be placed anywhere. This however can lead to code which is unnecessarily hard to read. @@ -63,12 +62,12 @@ public class DiscouragedCommentLocationRule : StandardRule("discouraged-comment- if (node.isPartOfComment()) { // Be restrictive when adding new locations at which comments are discouraged. Always run against major // open source projects first to verify whether valid cases are found to comment at this location. - if (node.afterNodeOfElementType(TYPE_PARAMETER_LIST) || - node.betweenNodesOfElementType(RPAR, THEN) || - node.betweenNodesOfElementType(THEN, ELSE_KEYWORD) || - node.betweenNodesOfElementType(ELSE_KEYWORD, ELSE) || - node.afterNodeOfElementType(DOT) || - node.afterNodeOfElementType(SAFE_ACCESS) + if (node.afterCodeSibling(TYPE_PARAMETER_LIST) || + node.betweenCodeSiblings(RPAR, THEN) || + node.betweenCodeSiblings(THEN, ELSE_KEYWORD) || + node.betweenCodeSiblings(ELSE_KEYWORD, ELSE) || + node.afterCodeSibling(DOT) || + node.afterCodeSibling(SAFE_ACCESS) ) { emit(node.startOffset, "No comment expected at this location", false) } @@ -81,15 +80,6 @@ public class DiscouragedCommentLocationRule : StandardRule("discouraged-comment- } } - private fun ASTNode.afterNodeOfElementType(afterElementType: IElementType) = prevCodeSibling()?.elementType == afterElementType - - private fun ASTNode.beforeNodeOfElementType(beforeElementType: IElementType) = nextCodeSibling()?.elementType == beforeElementType - - private fun ASTNode.betweenNodesOfElementType( - afterElementType: IElementType, - beforeElementType: IElementType, - ) = afterNodeOfElementType(afterElementType) && beforeNodeOfElementType(beforeElementType) - private fun visitListElement( node: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRule.kt index 39d9092b5d..04e50d8a7b 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRule.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK import com.pinterest.ktlint.rule.engine.core.api.ElementType.ELSE import com.pinterest.ktlint.rule.engine.core.api.ElementType.ELSE_KEYWORD @@ -10,12 +11,11 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACE import com.pinterest.ktlint.rule.engine.core.api.ElementType.THEN import com.pinterest.ktlint.rule.engine.core.api.IndentConfig import com.pinterest.ktlint.rule.engine.core.api.Rule -import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule -import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.betweenCodeSiblings import com.pinterest.ktlint.rule.engine.core.api.children import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_SIZE_PROPERTY @@ -44,7 +44,6 @@ import org.jetbrains.kotlin.utils.addToStdlib.applyIf public class IfElseWrappingRule : StandardRule( id = "if-else-wrapping", - visitorModifiers = setOf(RunAfterRule(DISCOURAGED_COMMENT_LOCATION_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED)), usesEditorConfigProperties = setOf( INDENT_SIZE_PROPERTY, @@ -67,8 +66,9 @@ public class IfElseWrappingRule : autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, ) { - if (node.elementType == IF) { - visitIf(node, autoCorrect, emit) + when { + node.elementType == IF -> visitIf(node, autoCorrect, emit) + node.isPartOfComment() && node.treeParent.elementType == IF -> visitComment(node, emit) } } @@ -193,6 +193,19 @@ public class IfElseWrappingRule : findChildByType(ELSE)?.firstChildNode?.elementType == IF } + private fun visitComment( + comment: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + ) { + require(comment.isPartOfComment()) + if (comment.betweenCodeSiblings(ElementType.RPAR, THEN) || + comment.betweenCodeSiblings(THEN, ELSE_KEYWORD) || + comment.betweenCodeSiblings(ELSE_KEYWORD, ELSE) + ) { + emit(comment.startOffset, "No comment expected at this location", false) + } + } + private companion object { val IF_THEN_ELSE_ELEMENT_TYPES = listOf( diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleTest.kt index ba4dde057b..ddb475a0d6 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleTest.kt @@ -14,8 +14,6 @@ class IfElseWrappingRuleTest { provider = { IfElseWrappingRule() }, additionalRuleProviders = setOf( - // Required by visitor modifier - RuleProvider { DiscouragedCommentLocationRule() }, // Keep formatted code readable RuleProvider { IndentationRule() }, ), @@ -238,4 +236,286 @@ class IfElseWrappingRuleTest { @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") ifElseWrappingRuleAssertThat(code).hasNoLintViolations() } + + @Nested + inner class `Given a comment between IF CONDITION and THEN` { + @Test + fun `Given EOL comment on same line as CONDITION`() { + val code = + """ + fun foo() { + if (true) // some comment + bar() + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(2, 15, "No comment expected at this location") + } + + @Test + fun `Given EOL comment on line below CONDITION`() { + val code = + """ + fun foo() { + if (true) + // some comment + bar() + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(3, 9, "No comment expected at this location") + } + + @Test + fun `Given block comment on same line as CONDITION`() { + val code = + """ + fun foo() { + if (true) /* some comment */ + bar() + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(2, 15, "No comment expected at this location") + } + + @Test + fun `Given block comment on line below CONDITION`() { + val code = + """ + fun foo() { + if (true) + /* some comment */ + bar() + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(3, 9, "No comment expected at this location") + } + + @Test + fun `Given KDOC on same line as CONDITION`() { + val code = + """ + fun foo() { + if (true) /** some comment */ + bar() + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(2, 15, "No comment expected at this location") + } + + @Test + fun `Given KDOC on line below CONDITION`() { + val code = + """ + fun foo() { + if (true) + /** some comment */ + bar() + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(3, 9, "No comment expected at this location") + } + } + + @Nested + inner class `Given a comment between THEN and ELSE` { + @Test + fun `Given EOL comment on same line as THEN`() { + val code = + """ + fun foobar() { + if (true) + foo() // some comment + else { + bar() + } + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(3, 15, "No comment expected at this location") + } + + @Test + fun `Given EOL comment on line below THEN`() { + val code = + """ + fun foobar() { + if (true) + foo() + // some comment + else { + bar() + } + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(4, 5, "No comment expected at this location") + } + + @Test + fun `Given block comment on same line as THEN`() { + val code = + """ + fun foobar() { + if (true) + foo() /* some comment */ + else { + bar() + } + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(3, 15, "No comment expected at this location") + } + + @Test + fun `Given block comment on line below THEN`() { + val code = + """ + fun foobar() { + if (true) + foo() + /* some comment */ + else { + bar() + } + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(4, 5, "No comment expected at this location") + } + + @Test + fun `Given KDOC on same line as THEN`() { + val code = + """ + fun foobar() { + if (true) + foo() /** some comment */ + else { + bar() + } + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(3, 15, "No comment expected at this location") + } + + @Test + fun `Given KDOC on line below THEN`() { + val code = + """ + fun foobar() { + if (true) + foo() + /** some comment */ + else { + bar() + } + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(4, 5, "No comment expected at this location") + } + } + + @Nested + inner class `Given a comment between ELSE KEYWORD and ELSE block` { + @Test + fun `Given EOL comment on same line as THEN`() { + val code = + """ + fun foobar() { + if (true) + foo() + else // some comment + bar() + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(4, 10, "No comment expected at this location") + } + + @Test + fun `Given EOL comment on line below THEN`() { + val code = + """ + fun foobar() { + if (true) + foo() + else + // some comment + bar() + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(5, 5, "No comment expected at this location") + } + + @Test + fun `Given block comment on same line as THEN`() { + val code = + """ + fun foobar() { + if (true) + foo() + else /* some comment */ + bar() + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(4, 10, "No comment expected at this location") + } + + @Test + fun `Given block comment on line below THEN`() { + val code = + """ + fun foobar() { + if (true) + foo() + else + /* some comment */ + bar() + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(5, 5, "No comment expected at this location") + } + + @Test + fun `Given KDOC on same line as THEN`() { + val code = + """ + fun foobar() { + if (true) + foo() + else /** some comment */ + bar() + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(4, 10, "No comment expected at this location") + } + + @Test + fun `Given KDOC on line below THEN`() { + val code = + """ + fun foobar() { + if (true) + foo() + else + /** some comment */ + bar() + } + """.trimIndent() + ifElseWrappingRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(5, 5, "No comment expected at this location") + } + } } From e6900276db0ae9406fb551a12fad90b5d55f06e7 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Sun, 19 Nov 2023 19:37:46 +0100 Subject: [PATCH 3/7] Add missing tests --- .../rules/DiscouragedCommentLocationRule.kt | 2 +- .../DiscouragedCommentLocationRuleTest.kt | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt index c4424bcee6..9ff816c794 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt @@ -133,7 +133,7 @@ public class DiscouragedCommentLocationRule : StandardRule("discouraged-comment- } else { emit( node.startOffset, - "A KDoc is not allowed on a '${node.treeParentElementTypeName()}", + "A KDoc is not allowed inside a '${node.treeParentElementTypeName()}'", false, ) } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRuleTest.kt index 90335c5e3c..bc2edcbd7c 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRuleTest.kt @@ -606,6 +606,39 @@ class DiscouragedCommentLocationRuleTest { @Nested inner class `Given a type parameter list ast node` { + @Test + fun `Given a kdoc inside a type parameter`() { + val code = + """ + class Foo + """.trimIndent() + discouragedCommentLocationRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(1, 14, "A KDoc is not allowed inside a 'type_parameter'") + } + + @Test + fun `Given a block comment inside a type parameter`() { + val code = + """ + class Foo + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + discouragedCommentLocationRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(1, 14, "A (block or EOL) comment inside or on same line after a 'type_parameter' is not allowed. It may be placed on a separate line above.") + } + + @Test + fun `Given an EOL comment inside a type parameter`() { + val code = + """ + class Foo + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + discouragedCommentLocationRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(1, 14, "A (block or EOL) comment inside or on same line after a 'type_parameter' is not allowed. It may be placed on a separate line above.") + } + @Test fun `Given a kdoc as child of type parameter list`() { val code = @@ -683,6 +716,39 @@ class DiscouragedCommentLocationRuleTest { @Nested inner class `Given a type argument list ast node` { + @Test + fun `Given a kdoc inside a type projection`() { + val code = + """ + fun Foo.foo() {} + """.trimIndent() + discouragedCommentLocationRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(1, 13, "A KDoc is not allowed inside a 'type_projection'") + } + + @Test + fun `Given a block comment inside a type projection`() { + val code = + """ + fun Foo.foo() {} + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + discouragedCommentLocationRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(1, 13, "A (block or EOL) comment inside or on same line after a 'type_projection' is not allowed. It may be placed on a separate line above.") + } + + @Test + fun `Given a EOL comment inside type projection`() { + val code = + """ + fun Foo.foo() {} + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + discouragedCommentLocationRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(1, 13, "A (block or EOL) comment inside or on same line after a 'type_projection' is not allowed. It may be placed on a separate line above.") + } + @Test fun `Given a kdoc as child of type argument list`() { val code = From 0b79a7528cb0fd13b7b2081bad414c49ad2a4a5c Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Tue, 21 Nov 2023 15:22:35 +0100 Subject: [PATCH 4/7] Remove dependency on `discouraged-comment-location` rule The `discouraged-comment-location` rule contained several unrelated comment locations. When disabling the rule, ktlint might throw an exception when another rule depends on it. It is understandable that users do not instantly understand the relation between for example the `if-else-wrapping` and the `discouraged-comment-location` rule. So rules now have to emit warnings themselves whenever comments can not be processed. For the `type-argument/type-argument-list`, `type-projection/type-parameter-list`, `value-argument/value-argument-list`, `value-parameter/value-parameter-list` new rules have been created to disallow comments. As those rules are more specific, it is more reasonable why specific rules depend on those rules. --- .../rules/KtlintSuppressionRuleTest.kt | 40 +++-- .../api/ktlint-ruleset-standard.api | 36 ++++ .../standard/StandardRuleSetProvider.kt | 8 + .../rules/ArgumentListWrappingRule.kt | 22 ++- .../standard/rules/ClassSignatureRule.kt | 9 +- .../rules/DiscouragedCommentLocationRule.kt | 15 +- .../standard/rules/FunctionSignatureRule.kt | 12 ++ .../standard/rules/TypeArgumentCommentRule.kt | 83 +++++++++ .../rules/TypeParameterCommentRule.kt | 83 +++++++++ .../rules/ValueArgumentCommentRule.kt | 89 ++++++++++ .../rules/ValueParameterCommentRule.kt | 119 +++++++++++++ .../rules/ArgumentListWrappingRuleTest.kt | 32 +++- .../rules/BinaryExpressionWrappingRuleTest.kt | 9 +- .../BlankLineBeforeDeclarationRuleTest.kt | 10 +- .../rules/ChainMethodContinuationRuleTest.kt | 14 +- .../standard/rules/ClassSignatureRuleTest.kt | 12 +- .../standard/rules/FunctionLiteralRuleTest.kt | 4 +- .../rules/FunctionSignatureRuleTest.kt | 17 +- .../standard/rules/IfElseWrappingRuleTest.kt | 18 +- .../rules/NoSingleLineBlockCommentRuleTest.kt | 11 +- .../rules/ParameterListWrappingRuleTest.kt | 16 +- .../rules/StringTemplateIndentRuleTest.kt | 18 +- .../rules/TrailingCommaOnCallSiteRuleTest.kt | 20 +-- .../TrailingCommaOnDeclarationSiteRuleTest.kt | 53 +++--- .../rules/TypeArgumentCommentRuleTest.kt | 98 +++++++++++ .../rules/TypeParameterCommentRuleTest.kt | 118 +++++++++++++ .../rules/ValueArgumentCommentRuleTest.kt | 144 ++++++++++++++++ .../rules/ValueParameterCommentRuleTest.kt | 160 ++++++++++++++++++ ktlint-test/api/ktlint-test.api | 7 + .../pinterest/ktlint/test/KtLintAssertThat.kt | 127 ++++++++++++-- 30 files changed, 1226 insertions(+), 178 deletions(-) create mode 100644 ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRule.kt create mode 100644 ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRule.kt create mode 100644 ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRule.kt create mode 100644 ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRule.kt create mode 100644 ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRuleTest.kt create mode 100644 ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRuleTest.kt create mode 100644 ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRuleTest.kt create mode 100644 ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRuleTest.kt diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRuleTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRuleTest.kt index 7bc5d684ea..1a22653e4b 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRuleTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRuleTest.kt @@ -2,11 +2,11 @@ package com.pinterest.ktlint.rule.engine.internal.rules import com.pinterest.ktlint.rule.engine.core.api.Rule import com.pinterest.ktlint.rule.engine.core.api.RuleId -import com.pinterest.ktlint.rule.engine.core.api.RuleProvider +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider import com.pinterest.ktlint.ruleset.standard.rules.ArgumentListWrappingRule import com.pinterest.ktlint.test.KtLintAssertThat.Companion.EOL_CHAR import com.pinterest.ktlint.test.KtLintAssertThat.Companion.MAX_LINE_LENGTH_MARKER -import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder import com.pinterest.ktlint.test.KtlintDocumentationTest import com.pinterest.ktlint.test.LintViolation import org.junit.jupiter.api.Nested @@ -17,25 +17,21 @@ import org.junit.jupiter.params.provider.ValueSource class KtlintSuppressionRuleTest { private val ktlintSuppressionRuleAssertThat = - assertThatRule( - provider = { KtlintSuppressionRule(emptyList()) }, - additionalRuleProviders = - setOf( - // Create a dummy rule for each rule id that is used in a ktlint directive or suppression in the tests in this - // class. If no rule provider is added for the rule id, a lint violation is thrown which will bloat the tests too - // much. - // - // Ids of real rules used but for which the real implementation is unwanted as it would modify the formatted code - RuleProvider { DummyRule("standard:no-wildcard-imports") }, - RuleProvider { DummyRule("standard:no-multi-spaces") }, - RuleProvider { DummyRule("standard:max-line-length") }, - RuleProvider { DummyRule("standard:package-name") }, - // Ids of fake rules in a custom and the standard rule set - RuleProvider { DummyRule("custom:foo") }, - RuleProvider { DummyRule("standard:bar") }, - RuleProvider { DummyRule("standard:foo") }, - ), - ) + assertThatRuleBuilder { KtlintSuppressionRule(emptyList()) } + // Create a dummy rule for each rule id that is used in a ktlint directive or suppression in the tests in this + // class. If no rule provider is added for the rule id, a lint violation is thrown which will bloat the tests too + // much. + // + // Ids of real rules used but for which the real implementation is unwanted as it would modify the formatted code + .addAdditionalRuleProvider { DummyRule("standard:no-wildcard-imports") } + .addAdditionalRuleProvider { DummyRule("standard:no-multi-spaces") } + .addAdditionalRuleProvider { DummyRule("standard:max-line-length") } + .addAdditionalRuleProvider { DummyRule("standard:package-name") } + // Ids of fake rules in a custom and the standard rule set + .addAdditionalRuleProvider { DummyRule("custom:foo") } + .addAdditionalRuleProvider { DummyRule("standard:bar") } + .addAdditionalRuleProvider { DummyRule("standard:foo") } + .build() @Nested inner class `Given a suppression annotation missing the rule set id prefix` { @@ -872,6 +868,7 @@ class KtlintSuppressionRuleTest { """.trimIndent() ktlintSuppressionRuleAssertThat(code) .addAdditionalRuleProvider { ArgumentListWrappingRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) .hasLintViolations( LintViolation(1, 4, "Directive 'ktlint-disable' is deprecated. Replace with @Suppress annotation"), LintViolation(4, 54, "Directive 'ktlint-disable' is deprecated. Replace with @Suppress annotation"), @@ -1359,6 +1356,7 @@ class KtlintSuppressionRuleTest { ktlintSuppressionRuleAssertThat(code) .setMaxLineLength() .addAdditionalRuleProvider { ArgumentListWrappingRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) .hasLintViolations( LintViolation(8, 12, "Directive 'ktlint-disable' is deprecated. Replace with @Suppress annotation"), LintViolation(10, 12, "Directive 'ktlint-enable' is obsolete after migrating to suppress annotations"), diff --git a/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api b/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api index 85d22eaada..9e028878fc 100644 --- a/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api +++ b/ktlint-ruleset-standard/api/ktlint-ruleset-standard.api @@ -850,6 +850,15 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/TryCatchFinallySp public static final fun getTRY_CATCH_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId; } +public final class com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRule : com/pinterest/ktlint/ruleset/standard/StandardRule { + public fun ()V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V +} + +public final class com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRuleKt { + public static final fun getTYPE_ARGUMENT_COMMENT_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId; +} + public final class com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V @@ -860,6 +869,15 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentListS public static final fun getTYPE_ARGUMENT_LIST_SPACING_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId; } +public final class com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRule : com/pinterest/ktlint/ruleset/standard/StandardRule { + public fun ()V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V +} + +public final class com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRuleKt { + public static final fun getTYPE_PARAMETER_COMMENT_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId; +} + public final class com/pinterest/ktlint/ruleset/standard/rules/TypeParameterListSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V @@ -879,6 +897,24 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/UnnecessaryParent public static final fun getUNNECESSARY_PARENTHESES_BEFORE_TRAILING_LAMBDA_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId; } +public final class com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRule : com/pinterest/ktlint/ruleset/standard/StandardRule { + public fun ()V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V +} + +public final class com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRuleKt { + public static final fun getVALUE_ARGUMENT_COMMENT_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId; +} + +public final class com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRule : com/pinterest/ktlint/ruleset/standard/StandardRule { + public fun ()V + public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V +} + +public final class com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRuleKt { + public static final fun getVALUE_PARAMETER_COMMENT_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId; +} + public final class com/pinterest/ktlint/ruleset/standard/rules/WrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule { public fun ()V public fun afterVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V 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 2ee08d6fa8..c0bcf400e3 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 @@ -86,9 +86,13 @@ import com.pinterest.ktlint.ruleset.standard.rules.StringTemplateRule import com.pinterest.ktlint.ruleset.standard.rules.TrailingCommaOnCallSiteRule import com.pinterest.ktlint.ruleset.standard.rules.TrailingCommaOnDeclarationSiteRule import com.pinterest.ktlint.ruleset.standard.rules.TryCatchFinallySpacingRule +import com.pinterest.ktlint.ruleset.standard.rules.TypeArgumentCommentRule import com.pinterest.ktlint.ruleset.standard.rules.TypeArgumentListSpacingRule +import com.pinterest.ktlint.ruleset.standard.rules.TypeParameterCommentRule import com.pinterest.ktlint.ruleset.standard.rules.TypeParameterListSpacingRule import com.pinterest.ktlint.ruleset.standard.rules.UnnecessaryParenthesesBeforeTrailingLambdaRule +import com.pinterest.ktlint.ruleset.standard.rules.ValueArgumentCommentRule +import com.pinterest.ktlint.ruleset.standard.rules.ValueParameterCommentRule import com.pinterest.ktlint.ruleset.standard.rules.WrappingRule public class StandardRuleSetProvider : RuleSetProviderV3(RuleSetId.STANDARD) { @@ -177,8 +181,12 @@ public class StandardRuleSetProvider : RuleSetProviderV3(RuleSetId.STANDARD) { RuleProvider { TrailingCommaOnCallSiteRule() }, RuleProvider { TrailingCommaOnDeclarationSiteRule() }, RuleProvider { TryCatchFinallySpacingRule() }, + RuleProvider { TypeArgumentCommentRule() }, RuleProvider { TypeArgumentListSpacingRule() }, + RuleProvider { TypeParameterCommentRule() }, RuleProvider { TypeParameterListSpacingRule() }, + RuleProvider { ValueArgumentCommentRule() }, + RuleProvider { ValueParameterCommentRule() }, RuleProvider { UnnecessaryParenthesesBeforeTrailingLambdaRule() }, RuleProvider { WrappingRule() }, ) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt index 59294c2237..8e02ed1609 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRule.kt @@ -5,6 +5,8 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.ELSE import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT_LIST import com.pinterest.ktlint.rule.engine.core.api.IndentConfig +import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule +import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint @@ -47,15 +49,17 @@ public class ArgumentListWrappingRule : id = "argument-list-wrapping", visitorModifiers = setOf( - // ArgumentListWrapping should only be used in case the max_line_length is still violated after running rules below: - VisitorModifier.RunAfterRule( - ruleId = WRAPPING_RULE_ID, - mode = REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED, - ), - VisitorModifier.RunAfterRule( - ruleId = CLASS_SIGNATURE_RULE_ID, - mode = REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED, - ), + // Disallow comments at unexpected locations in the value parameter list + // fun foo( + // bar /* some comment */: Bar + // ) + // or + // class Foo( + // bar /* some comment */: Bar + // ) + RunAfterRule(VALUE_ARGUMENT_COMMENT_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED), + RunAfterRule(WRAPPING_RULE_ID, REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED), + RunAfterRule(CLASS_SIGNATURE_RULE_ID, REGARDLESS_WHETHER_RUN_AFTER_RULE_IS_LOADED_OR_DISABLED), ), usesEditorConfigProperties = setOf( diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRule.kt index 5b4d8d1c18..9f3a32e8b3 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRule.kt @@ -63,9 +63,16 @@ public class ClassSignatureRule : id = "class-signature", visitorModifiers = setOf( + // Disallow comments at unexpected locations in the type parameter list + // class Foo + RunAfterRule(TYPE_PARAMETER_COMMENT_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED), + // Disallow comments at unexpected locations in the value parameter list + // class Foo( + // bar /* some comment */: Bar + // ) + RunAfterRule(VALUE_PARAMETER_COMMENT_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED), // Run after wrapping and spacing rules RunAsLateAsPossible, - RunAfterRule(DISCOURAGED_COMMENT_LOCATION_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED), ), usesEditorConfigProperties = setOf( diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt index 9ff816c794..2e982cfabb 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt @@ -88,19 +88,7 @@ public class DiscouragedCommentLocationRule : StandardRule("discouraged-comment- EOL_COMMENT, BLOCK_COMMENT -> { // Disallow a comment inside a VALUE_PARAMETER. Note that an EOL comment which is placed on a separate line is a direct // child of the list. - // class Foo( - // val bar: - // // some comment - // Bar, - // ) - // or - // class Foo( - // val bar: Bar // some comment - // ) - // Although the last example looks ok it is not accepted because it can mess up a lot of things when trailing commas are - // enabled and/or multiple arguments are used in the class. - // It seems not to be possible to create a comment in a VALUE_ARGUMENT, TYPE_PROJECTION or TYPE_PARAMETER as a direct child - // of that type. However, if it would be possible, it is ok to emit the error analog to the VALUE_ARGUMENT + // class Foo emit( node.startOffset, "A (block or EOL) comment inside or on same line after a '${node.treeParentElementTypeName()}' is not allowed. It " + @@ -180,4 +168,5 @@ public class DiscouragedCommentLocationRule : StandardRule("discouraged-comment- } } +@Deprecated("Marked for removal in ktlint 2.0. See https://github.com/pinterest/ktlint/issues/2367") public val DISCOURAGED_COMMENT_LOCATION_RULE_ID: RuleId = DiscouragedCommentLocationRule().ruleId diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt index 85fe956cb1..b4a496476b 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRule.kt @@ -18,6 +18,7 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER_LIS import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE import com.pinterest.ktlint.rule.engine.core.api.IndentConfig import com.pinterest.ktlint.rule.engine.core.api.IndentConfig.Companion.DEFAULT_INDENT_CONFIG +import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL @@ -60,6 +61,17 @@ public class FunctionSignatureRule : id = "function-signature", visitorModifiers = setOf( + // Disallow comments at unexpected locations in the type parameter list + // fun Foo.foo() {} + VisitorModifier.RunAfterRule(TYPE_PARAMETER_COMMENT_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED), + // Disallow comments at unexpected locations in the type argument list + // fun Foo.foo() {} + VisitorModifier.RunAfterRule(TYPE_ARGUMENT_COMMENT_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED), + // Disallow comments at unexpected locations in the value parameter list + // fun foo( + // bar /* some comment */: Bar + // ) + VisitorModifier.RunAfterRule(VALUE_PARAMETER_COMMENT_RULE_ID, ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED), // Run after wrapping and spacing rules VisitorModifier.RunAsLateAsPossible, ), diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRule.kt new file mode 100644 index 0000000000..7683824ace --- /dev/null +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRule.kt @@ -0,0 +1,83 @@ +package com.pinterest.ktlint.ruleset.standard.rules + +import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK_COMMENT +import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT +import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC +import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_ARGUMENT_LIST +import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_PROJECTION +import com.pinterest.ktlint.rule.engine.core.api.RuleId +import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint +import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment +import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline +import com.pinterest.ktlint.rule.engine.core.api.prevLeaf +import com.pinterest.ktlint.ruleset.standard.StandardRule +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet + +/** + * The AST allows comments to be placed anywhere. This however can lead to code which is unnecessarily hard to read. Or, it makes + * development of rules unnecessarily complex. + * + * This rule is based on the DiscouragedCommentLocationRule which is split in several distinct rules. In this way it becomes more clear why + * another rule depends on this rule. + */ +@SinceKtlint("1.1", STABLE) +public class TypeArgumentCommentRule : StandardRule("type-argument-comment") { + override fun beforeVisitChildNodes( + node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + ) { + if (node.isPartOfComment() && node.treeParent.elementType in typeArgumentTokenSet) { + when (node.elementType) { + EOL_COMMENT, BLOCK_COMMENT -> { + if (node.treeParent.elementType == TYPE_PROJECTION) { + // Disallow: + // fun Foo.foo() {} + // or + // fun Foo.foo() {} + emit( + node.startOffset, + "A (block or EOL) comment inside or on same line after a 'type_projection' is not allowed. It may be placed " + + "on a separate line above.", + false, + ) + } else if (node.treeParent.elementType == TYPE_ARGUMENT_LIST) { + if (node.prevLeaf().isWhiteSpaceWithNewline()) { + // Allow: + // fun Foo< + // /* some comment */ + // out Any + // >.foo() {} + } else { + // Disallow + // fun Foo< + // val bar1: Bar, // some comment 1 + // // some comment 2 + // val bar2: Bar, + // >.foo() {} + // It is not clear whether "some comment 2" belongs to bar1 as a continuation of "some comment 1" or that it belongs to + // bar2. Note both comments are direct children of the type_argument_list. + emit( + node.startOffset, + "A comment in a 'type_argument_list' is only allowed when placed on a separate line", + false, + ) + } + } + } + + KDOC -> { + emit(node.startOffset, "A KDoc is not allowed inside a 'type_argument_list'", false) + } + } + } + } + + private companion object { + val typeArgumentTokenSet = TokenSet.create(TYPE_PROJECTION, TYPE_ARGUMENT_LIST) + } +} + +public val TYPE_ARGUMENT_COMMENT_RULE_ID: RuleId = TypeArgumentCommentRule().ruleId diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRule.kt new file mode 100644 index 0000000000..e41fb751e1 --- /dev/null +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRule.kt @@ -0,0 +1,83 @@ +package com.pinterest.ktlint.ruleset.standard.rules + +import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK_COMMENT +import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT +import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC +import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_PARAMETER +import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_PARAMETER_LIST +import com.pinterest.ktlint.rule.engine.core.api.RuleId +import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint +import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment +import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline +import com.pinterest.ktlint.rule.engine.core.api.prevLeaf +import com.pinterest.ktlint.ruleset.standard.StandardRule +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet + +/** + * The AST allows comments to be placed anywhere. This however can lead to code which is unnecessarily hard to read. Or, it makes + * development of rules unnecessarily complex. + * + * This rule is based on the DiscouragedCommentLocationRule which is split in several distinct rules. In this way it becomes more clear why + * another rule depends on this rule. + */ +@SinceKtlint("1.1", STABLE) +public class TypeParameterCommentRule : StandardRule("type-parameter-comment") { + override fun beforeVisitChildNodes( + node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + ) { + if (node.isPartOfComment() && node.treeParent.elementType in typeParameterTokenSet) { + when (node.elementType) { + EOL_COMMENT, BLOCK_COMMENT -> { + if (node.treeParent.elementType == TYPE_PARAMETER) { + // Disallow: + // class Foo + // or + // class Foo + emit( + node.startOffset, + "A (block or EOL) comment inside or on same line after a 'type_parameter' is not allowed. It may be placed " + + "on a separate line above.", + false, + ) + } else if (node.treeParent.elementType == TYPE_PARAMETER_LIST) { + if (node.prevLeaf().isWhiteSpaceWithNewline()) { + // Allow + // class Foo< + // /* some comment */ + // in Bar + // > + } else { + // Disallow + // class Foo< + // in Bar, // some comment 1 + // // some comment 2 + // in Bar2, + // ) + // It is not clear whether "some comment 2" belongs to bar1 as a continuation of "some comment 1" or that it + // belongs to bar2. Note both comments are direct children of the value_argument_list. + emit( + node.startOffset, + "A comment in a 'value_argument_list' is only allowed when placed on a separate line", + false, + ) + } + } + } + + KDOC -> { + emit(node.startOffset, "A KDoc is not allowed inside a 'type_parameter_list'", false) + } + } + } + } + + private companion object { + val typeParameterTokenSet = TokenSet.create(TYPE_PARAMETER, TYPE_PARAMETER_LIST) + } +} + +public val TYPE_PARAMETER_COMMENT_RULE_ID: RuleId = TypeParameterCommentRule().ruleId diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRule.kt new file mode 100644 index 0000000000..b770417287 --- /dev/null +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRule.kt @@ -0,0 +1,89 @@ +package com.pinterest.ktlint.ruleset.standard.rules + +import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK_COMMENT +import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT +import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC +import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT +import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT_LIST +import com.pinterest.ktlint.rule.engine.core.api.RuleId +import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint +import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment +import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline +import com.pinterest.ktlint.rule.engine.core.api.prevLeaf +import com.pinterest.ktlint.ruleset.standard.StandardRule +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet + +/** + * The AST allows comments to be placed anywhere. This however can lead to code which is unnecessarily hard to read. Or, it makes + * development of rules unnecessarily complex. + * + * This rule is based on the DiscouragedCommentLocationRule which is split in several distinct rules. In this way it becomes more clear why + * another rule depends on this rule. + */ +@SinceKtlint("1.1", STABLE) +public class ValueArgumentCommentRule : StandardRule("value-argument-comment") { + override fun beforeVisitChildNodes( + node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + ) { + if (node.isPartOfComment() && node.treeParent.elementType in valueArgumentTokenSet) { + when (node.elementType) { + EOL_COMMENT, BLOCK_COMMENT -> { + if (node.treeParent.elementType == VALUE_ARGUMENT) { + // Disallow: + // val foo = foo( + // bar /* some comment */ = "bar" + // ) + // or + // val foo = foo( + // bar = + // // some comment + // "bar" + // ) + emit( + node.startOffset, + "A (block or EOL) comment inside or on same line after a 'value_argument' is not allowed. It may be placed " + + "on a separate line above.", + false, + ) + } else if (node.treeParent.elementType == VALUE_ARGUMENT_LIST) { + if (node.prevLeaf().isWhiteSpaceWithNewline()) { + // Allow: + // val foo = foo( + // // some comment + // bar = "bar" + // ) + } else { + // Disallow + // class Foo( + // val bar1: Bar, // some comment 1 + // // some comment 2 + // val bar2: Bar, + // ) + // It is not clear whether "some comment 2" belongs to bar1 as a continuation of "some comment 1" or that it belongs to + // bar2. Note both comments are direct children of the value_argument_list. + emit( + node.startOffset, + "A comment in a 'value_argument_list' is only allowed when placed on a separate line", + false, + ) + } + } + } + + KDOC -> { + emit(node.startOffset, "A KDoc is not allowed inside a 'value_argument_list'", false) + } + } + } + } + + private companion object { + val valueArgumentTokenSet = TokenSet.create(VALUE_ARGUMENT, VALUE_ARGUMENT_LIST) + } +} + +public val VALUE_ARGUMENT_COMMENT_RULE_ID: RuleId = ValueArgumentCommentRule().ruleId diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRule.kt new file mode 100644 index 0000000000..2ba051fa04 --- /dev/null +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRule.kt @@ -0,0 +1,119 @@ +package com.pinterest.ktlint.ruleset.standard.rules + +import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK_COMMENT +import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT +import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC +import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER +import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER_LIST +import com.pinterest.ktlint.rule.engine.core.api.RuleId +import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint +import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE +import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment +import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline +import com.pinterest.ktlint.rule.engine.core.api.prevLeaf +import com.pinterest.ktlint.ruleset.standard.StandardRule +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet + +/** + * The AST allows comments to be placed anywhere. This however can lead to code which is unnecessarily hard to read. Or, it makes + * development of rules unnecessarily complex. + * + * This rule is based on the DiscouragedCommentLocationRule which is split in several distinct rules. In this way it becomes more clear why + * another rule depends on this rule. + */ +@SinceKtlint("1.1", STABLE) +public class ValueParameterCommentRule : StandardRule("value-parameter-comment") { + override fun beforeVisitChildNodes( + node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + ) { + if (node.isPartOfComment() && node.treeParent.elementType in valueParameterTokenSet) { + when (node.elementType) { + EOL_COMMENT, BLOCK_COMMENT -> { + if (node.treeParent.elementType == VALUE_PARAMETER) { + // Disallow: + // class Foo( + // bar /* some comment */ = "bar" + // ) + // or + // class Foo( + // bar = + // // some comment + // "bar" + // ) + emit( + node.startOffset, + "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed " + + "on a separate line above.", + false, + ) + } else if (node.treeParent.elementType == VALUE_PARAMETER_LIST) { + if (node.prevLeaf().isWhiteSpaceWithNewline()) { + // Allow: + // class Foo( + // // some comment + // bar = "bar" + // ) + } else { + // Disallow + // class Foo( + // val bar1: Bar, // some comment 1 + // // some comment 2 + // val bar2: Bar, + // ) + // It is not clear whether "some comment 2" belongs to bar1 as a continuation of "some comment 1" or that it belongs to + // bar2. Note both comments are direct children of the value_parameter_list. + emit( + node.startOffset, + "A comment in a 'value_parameter_list' is only allowed when placed on a separate line", + false, + ) + } + } + } + + KDOC -> { + if (node.treeParent.elementType == VALUE_PARAMETER) { + if (node == node.treeParent.firstChildNode) { + // Allow + // class Foo( + // /** some comment */ + // val bar: Bar, + // ) + } else { + // Disallow a kdoc inside a VALUE_PARAMETER. + // class Foo( + // val bar: + // /** some comment */ + // Bar, + // ) + // or + // class Foo( + // val bar: Bar /** some comment */ + // ) + emit( + node.startOffset, + "A kdoc in a 'value_parameter' is only allowed when placed on a new line before this element", + false, + ) + } + } else { + emit( + node.startOffset, + "A KDoc is not allowed inside a 'value_parameter_list' when not followed by a property", + false, + ) + } + } + } + } + } + + private companion object { + val valueParameterTokenSet = TokenSet.create(VALUE_PARAMETER, VALUE_PARAMETER_LIST) + } +} + +public val VALUE_PARAMETER_COMMENT_RULE_ID: RuleId = ValueParameterCommentRule().ruleId diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt index 14e714d4ba..b1e201278e 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt @@ -1,17 +1,21 @@ package com.pinterest.ktlint.ruleset.standard.rules import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PROPERTY +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider import com.pinterest.ktlint.ruleset.standard.rules.ClassSignatureRule.Companion.FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY import com.pinterest.ktlint.test.KtLintAssertThat.Companion.EOL_CHAR import com.pinterest.ktlint.test.KtLintAssertThat.Companion.MAX_LINE_LENGTH_MARKER -import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder import com.pinterest.ktlint.test.LintViolation import com.pinterest.ktlint.test.MULTILINE_STRING_QUOTE import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class ArgumentListWrappingRuleTest { - private val argumentListWrappingRuleAssertThat = assertThatRule { ArgumentListWrappingRule() } + private val argumentListWrappingRuleAssertThat = + assertThatRuleBuilder { ArgumentListWrappingRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) + .build() @Test fun `Given a function call and not all arguments are on the same line`() { @@ -886,7 +890,7 @@ class ArgumentListWrappingRuleTest { argumentListWrappingRuleAssertThat(code) .setMaxLineLength() .addAdditionalRuleProvider { ClassSignatureRule() } - .addAdditionalRuleProvider { DiscouragedCommentLocationRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) .withEditorConfigOverride(FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY to 4) .hasLintViolationForAdditionalRule(5, 37, "Super type should start on a newline") .hasLintViolations( @@ -895,4 +899,26 @@ class ArgumentListWrappingRuleTest { LintViolation(5, 88, "Missing newline before \")\""), ).isFormattedAs(formattedCode) } + + @Test + fun `Given a value argument with a disallowed comment`() { + val code = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + val foo = foo(bar = /* some disallowed comment location */ "bar") + """.trimIndent() + val formattedCode = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + val foo = foo( + bar = /* some disallowed comment location */ "bar" + ) + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + argumentListWrappingRuleAssertThat(code) + .setMaxLineLength() + .withEditorConfigOverride(FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY to 1) + .hasLintViolationForAdditionalRule(2, 21, "A (block or EOL) comment inside or on same line after a 'value_argument' is not allowed. It may be placed on a separate line above.", false) + .isFormattedAs(formattedCode) + } } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BinaryExpressionWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BinaryExpressionWrappingRuleTest.kt index 51483b8480..6a811593d9 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BinaryExpressionWrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BinaryExpressionWrappingRuleTest.kt @@ -1,14 +1,15 @@ package com.pinterest.ktlint.ruleset.standard.rules -import com.pinterest.ktlint.test.KtLintAssertThat +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider import com.pinterest.ktlint.test.KtLintAssertThat.Companion.EOL_CHAR import com.pinterest.ktlint.test.KtLintAssertThat.Companion.MAX_LINE_LENGTH_MARKER +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule import com.pinterest.ktlint.test.LintViolation import com.pinterest.ktlint.test.MULTILINE_STRING_QUOTE import org.junit.jupiter.api.Test class BinaryExpressionWrappingRuleTest { - private val binaryExpressionWrappingRuleAssertThat = KtLintAssertThat.assertThatRule { BinaryExpressionWrappingRule() } + private val binaryExpressionWrappingRuleAssertThat = assertThatRule { BinaryExpressionWrappingRule() } @Test fun `Given a property with a binary expression on same line as equals, and it exceeds the max line length then wrap before the expression`() { @@ -249,6 +250,7 @@ class BinaryExpressionWrappingRuleTest { binaryExpressionWrappingRuleAssertThat(code) .addAdditionalRuleProvider { ArgumentListWrappingRule() } .addAdditionalRuleProvider { WrappingRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) .setMaxLineLength() .hasLintViolations( // Although violations below are reported by the Linter, they will not be enforced by the formatter. After the @@ -285,6 +287,7 @@ class BinaryExpressionWrappingRuleTest { binaryExpressionWrappingRuleAssertThat(code) .setMaxLineLength() .addAdditionalRuleProvider { ArgumentListWrappingRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) .addAdditionalRuleProvider { WrappingRule() } // Although the argument-list-wrapping runs before binary-expression-wrapping, it may not wrap the argument values of a // function call in case that call is part of a binary expression. It might be better to break the line at the operation @@ -305,6 +308,7 @@ class BinaryExpressionWrappingRuleTest { .setMaxLineLength() .addAdditionalRuleProvider { ArgumentListWrappingRule() } .addAdditionalRuleProvider { WrappingRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) .hasLintViolationWithoutAutoCorrect(3, 1, "Line is exceeding max line length") } @@ -322,6 +326,7 @@ class BinaryExpressionWrappingRuleTest { .addAdditionalRuleProvider { ArgumentListWrappingRule() } .addAdditionalRuleProvider { WrappingRule() } .addAdditionalRuleProvider { MaxLineLengthRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) .hasLintViolationWithoutAutoCorrect(4, 1, "Line is exceeding max line length") } } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRuleTest.kt index bcfb78a219..180da58b9d 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/BlankLineBeforeDeclarationRuleTest.kt @@ -1,19 +1,13 @@ package com.pinterest.ktlint.ruleset.standard.rules -import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY -import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue -import com.pinterest.ktlint.test.KtLintAssertThat +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule import com.pinterest.ktlint.test.KtlintDocumentationTest import com.pinterest.ktlint.test.LintViolation import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class BlankLineBeforeDeclarationRuleTest { - private val blankLineBeforeDeclarationRuleAssertThat = - KtLintAssertThat.assertThatRule( - provider = { BlankLineBeforeDeclarationRule() }, - editorConfigProperties = setOf(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official), - ) + private val blankLineBeforeDeclarationRuleAssertThat = assertThatRule { BlankLineBeforeDeclarationRule() } @Test fun `Given some consecutive classes not separated by a blank line then insert a blank line in between`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRuleTest.kt index 88acf3afc1..f6d6b61245 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRuleTest.kt @@ -1,10 +1,10 @@ package com.pinterest.ktlint.ruleset.standard.rules -import com.pinterest.ktlint.rule.engine.core.api.RuleProvider +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider import com.pinterest.ktlint.ruleset.standard.rules.ChainMethodContinuationRule.Companion.FORCE_MULTILINE_WHEN_CHAIN_OPERATOR_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY import com.pinterest.ktlint.test.KtLintAssertThat.Companion.EOL_CHAR import com.pinterest.ktlint.test.KtLintAssertThat.Companion.MAX_LINE_LENGTH_MARKER -import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder import com.pinterest.ktlint.test.LintViolation import com.pinterest.ktlint.test.MULTILINE_STRING_QUOTE import com.pinterest.ktlint.test.replaceStringTemplatePlaceholder @@ -15,13 +15,9 @@ import org.junit.jupiter.params.provider.ValueSource class ChainMethodContinuationRuleTest { private val chainMethodContinuationRuleAssertThat = - assertThatRule( - provider = { ChainMethodContinuationRule() }, - additionalRuleProviders = - setOf( - RuleProvider { ArgumentListWrappingRule() }, - ), - ) + assertThatRuleBuilder { ChainMethodContinuationRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) + .build() @Test fun `Given that no maximum line length is set, and a single line method chain does not exceed the maximum number of chain operators then do not wrap`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRuleTest.kt index 5832378ea3..6a5f010470 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRuleTest.kt @@ -1,15 +1,15 @@ package com.pinterest.ktlint.ruleset.standard.rules -import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue.intellij_idea import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue.ktlint_official import com.pinterest.ktlint.rule.engine.core.api.editorconfig.ec4j.toPropertyWithValue +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider import com.pinterest.ktlint.ruleset.standard.rules.ClassSignatureRule.Companion.FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY import com.pinterest.ktlint.test.KtLintAssertThat.Companion.EOL_CHAR import com.pinterest.ktlint.test.KtLintAssertThat.Companion.MAX_LINE_LENGTH_MARKER -import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder import com.pinterest.ktlint.test.LintViolation import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType @@ -23,10 +23,9 @@ import org.junit.jupiter.params.provider.ValueSource class ClassSignatureRuleTest { private val classSignatureWrappingRuleAssertThat = - assertThatRule( - provider = { ClassSignatureRule() }, - additionalRuleProviders = setOf(RuleProvider { DiscouragedCommentLocationRule() }), - ) + assertThatRuleBuilder { ClassSignatureRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) + .build() @Nested inner class `Given a class with a body` { @@ -1555,6 +1554,7 @@ class ClassSignatureRuleTest { """.trimIndent() classSignatureWrappingRuleAssertThat(code) .setMaxLineLength() + .addAdditionalRuleProvider { ValueArgumentCommentRule() } .addAdditionalRuleProvider { ArgumentListWrappingRule() } .addAdditionalRuleProvider { WrappingRule() } .withEditorConfigOverride(FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY to 4) diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleTest.kt index 817dcd729a..c4667fa8d5 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleTest.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.ruleset.standard.rules +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider import com.pinterest.ktlint.test.KtLintAssertThat.Companion.EOL_CHAR import com.pinterest.ktlint.test.KtLintAssertThat.Companion.MAX_LINE_LENGTH_MARKER import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule @@ -434,8 +435,9 @@ class FunctionLiteralRuleTest { functionLiteralRuleAssertThat(code) .addAdditionalRuleProvider { MultilineExpressionWrappingRule() } .addAdditionalRuleProvider { ChainMethodContinuationRule() } - .addAdditionalRuleProvider { ArgumentListWrappingRule() } .addAdditionalRuleProvider { IndentationRule() } + .addAdditionalRuleProvider { ArgumentListWrappingRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) .setMaxLineLength() .hasLintViolations( LintViolation(2, 14, "Newline expected after opening brace"), diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRuleTest.kt index 1fa2ff0d0a..5fe31160b1 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRuleTest.kt @@ -4,13 +4,14 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERT import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue.ktlint_official import com.pinterest.ktlint.rule.engine.core.api.editorconfig.ec4j.toPropertyWithValue +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider import com.pinterest.ktlint.ruleset.standard.rules.FunctionSignatureRule.Companion.FORCE_MULTILINE_WHEN_PARAMETER_COUNT_GREATER_OR_EQUAL_THAN_PROPERTY import com.pinterest.ktlint.ruleset.standard.rules.FunctionSignatureRule.Companion.FUNCTION_BODY_EXPRESSION_WRAPPING_PROPERTY import com.pinterest.ktlint.ruleset.standard.rules.FunctionSignatureRule.FunctionBodyExpressionWrapping import com.pinterest.ktlint.ruleset.standard.rules.FunctionSignatureRule.FunctionBodyExpressionWrapping.default import com.pinterest.ktlint.test.KtLintAssertThat.Companion.EOL_CHAR import com.pinterest.ktlint.test.KtLintAssertThat.Companion.MAX_LINE_LENGTH_MARKER -import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder import com.pinterest.ktlint.test.LintViolation import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType @@ -22,7 +23,10 @@ import org.junit.jupiter.params.provider.CsvSource import org.junit.jupiter.params.provider.EnumSource class FunctionSignatureRuleTest { - private val functionSignatureWrappingRuleAssertThat = assertThatRule { FunctionSignatureRule() } + private val functionSignatureWrappingRuleAssertThat = + assertThatRuleBuilder { FunctionSignatureRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) + .build() @Test fun `Given a single line function signature which is smaller than or equal to the max line length, and the function is followed by a body block, then do no change the signature`() { @@ -219,9 +223,16 @@ class FunctionSignatureRuleTest { b: Any ): String = "f10" """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") functionSignatureWrappingRuleAssertThat(code) .setMaxLineLength() - .hasNoLintViolations() + .hasLintViolationsForAdditionalRule( + LintViolation(5, 17, "A comment in a 'value_parameter_list' is only allowed when placed on a separate line", false), + LintViolation(6, 18, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above.", false), + LintViolation(7, 19, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above.", false), + LintViolation(8, 23, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above.", false), + LintViolation(13, 13, "A comment in a 'value_parameter_list' is only allowed when placed on a separate line", false), + ).hasNoLintViolationsExceptInAdditionalRules() } @Test diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleTest.kt index ddb475a0d6..36881c679b 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleTest.kt @@ -1,24 +1,16 @@ package com.pinterest.ktlint.ruleset.standard.rules -import com.pinterest.ktlint.rule.engine.core.api.RuleProvider -import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY -import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue.ktlint_official -import com.pinterest.ktlint.test.KtLintAssertThat +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder import com.pinterest.ktlint.test.LintViolation import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class IfElseWrappingRuleTest { private val ifElseWrappingRuleAssertThat = - KtLintAssertThat.assertThatRule( - provider = { IfElseWrappingRule() }, - additionalRuleProviders = - setOf( - // Keep formatted code readable - RuleProvider { IndentationRule() }, - ), - editorConfigProperties = setOf(CODE_STYLE_PROPERTY to ktlint_official), - ) + assertThatRuleBuilder { IfElseWrappingRule() } + // Keep formatted code readable + .addAdditionalRuleProvider { IndentationRule() } + .build() @Test fun `Given a single line if statement without else then do not report a violation`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRuleTest.kt index ec88d2c7c6..16a4627b93 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRuleTest.kt @@ -1,19 +1,16 @@ package com.pinterest.ktlint.ruleset.standard.rules -import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue -import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class NoSingleLineBlockCommentRuleTest { private val noSingleLineBlockCommentRuleAssertThat = - assertThatRule( - provider = { NoSingleLineBlockCommentRule() }, - additionalRuleProviders = setOf(RuleProvider { CommentWrappingRule() }), - editorConfigProperties = setOf(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official), - ) + assertThatRuleBuilder { NoSingleLineBlockCommentRule() } + .addAdditionalRuleProvider { CommentWrappingRule() } + .build() @Test fun `Given a single line block comment then replace it with an EOL comment`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleTest.kt index 5bdb043133..6c11bcd105 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleTest.kt @@ -1,12 +1,11 @@ package com.pinterest.ktlint.ruleset.standard.rules -import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue.ktlint_official import com.pinterest.ktlint.test.KtLintAssertThat.Companion.EOL_CHAR import com.pinterest.ktlint.test.KtLintAssertThat.Companion.MAX_LINE_LENGTH_MARKER -import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder import com.pinterest.ktlint.test.LintViolation import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test @@ -15,15 +14,10 @@ import org.junit.jupiter.params.provider.EnumSource class ParameterListWrappingRuleTest { private val parameterListWrappingRuleAssertThat = - assertThatRule( - provider = { ParameterListWrappingRule() }, - additionalRuleProviders = - setOf( - // Apply the IndentationRule always as additional rule, so that the formattedCode in the unit test looks - // correct. - RuleProvider { IndentationRule() }, - ), - ) + assertThatRuleBuilder { ParameterListWrappingRule() } + // Keep formatted code readable + .addAdditionalRuleProvider { IndentationRule() } + .build() @Test fun `Given a class with parameters on multiple lines then put each parameter and closing parenthesis on a separate line`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt index 69b8b2372a..a83e9bf4b4 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt @@ -1,11 +1,9 @@ package com.pinterest.ktlint.ruleset.standard.rules import com.pinterest.ktlint.rule.engine.core.api.IndentConfig -import com.pinterest.ktlint.rule.engine.core.api.RuleProvider -import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CODE_STYLE_PROPERTY -import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleValue import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_STYLE_PROPERTY -import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder import com.pinterest.ktlint.test.LintViolation import com.pinterest.ktlint.test.MULTILINE_STRING_QUOTE import com.pinterest.ktlint.test.TAB @@ -14,15 +12,9 @@ import org.junit.jupiter.api.Test class StringTemplateIndentRuleTest { private val stringTemplateIndentRuleAssertThat = - assertThatRule( - provider = { StringTemplateIndentRule() }, - additionalRuleProviders = - setOf( - RuleProvider { MultilineExpressionWrappingRule() }, - RuleProvider { IndentationRule() }, - ), - editorConfigProperties = setOf(CODE_STYLE_PROPERTY to CodeStyleValue.ktlint_official), - ) + assertThatRuleBuilder { StringTemplateIndentRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) + .build() @Test fun `Do not move a multiline string literal after return statement to a new line as that results in a compilation error`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRuleTest.kt index ab974300b3..c7efd07216 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRuleTest.kt @@ -1,25 +1,19 @@ package com.pinterest.ktlint.ruleset.standard.rules -import com.pinterest.ktlint.rule.engine.core.api.RuleProvider +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider import com.pinterest.ktlint.ruleset.standard.rules.TrailingCommaOnCallSiteRule.Companion.TRAILING_COMMA_ON_CALL_SITE_PROPERTY -import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder import com.pinterest.ktlint.test.LintViolation import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class TrailingCommaOnCallSiteRuleTest { private val trailingCommaOnCallSiteRuleAssertThat = - assertThatRule( - provider = { TrailingCommaOnCallSiteRule() }, - additionalRuleProviders = - setOf( - // WrappingRule must be loaded in order to run TrailingCommaOnCallSiteRule - RuleProvider { WrappingRule() }, - // Apply the IndentationRule always as additional rule, so that the formattedCode in the unit test looks - // correct. - RuleProvider { IndentationRule() }, - ), - ) + assertThatRuleBuilder { TrailingCommaOnCallSiteRule() } + // Keep formatted code readable + .addAdditionalRuleProvider { IndentationRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) + .build() @Test fun `Given property allow trailing comma on call site is not set then remove trailing commas`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRuleTest.kt index 5998046ed7..9bd78efbde 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRuleTest.kt @@ -1,25 +1,19 @@ package com.pinterest.ktlint.ruleset.standard.rules -import com.pinterest.ktlint.rule.engine.core.api.RuleProvider +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider import com.pinterest.ktlint.ruleset.standard.rules.TrailingCommaOnDeclarationSiteRule.Companion.TRAILING_COMMA_ON_DECLARATION_SITE_PROPERTY -import com.pinterest.ktlint.test.KtLintAssertThat +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder import com.pinterest.ktlint.test.LintViolation import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class TrailingCommaOnDeclarationSiteRuleTest { private val trailingCommaOnDeclarationSiteRuleAssertThat = - KtLintAssertThat.assertThatRule( - provider = { TrailingCommaOnDeclarationSiteRule() }, - additionalRuleProviders = - setOf( - // WrappingRule must be loaded in order to run TrailingCommaOnCallSiteRule - RuleProvider { WrappingRule() }, - // Apply the IndentationRule always as additional rule, so that the formattedCode in the unit test looks - // correct. - RuleProvider { IndentationRule() }, - ), - ) + assertThatRuleBuilder { TrailingCommaOnDeclarationSiteRule() } + // Unit tests becomes more readable when properly indented + .addAdditionalRuleProvider { IndentationRule() } + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) + .build() @Test fun `Given property allow trailing comma on declaration site is not set then remove trailing commas`() { @@ -936,14 +930,10 @@ class TrailingCommaOnDeclarationSiteRuleTest { // Ensure that AST before Enumeration class is changed by another rule before the running the // TrailingCommaOnDeclarationSiteRule val multiLineIfElseRuleAssertThat = - KtLintAssertThat.assertThatRule( - provider = { MultiLineIfElseRule() }, - additionalRuleProviders = - setOf( - RuleProvider { TrailingCommaOnDeclarationSiteRule() }, - RuleProvider { WrappingRule() }, - ), - ) + assertThatRuleBuilder { MultiLineIfElseRule() } + .addAdditionalRuleProvider { TrailingCommaOnDeclarationSiteRule() } + .addAdditionalRuleProvider { WrappingRule() } + .build() multiLineIfElseRuleAssertThat(code) .withEditorConfigOverride(TRAILING_COMMA_ON_DECLARATION_SITE_PROPERTY to true) @@ -1026,20 +1016,15 @@ class TrailingCommaOnDeclarationSiteRuleTest { } """.trimIndent() val noSemicolonsRuleAssertThat = - KtLintAssertThat.assertThatRule( - provider = { NoSemicolonsRule() }, - additionalRuleProviders = - setOf( - // WrappingRule must be loaded in order to run TrailingCommaOnCallSiteRule - RuleProvider { WrappingRule() }, - // Apply the IndentationRule always as additional rule, so that the formattedCode in the unit test looks - // correct. - RuleProvider { IndentationRule() }, - RuleProvider { TrailingCommaOnDeclarationSiteRule() }, - ), - ) + assertThatRuleBuilder { NoSemicolonsRule() } + // Keep formatted code readable + .addAdditionalRuleProvider { IndentationRule() } + .addAdditionalRuleProvider { TrailingCommaOnDeclarationSiteRule() } + .withEditorConfigOverride(TRAILING_COMMA_ON_DECLARATION_SITE_PROPERTY to true) + .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) + .build() + noSemicolonsRuleAssertThat(code) - .withEditorConfigOverride(TRAILING_COMMA_ON_DECLARATION_SITE_PROPERTY to true) .hasNoLintViolationsForRuleId(NO_SEMICOLONS_RULE_ID) .isFormattedAs(formattedCode) } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRuleTest.kt new file mode 100644 index 0000000000..c057061289 --- /dev/null +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeArgumentCommentRuleTest.kt @@ -0,0 +1,98 @@ +package com.pinterest.ktlint.ruleset.standard.rules + +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.test.LintViolation +import org.junit.jupiter.api.Test + +class TypeArgumentCommentRuleTest { + private val typeArgumentCommentRuleAssertThat = assertThatRule { TypeArgumentCommentRule() } + + @Test + fun `Given a kdoc inside a type projection`() { + val code = + """ + fun Foo.foo() {} + """.trimIndent() + typeArgumentCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(1, 13, "A KDoc is not allowed inside a 'type_argument_list'") + } + + @Test + fun `Given a block comment inside a type projection`() { + val code = + """ + fun Foo.foo() {} + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + typeArgumentCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(1, 13, "A (block or EOL) comment inside or on same line after a 'type_projection' is not allowed. It may be placed on a separate line above.") + } + + @Test + fun `Given a EOL comment inside type projection`() { + val code = + """ + fun Foo.foo() {} + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + typeArgumentCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(1, 13, "A (block or EOL) comment inside or on same line after a 'type_projection' is not allowed. It may be placed on a separate line above.") + } + + @Test + fun `Given a kdoc as child of type argument list`() { + val code = + """ + val fooBar: FooBar< + /** some comment */ + Foo, Bar> + """.trimIndent() + typeArgumentCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(2, 5, "A KDoc is not allowed inside a 'type_argument_list'") + } + + @Test + fun `Given a comment on separate line before type projection ast node`() { + val code = + """ + val fooBar1: FooBar< + // some comment + Foo, Bar> + val fooBar2: FooBar< + /* some comment */ + Foo, Bar> + """.trimIndent() + typeArgumentCommentRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a comment after a comma on the same line as an type projection ast node`() { + val code = + """ + val fooBar1: FooBar + val fooBar2: FooBar + """.trimIndent() + typeArgumentCommentRuleAssertThat(code) + .hasLintViolationsWithoutAutoCorrect( + LintViolation(1, 26, "A comment in a 'type_argument_list' is only allowed when placed on a separate line"), + LintViolation(3, 26, "A comment in a 'type_argument_list' is only allowed when placed on a separate line"), + ) + } + + @Test + fun `Given a comment as last node of type argument list`() { + val code = + """ + val fooBar: FooBar + val fooBar: FooBar + """.trimIndent() + typeArgumentCommentRuleAssertThat(code).hasNoLintViolations() + } +} diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRuleTest.kt new file mode 100644 index 0000000000..75b234f525 --- /dev/null +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TypeParameterCommentRuleTest.kt @@ -0,0 +1,118 @@ +package com.pinterest.ktlint.ruleset.standard.rules + +import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule +import com.pinterest.ktlint.test.LintViolation +import org.junit.jupiter.api.Test + +class TypeParameterCommentRuleTest { + private val typeParameterCommentRuleAssertThat = assertThatRule { TypeParameterCommentRule() } + + @Test + fun `Given a kdoc inside a type parameter`() { + val code = + """ + class Foo + """.trimIndent() + typeParameterCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(1, 14, "A KDoc is not allowed inside a 'type_parameter_list'") + } + + @Test + fun `Given a block comment inside a type parameter`() { + val code = + """ + class Foo + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + typeParameterCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(1, 14, "A (block or EOL) comment inside or on same line after a 'type_parameter' is not allowed. It may be placed on a separate line above.") + } + + @Test + fun `Given an EOL comment inside a type parameter`() { + val code = + """ + class Foo + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + typeParameterCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(1, 14, "A (block or EOL) comment inside or on same line after a 'type_parameter' is not allowed. It may be placed on a separate line above.") + } + + @Test + fun `Given a kdoc as child of type parameter list`() { + val code = + """ + class Foo< + /** some comment */ + Bar> + """.trimIndent() + typeParameterCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(2, 5, "A KDoc is not allowed inside a 'type_parameter_list'") + } + + @Test + fun `Given a comment on separate line before type parameter ast node`() { + val code = + """ + class Foo1< + // some comment + Bar> + class Foo2< + /* some comment */ + Bar> + """.trimIndent() + typeParameterCommentRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a comment after, but on the same line as an type parameter ast node`() { + val code = + """ + class Foo1< + Bar // some comment + > + class Foo2 + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + typeParameterCommentRuleAssertThat(code) + .hasLintViolationsWithoutAutoCorrect( + LintViolation(2, 9, "A comment in a 'value_argument_list' is only allowed when placed on a separate line"), + LintViolation(4, 16, "A comment in a 'value_argument_list' is only allowed when placed on a separate line"), + ) + } + + @Test + fun `Given a comment after a comma on the same line as an type parameter ast node`() { + val code = + """ + class FooBar1< + Foo, // some comment + Bar> + class FooBar2 + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + typeParameterCommentRuleAssertThat(code) + .hasLintViolationsWithoutAutoCorrect( + LintViolation(2, 10, "A comment in a 'value_argument_list' is only allowed when placed on a separate line"), + LintViolation(4, 20, "A comment in a 'value_argument_list' is only allowed when placed on a separate line"), + ) + } + + @Test + fun `Given a comment as last node of type parameter list`() { + val code = + """ + class FooBar1< + Foo + // some comment + > + class FooBar2< + Foo + /* some comment */ + > + """.trimIndent() + typeParameterCommentRuleAssertThat(code).hasNoLintViolations() + } +} diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRuleTest.kt new file mode 100644 index 0000000000..7bb695fae4 --- /dev/null +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueArgumentCommentRuleTest.kt @@ -0,0 +1,144 @@ +package com.pinterest.ktlint.ruleset.standard.rules + +import com.pinterest.ktlint.test.KtLintAssertThat +import com.pinterest.ktlint.test.LintViolation +import org.junit.jupiter.api.Test + +class ValueArgumentCommentRuleTest { + private val valueArgumentCommentRuleAssertThat = KtLintAssertThat.assertThatRule { ValueArgumentCommentRule() } + + @Test + fun `Given a kdoc inside a value argument`() { + val code = + """ + val foo = foo( + bar /** some comment */ = "bar" + ) + """.trimIndent() + valueArgumentCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(2, 9, "A KDoc is not allowed inside a 'value_argument_list'") + } + + @Test + fun `Given a block comment inside a value argument`() { + val code = + """ + val foo = foo( + bar /* some comment */ = "bar" + ) + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + valueArgumentCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(2, 9, "A (block or EOL) comment inside or on same line after a 'value_argument' is not allowed. It may be placed on a separate line above.") + } + + @Test + fun `Given an EOL comment inside a value argument`() { + val code = + """ + val foo = foo( + bar // some comment + = "bar" + ) + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + valueArgumentCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(2, 9, "A (block or EOL) comment inside or on same line after a 'value_argument' is not allowed. It may be placed on a separate line above.") + } + + @Test + fun `Given a kdoc as child of value argument list`() { + val code = + """ + val foo = foo( + /** some comment */ + ) + """.trimIndent() + valueArgumentCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(2, 5, "A KDoc is not allowed inside a 'value_argument_list'") + } + + @Test + fun `Given a comment as only child of value argument list`() { + val code = + """ + val foo1 = foo( + // some comment + ) + val foo2 = foo( + /* some comment */ + ) + """.trimIndent() + valueArgumentCommentRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a comment on separate line before value argument ast node`() { + val code = + """ + val foo1 = foo( + // some comment + "bar" + ) + val foo2 = foo( + /* some comment */ + "bar" + ) + """.trimIndent() + valueArgumentCommentRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a comment as last node of value argument ast node`() { + val code = + """ + val foo1 = foo( + "bar" // some comment + ) + val foo2 = foo( + "bar" /* some comment */ + ) + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + valueArgumentCommentRuleAssertThat(code) + .hasLintViolationsWithoutAutoCorrect( + LintViolation(2, 11, "A comment in a 'value_argument_list' is only allowed when placed on a separate line"), + LintViolation(5, 11, "A comment in a 'value_argument_list' is only allowed when placed on a separate line"), + ) + } + + @Test + fun `Given a comment after a comma on the same line as an value argument ast node`() { + val code = + """ + val foo1 = foo( + "bar", // some comment + ) + val foo2 = foo( + "bar", /* some comment */ + ) + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + valueArgumentCommentRuleAssertThat(code) + .hasLintViolationsWithoutAutoCorrect( + LintViolation(2, 12, "A comment in a 'value_argument_list' is only allowed when placed on a separate line"), + LintViolation(5, 12, "A comment in a 'value_argument_list' is only allowed when placed on a separate line"), + ) + } + + @Test + fun `Given a comment as last node of value argument list`() { + val code = + """ + val foo1 = foo( + "bar" + // some comment + ) + val foo1 = foo( + "bar" + /* some comment */ + ) + """.trimIndent() + valueArgumentCommentRuleAssertThat(code).hasNoLintViolations() + } +} diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRuleTest.kt new file mode 100644 index 0000000000..bdc5ba1e78 --- /dev/null +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ValueParameterCommentRuleTest.kt @@ -0,0 +1,160 @@ +package com.pinterest.ktlint.ruleset.standard.rules + +import com.pinterest.ktlint.test.KtLintAssertThat +import com.pinterest.ktlint.test.LintViolation +import org.junit.jupiter.api.Test + +class ValueParameterCommentRuleTest { + private val valueParameterCommentRuleAssertThat = KtLintAssertThat.assertThatRule { ValueParameterCommentRule() } + + @Test + fun `Given a kdoc as child of value parameter list`() { + val code = + """ + class Foo( + /** some comment */ + ) + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + valueParameterCommentRuleAssertThat(code) + .hasLintViolationWithoutAutoCorrect(2, 5, "A KDoc is not allowed inside a 'value_parameter_list' when not followed by a property") + } + + @Test + fun `Given a kdoc as only child of value parameter list`() { + val code = + """ + class Foo1( + // some comment + ) + class Foo2( + /* some comment */ + ) + + """.trimIndent() + valueParameterCommentRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a comment as only child of value parameter list`() { + val code = + """ + class Foo1( + // some comment + ) + class Foo2( + /* some comment */ + ) + + """.trimIndent() + valueParameterCommentRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a comment on separate line before value parameter ast node`() { + val code = + """ + class Foo1( + // some comment + val bar: Bar, + // some comment + val bar: Bar, + ) + class Foo2( + /* some comment */ + val bar: Bar, + /* some comment */ + val bar: Bar, + ) + class Foo3( + /** some comment */ + val bar: Bar, + /** some comment */ + val bar: Bar, + ) + """.trimIndent() + valueParameterCommentRuleAssertThat(code).hasNoLintViolations() + } + + @Test + fun `Given a comment inside value parameter ast node`() { + val code = + """ + class Foo1( + val bar: + // some comment + Bar + ) + class Foo2( + val bar: /* some comment */ Bar + ) + class Foo3( + val bar: /** some comment */ Bar + ) + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + valueParameterCommentRuleAssertThat(code) + .hasLintViolationsWithoutAutoCorrect( + LintViolation(3, 9, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above."), + LintViolation(7, 14, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above."), + LintViolation(10, 14, "A kdoc in a 'value_parameter' is only allowed when placed on a new line before this element"), + ) + } + + @Test + fun `Given a comment as last node of value parameter ast node`() { + val code = + """ + class Foo1( + val bar: Bar // some comment + ) + class Foo2( + val bar: Bar /* some comment */ + ) + class Foo3( + val bar: Bar /* some comment */ + ) + """.trimIndent() + @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") + valueParameterCommentRuleAssertThat(code) + .hasLintViolationsWithoutAutoCorrect( + LintViolation(2, 18, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above."), + LintViolation(5, 18, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above."), + LintViolation(8, 18, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above."), + ) + } + + @Test + fun `Given a comment after a comma on the same line as an value parameter ast node`() { + val code = + """ + class Foo1( + val bar: Bar, // some comment + ) + class Foo2( + val bar: Bar, /* some comment */ + ) + """.trimIndent() + valueParameterCommentRuleAssertThat(code) + .hasLintViolationsWithoutAutoCorrect( + LintViolation(2, 19, "A comment in a 'value_parameter_list' is only allowed when placed on a separate line"), + LintViolation(5, 19, "A comment in a 'value_parameter_list' is only allowed when placed on a separate line"), + ) + } + + @Test + fun `Given a comment as last node of value parameter list`() { + val code = + """ + class Foo( + val bar: Bar + // some comment + ) + class Foo( + val bar: Bar + /* some comment */ + ) + """.trimIndent() + valueParameterCommentRuleAssertThat(code).hasNoLintViolations() + } +} diff --git a/ktlint-test/api/ktlint-test.api b/ktlint-test/api/ktlint-test.api index 87c94424c5..abf7171612 100644 --- a/ktlint-test/api/ktlint-test.api +++ b/ktlint-test/api/ktlint-test.api @@ -5,11 +5,15 @@ public final class com/pinterest/ktlint/test/KtLintAssertThat { public fun (Lcom/pinterest/ktlint/rule/engine/core/api/RuleProvider;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;)V public final fun addAdditionalRuleProvider (Lkotlin/jvm/functions/Function0;)Lcom/pinterest/ktlint/test/KtLintAssertThat; public final fun addAdditionalRuleProviders ([Lkotlin/jvm/functions/Function0;)Lcom/pinterest/ktlint/test/KtLintAssertThat; + public final fun addRequiredRuleProviderDependenciesFrom (Lcom/pinterest/ktlint/cli/ruleset/core/api/RuleSetProviderV3;)Lcom/pinterest/ktlint/test/KtLintAssertThat; public final fun asFileWithPath (Ljava/lang/String;)Lcom/pinterest/ktlint/test/KtLintAssertThat; public final fun asKotlinScript (Z)Lcom/pinterest/ktlint/test/KtLintAssertThat; public static synthetic fun asKotlinScript$default (Lcom/pinterest/ktlint/test/KtLintAssertThat;ZILjava/lang/Object;)Lcom/pinterest/ktlint/test/KtLintAssertThat; + public final fun build ()Lkotlin/jvm/functions/Function1; public final fun hasLintViolation (IILjava/lang/String;)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; public final fun hasLintViolationForAdditionalRule (IILjava/lang/String;)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; + public final fun hasLintViolationForAdditionalRule (IILjava/lang/String;Z)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; + public static synthetic fun hasLintViolationForAdditionalRule$default (Lcom/pinterest/ktlint/test/KtLintAssertThat;IILjava/lang/String;ZILjava/lang/Object;)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; public final fun hasLintViolationWithoutAutoCorrect (IILjava/lang/String;)V public final fun hasLintViolations ([Lcom/pinterest/ktlint/test/LintViolation;)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; public final fun hasLintViolationsForAdditionalRule ([Lcom/pinterest/ktlint/test/LintViolation;)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; @@ -26,6 +30,7 @@ public final class com/pinterest/ktlint/test/KtLintAssertThat$Companion { public final fun assertThatRule (Lkotlin/jvm/functions/Function0;)Lkotlin/jvm/functions/Function1; public final fun assertThatRule (Lkotlin/jvm/functions/Function0;Ljava/util/Set;Ljava/util/Set;)Lkotlin/jvm/functions/Function1; public static synthetic fun assertThatRule$default (Lcom/pinterest/ktlint/test/KtLintAssertThat$Companion;Lkotlin/jvm/functions/Function0;Ljava/util/Set;Ljava/util/Set;ILjava/lang/Object;)Lkotlin/jvm/functions/Function1; + public final fun assertThatRuleBuilder (Lkotlin/jvm/functions/Function0;)Lcom/pinterest/ktlint/test/KtLintAssertThat; } public final class com/pinterest/ktlint/test/KtLintAssertThatAssertable : org/assertj/core/api/AbstractAssert { @@ -33,6 +38,8 @@ public final class com/pinterest/ktlint/test/KtLintAssertThatAssertable : org/as public synthetic fun (Lcom/pinterest/ktlint/rule/engine/core/api/RuleProvider;Lcom/pinterest/ktlint/rule/engine/api/Code;Lcom/pinterest/ktlint/rule/engine/api/EditorConfigOverride;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun hasLintViolation (IILjava/lang/String;)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; public final fun hasLintViolationForAdditionalRule (IILjava/lang/String;)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; + public final fun hasLintViolationForAdditionalRule (IILjava/lang/String;Z)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; + public static synthetic fun hasLintViolationForAdditionalRule$default (Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable;IILjava/lang/String;ZILjava/lang/Object;)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; public final fun hasLintViolationWithoutAutoCorrect (IILjava/lang/String;)V public final fun hasLintViolations ([Lcom/pinterest/ktlint/test/LintViolation;)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; public final fun hasLintViolationsForAdditionalRules ([Lcom/pinterest/ktlint/test/LintViolation;)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; diff --git a/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt b/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt index d8976da8a2..cb367c68db 100644 --- a/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt +++ b/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt @@ -1,5 +1,6 @@ package com.pinterest.ktlint.test +import com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3 import com.pinterest.ktlint.logger.api.initKtLintKLogger import com.pinterest.ktlint.logger.api.setDefaultLoggerModifier import com.pinterest.ktlint.rule.engine.api.Code @@ -9,6 +10,7 @@ import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride.Companion.plus import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine import com.pinterest.ktlint.rule.engine.api.LintError import com.pinterest.ktlint.rule.engine.core.api.Rule +import com.pinterest.ktlint.rule.engine.core.api.Rule.VisitorModifier.RunAfterRule.Mode.ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.RuleProvider import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EXPERIMENTAL_RULES_EXECUTION_PROPERTY @@ -164,8 +166,10 @@ public class KtLintAssertThat( } /** - * Adds a single provider of an additional rule to be executed when linting/formatting the code. This can to be used to unit test rules - * which are best to be tested in conjunction with other rules, for example wrapping and indenting. + * Adds additional rule providers to be executed when linting/formatting the code. This can to be used to unit test rules which are best + * to be tested in conjunction with other rules, for example wrapping and indenting. + * + * Note that the rule providers which are required for the main [ruleProvider] are added automatically when creating the assertThat. * * Caution: * An additional rule provider for a rule which actually is executed before the rule under test, might result in unexpected @@ -184,6 +188,41 @@ public class KtLintAssertThat( return this } + /** + * Adds additional rule providers for the [ruleProvider] and each [additionalRuleProviders] that itself is depending on another required + * rule. This method only has to be called once after all [additionalRuleProviders] have been added. Also, it only needs to be called in + * case at least one the [additionalRuleProviders] has a dependency on another required rule. + */ + public fun addRequiredRuleProviderDependenciesFrom(ruleSetProviderV3: RuleSetProviderV3): KtLintAssertThat { + val size = additionalRuleProviders.size + ruleSetProviderV3 + .findRequiredRuleProviders(ruleProvider) + .let { additionalRuleProviders.addAll(it) } + additionalRuleProviders + .map { ruleSetProviderV3.findRequiredRuleProviders(it) } + .flatten() + .filter { it !in additionalRuleProviders } + .let { additionalRuleProviders.addAll(it) } + if (additionalRuleProviders.size == size) { + LOGGER.warn { "Call to 'addRequiredRuleProviderDependenciesFrom' is useless as no rule providers have to be added" } + } + + return this + } + + /** + * Builds the [KtLintAssertThat] lambda from that is able to lint or format a given piece of code. + */ + public fun build(): (String) -> KtLintAssertThat = + { code -> + KtLintAssertThat( + ruleProvider = this.ruleProvider, + code = code, + additionalRuleProviders = this.additionalRuleProviders.toMutableSet(), + editorConfigProperties = this.editorConfigProperties.toMutableSet(), + ) + } + /** * Asserts that the code does not contain any [LintViolation]s in the rule associated with the KtLintAssertThat. * @@ -230,7 +269,18 @@ public class KtLintAssertThat( line: Int, col: Int, detail: String, - ): KtLintAssertThatAssertable = ktLintAssertThatAssertable().hasLintViolationForAdditionalRule(line, col, detail) + ): KtLintAssertThatAssertable = hasLintViolationForAdditionalRule(line, col, detail, canBeAutoCorrected = true) + + /** + * Asserts that the code does contain given [LintViolation] caused by an additional rule which automatically can be corrected. This is a + * sugar-coated version of [hasLintViolations] for the case that the code contains exactly one lint violation. + */ + public fun hasLintViolationForAdditionalRule( + line: Int, + col: Int, + detail: String, + canBeAutoCorrected: Boolean = true, + ): KtLintAssertThatAssertable = ktLintAssertThatAssertable().hasLintViolationForAdditionalRule(line, col, detail, canBeAutoCorrected) /** * Asserts that the code does contain given [LintViolation]s caused by an additional rules which can be automatically corrected. Note @@ -291,7 +341,24 @@ public class KtLintAssertThat( * Creates an assertThat assertion function for the rule provided by [provider]. This assertion function has extensions specifically * for testing KtLint rules. */ - public fun assertThatRule(provider: () -> Rule): (String) -> KtLintAssertThat = RuleProvider { provider() }.assertThat() + public fun assertThatRule(provider: () -> Rule): (String) -> KtLintAssertThat = assertThatRuleBuilder(provider).build() + + /** + * Creates a builder for constructing an assertThat assertion function for the rule provided by [provider]. Before constructing + * (e.g. before calling [build]) the builder can be customized with functions like [addAdditionalRuleProvider] or + * [addEditorConfigProperties]. This has effect on all invocations of the assertion function, and as of that is intended to create + * the base assertion function. + * After constructing (using function [build]) the created assertion function can be customized with function like + * [addAdditionalRuleProvider], [addEditorConfigProperties]. Those customization only affect the assert function for one piece of + * code that is to be linted or formatted. + */ + public fun assertThatRuleBuilder(provider: () -> Rule): KtLintAssertThat = + KtLintAssertThat( + ruleProvider = RuleProvider { provider() }, + code = "", + additionalRuleProviders = mutableSetOf(), + editorConfigProperties = mutableSetOf(), + ) /** * Creates an assertThat assertion function for the rule provided by [provider]. This assertion function has extensions specifically @@ -299,21 +366,19 @@ public class KtLintAssertThat( * This means that the unit test only has to check the lint violations thrown by the rule for which the assertThat is created. But * the code is formatted by both the rule and the rules provided by [additionalRuleProviders] in the order as defined by the rule * definitions. + * + * Use function [assertThatRule] to create a default assertThat assertion function. In case a more specialized assertion function is + * needed, then use [assertThatRuleBuilder]. */ - + @Deprecated(message = "Marked for removal in Ktlint 2.0. See KDOC for alternative") public fun assertThatRule( provider: () -> Rule, additionalRuleProviders: Set = emptySet(), editorConfigProperties: Set, *>> = emptySet(), - ): (String) -> KtLintAssertThat = RuleProvider { provider() }.assertThat(additionalRuleProviders, editorConfigProperties) - - private fun RuleProvider.assertThat( - additionalRuleProviders: Set = emptySet(), - editorConfigProperties: Set, *>> = emptySet(), ): (String) -> KtLintAssertThat = { code -> KtLintAssertThat( - ruleProvider = this, + ruleProvider = RuleProvider { provider() }, code = code, additionalRuleProviders = additionalRuleProviders.toMutableSet(), editorConfigProperties = editorConfigProperties.toMutableSet(), @@ -463,12 +528,24 @@ public class KtLintAssertThatAssertable( line: Int, col: Int, detail: String, + ): KtLintAssertThatAssertable = hasLintViolationForAdditionalRule(line, col, detail, canBeAutoCorrected = true) + + /** + * Asserts that the code does contain given [LintViolation] caused by an additional rule. This is a sugar-coated version of + * [hasLintViolationsForAdditionalRules] for the case that the code contains exactly one lint violation. + */ + public fun hasLintViolationForAdditionalRule( + line: Int, + col: Int, + detail: String, + canBeAutoCorrected: Boolean = true, ): KtLintAssertThatAssertable = hasLintViolationsForAdditionalRules( LintViolation( line = line, col = col, detail = detail, + canBeAutoCorrected = canBeAutoCorrected, ), ) @@ -684,3 +761,31 @@ private fun EditorConfigOverride.extendWithRuleSetRuleExecutionsFor(ruleProvider private fun EditorConfigOverride.enableExperimentalRules(): EditorConfigOverride = plus(EXPERIMENTAL_RULES_EXECUTION_PROPERTY to RuleExecution.enabled) + +private fun RuleSetProviderV3.findRequiredRuleProviders(ruleProvider: RuleProvider): Set { + val resultRuleProviders = mutableSetOf() + + val ruleProviders = ArrayDeque() + ruleProviders.add(ruleProvider) + // Recursively add all rule providers which are required to run the given rule + while (ruleProviders.firstOrNull() != null) { + with(ruleProviders.removeFirst()) { + if (this != ruleProvider) { + resultRuleProviders.add(this) + } + + runAfterRules + .filter { it.mode == ONLY_WHEN_RUN_AFTER_RULE_IS_LOADED_AND_ENABLED } + .forEach { runAfterRule -> + findRuleProvider(runAfterRule.ruleId).let { ruleProviders.add(it) } + } + } + } + + return resultRuleProviders +} + +private fun RuleSetProviderV3.findRuleProvider(ruleId: RuleId): RuleProvider = + getRuleProviders() + .find { it.ruleId == ruleId } + ?: throw IllegalArgumentException("Can not find rule '${ruleId.value}' in given rule set '${this.id.value}'") From 7c1c571715654c6cd5256eed0c271541a77fb851 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Wed, 22 Nov 2023 17:37:57 +0100 Subject: [PATCH 5/7] Rename `build` to `assertThat` --- .../engine/internal/rules/KtlintSuppressionRuleTest.kt | 2 +- .../standard/rules/ArgumentListWrappingRuleTest.kt | 2 +- .../standard/rules/ChainMethodContinuationRuleTest.kt | 2 +- .../ruleset/standard/rules/ClassSignatureRuleTest.kt | 2 +- .../ruleset/standard/rules/FunctionSignatureRuleTest.kt | 2 +- .../ruleset/standard/rules/IfElseWrappingRuleTest.kt | 2 +- .../standard/rules/NoSingleLineBlockCommentRuleTest.kt | 2 +- .../standard/rules/ParameterListWrappingRuleTest.kt | 2 +- .../standard/rules/StringTemplateIndentRuleTest.kt | 2 +- .../standard/rules/TrailingCommaOnCallSiteRuleTest.kt | 2 +- .../rules/TrailingCommaOnDeclarationSiteRuleTest.kt | 6 +++--- ktlint-test/api/ktlint-test.api | 2 +- .../kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt | 8 ++++---- 13 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRuleTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRuleTest.kt index 1a22653e4b..1a89fa44ef 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRuleTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/rules/KtlintSuppressionRuleTest.kt @@ -31,7 +31,7 @@ class KtlintSuppressionRuleTest { .addAdditionalRuleProvider { DummyRule("custom:foo") } .addAdditionalRuleProvider { DummyRule("standard:bar") } .addAdditionalRuleProvider { DummyRule("standard:foo") } - .build() + .assertThat() @Nested inner class `Given a suppression annotation missing the rule set id prefix` { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt index b1e201278e..557423ef24 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ArgumentListWrappingRuleTest.kt @@ -15,7 +15,7 @@ class ArgumentListWrappingRuleTest { private val argumentListWrappingRuleAssertThat = assertThatRuleBuilder { ArgumentListWrappingRule() } .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) - .build() + .assertThat() @Test fun `Given a function call and not all arguments are on the same line`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRuleTest.kt index f6d6b61245..ce56d437b9 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ChainMethodContinuationRuleTest.kt @@ -17,7 +17,7 @@ class ChainMethodContinuationRuleTest { private val chainMethodContinuationRuleAssertThat = assertThatRuleBuilder { ChainMethodContinuationRule() } .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) - .build() + .assertThat() @Test fun `Given that no maximum line length is set, and a single line method chain does not exceed the maximum number of chain operators then do not wrap`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRuleTest.kt index 6a5f010470..b545b4c0c0 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassSignatureRuleTest.kt @@ -25,7 +25,7 @@ class ClassSignatureRuleTest { private val classSignatureWrappingRuleAssertThat = assertThatRuleBuilder { ClassSignatureRule() } .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) - .build() + .assertThat() @Nested inner class `Given a class with a body` { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRuleTest.kt index 5fe31160b1..ff3c50ad45 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionSignatureRuleTest.kt @@ -26,7 +26,7 @@ class FunctionSignatureRuleTest { private val functionSignatureWrappingRuleAssertThat = assertThatRuleBuilder { FunctionSignatureRule() } .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) - .build() + .assertThat() @Test fun `Given a single line function signature which is smaller than or equal to the max line length, and the function is followed by a body block, then do no change the signature`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleTest.kt index 36881c679b..b950748c87 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/IfElseWrappingRuleTest.kt @@ -10,7 +10,7 @@ class IfElseWrappingRuleTest { assertThatRuleBuilder { IfElseWrappingRule() } // Keep formatted code readable .addAdditionalRuleProvider { IndentationRule() } - .build() + .assertThat() @Test fun `Given a single line if statement without else then do not report a violation`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRuleTest.kt index 16a4627b93..9c15c169c6 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSingleLineBlockCommentRuleTest.kt @@ -10,7 +10,7 @@ class NoSingleLineBlockCommentRuleTest { private val noSingleLineBlockCommentRuleAssertThat = assertThatRuleBuilder { NoSingleLineBlockCommentRule() } .addAdditionalRuleProvider { CommentWrappingRule() } - .build() + .assertThat() @Test fun `Given a single line block comment then replace it with an EOL comment`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleTest.kt index 6c11bcd105..0fed411c8d 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListWrappingRuleTest.kt @@ -17,7 +17,7 @@ class ParameterListWrappingRuleTest { assertThatRuleBuilder { ParameterListWrappingRule() } // Keep formatted code readable .addAdditionalRuleProvider { IndentationRule() } - .build() + .assertThat() @Test fun `Given a class with parameters on multiple lines then put each parameter and closing parenthesis on a separate line`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt index a83e9bf4b4..0517d95c15 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateIndentRuleTest.kt @@ -14,7 +14,7 @@ class StringTemplateIndentRuleTest { private val stringTemplateIndentRuleAssertThat = assertThatRuleBuilder { StringTemplateIndentRule() } .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) - .build() + .assertThat() @Test fun `Do not move a multiline string literal after return statement to a new line as that results in a compilation error`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRuleTest.kt index c7efd07216..27037e3457 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRuleTest.kt @@ -13,7 +13,7 @@ class TrailingCommaOnCallSiteRuleTest { // Keep formatted code readable .addAdditionalRuleProvider { IndentationRule() } .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) - .build() + .assertThat() @Test fun `Given property allow trailing comma on call site is not set then remove trailing commas`() { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRuleTest.kt index 9bd78efbde..538f3cbed5 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRuleTest.kt @@ -13,7 +13,7 @@ class TrailingCommaOnDeclarationSiteRuleTest { // Unit tests becomes more readable when properly indented .addAdditionalRuleProvider { IndentationRule() } .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) - .build() + .assertThat() @Test fun `Given property allow trailing comma on declaration site is not set then remove trailing commas`() { @@ -933,7 +933,7 @@ class TrailingCommaOnDeclarationSiteRuleTest { assertThatRuleBuilder { MultiLineIfElseRule() } .addAdditionalRuleProvider { TrailingCommaOnDeclarationSiteRule() } .addAdditionalRuleProvider { WrappingRule() } - .build() + .assertThat() multiLineIfElseRuleAssertThat(code) .withEditorConfigOverride(TRAILING_COMMA_ON_DECLARATION_SITE_PROPERTY to true) @@ -1022,7 +1022,7 @@ class TrailingCommaOnDeclarationSiteRuleTest { .addAdditionalRuleProvider { TrailingCommaOnDeclarationSiteRule() } .withEditorConfigOverride(TRAILING_COMMA_ON_DECLARATION_SITE_PROPERTY to true) .addRequiredRuleProviderDependenciesFrom(StandardRuleSetProvider()) - .build() + .assertThat() noSemicolonsRuleAssertThat(code) .hasNoLintViolationsForRuleId(NO_SEMICOLONS_RULE_ID) diff --git a/ktlint-test/api/ktlint-test.api b/ktlint-test/api/ktlint-test.api index abf7171612..f744d4b45e 100644 --- a/ktlint-test/api/ktlint-test.api +++ b/ktlint-test/api/ktlint-test.api @@ -9,7 +9,7 @@ public final class com/pinterest/ktlint/test/KtLintAssertThat { public final fun asFileWithPath (Ljava/lang/String;)Lcom/pinterest/ktlint/test/KtLintAssertThat; public final fun asKotlinScript (Z)Lcom/pinterest/ktlint/test/KtLintAssertThat; public static synthetic fun asKotlinScript$default (Lcom/pinterest/ktlint/test/KtLintAssertThat;ZILjava/lang/Object;)Lcom/pinterest/ktlint/test/KtLintAssertThat; - public final fun build ()Lkotlin/jvm/functions/Function1; + public final fun assertThat ()Lkotlin/jvm/functions/Function1; public final fun hasLintViolation (IILjava/lang/String;)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; public final fun hasLintViolationForAdditionalRule (IILjava/lang/String;)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; public final fun hasLintViolationForAdditionalRule (IILjava/lang/String;Z)Lcom/pinterest/ktlint/test/KtLintAssertThatAssertable; diff --git a/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt b/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt index cb367c68db..965994ee83 100644 --- a/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt +++ b/ktlint-test/src/main/kotlin/com/pinterest/ktlint/test/KtLintAssertThat.kt @@ -213,7 +213,7 @@ public class KtLintAssertThat( /** * Builds the [KtLintAssertThat] lambda from that is able to lint or format a given piece of code. */ - public fun build(): (String) -> KtLintAssertThat = + public fun assertThat(): (String) -> KtLintAssertThat = { code -> KtLintAssertThat( ruleProvider = this.ruleProvider, @@ -341,14 +341,14 @@ public class KtLintAssertThat( * Creates an assertThat assertion function for the rule provided by [provider]. This assertion function has extensions specifically * for testing KtLint rules. */ - public fun assertThatRule(provider: () -> Rule): (String) -> KtLintAssertThat = assertThatRuleBuilder(provider).build() + public fun assertThatRule(provider: () -> Rule): (String) -> KtLintAssertThat = assertThatRuleBuilder(provider).assertThat() /** * Creates a builder for constructing an assertThat assertion function for the rule provided by [provider]. Before constructing - * (e.g. before calling [build]) the builder can be customized with functions like [addAdditionalRuleProvider] or + * (e.g. before calling [assertThat]) the builder can be customized with functions like [addAdditionalRuleProvider] or * [addEditorConfigProperties]. This has effect on all invocations of the assertion function, and as of that is intended to create * the base assertion function. - * After constructing (using function [build]) the created assertion function can be customized with function like + * After constructing (using function [assertThat]) the created assertion function can be customized with function like * [addAdditionalRuleProvider], [addEditorConfigProperties]. Those customization only affect the assert function for one piece of * code that is to be linted or formatted. */ From 8efae4519d130f3f98a8bb5657bad52812ba2c15 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Wed, 22 Nov 2023 18:05:41 +0100 Subject: [PATCH 6/7] Add documentation for new rules, remove discourage-comment-location from docs --- documentation/snapshot/docs/rules/standard.md | 194 ++++++++++++++++-- 1 file changed, 173 insertions(+), 21 deletions(-) diff --git a/documentation/snapshot/docs/rules/standard.md b/documentation/snapshot/docs/rules/standard.md index a7629ab43d..d8c7b2894a 100644 --- a/documentation/snapshot/docs/rules/standard.md +++ b/documentation/snapshot/docs/rules/standard.md @@ -128,27 +128,6 @@ Lines in a block comment which (exclusive the indentation) start with a `*` shou Rule id: `block-comment-initial-star-alignment` (`standard` rule set) -## Discouraged comment location - -Detect discouraged comment locations (no autocorrect). - -!!! note - Kotlin allows comments to be placed almost everywhere. As this can lead to code which is hard to read, most of them will never be used in practice. Ideally each rule takes comments at all possible locations into account. Sometimes this is really hard and not worth the effort. By explicitly forbidding such comment locations, the development of those rules becomes a bit easier. - -=== "[:material-heart-off-outline:](#) Disallowed" - - ```kotlin - fun /* some comment */ foo(t: T) = "some-result" - - fun foo() { - if (true) - // some comment - bar() - } - ``` - -Rule id: `discouraged-comment-location` (`standard` rule set) - ## Enum entry Enum entry names should be uppercase underscore-separated or upper camel-case separated. @@ -2022,6 +2001,89 @@ Consistent removal (default) or adding of trailing commas on declaration site. Rule id: `trailing-comma-on-declaration-site` (`standard` rule set) +## Type argument comment + +Disallows comments to be placed at certain locations inside a type argument (list). A KDoc is not allowed. + +=== "[:material-heart:](#) Ktlint" + + ```kotlin + fun Foo< + /* some comment */ + out Any + >.foo() {} + fun Foo< + // some comment + out Any + >.foo() {} + ``` + +=== "[:material-heart-off-outline:](#) Disallowed" + + ```kotlin + fun Foo.foo() {} + fun Foo< + out Any, // some comment + >.foo() {} + val fooBar: FooBar< + /** some comment */ + Foo, + Bar + > + ``` + +Note: Although code sample below might look ok, it is semantically and programmatically unclear to which element `some comment 1` refers. As of that comments are only allowed when starting on a separate line. +```kotlin +fun Foo< + out Bar1, // some comment 1 + // some comment 2 + out Bar2, // some comment + >.foo() {} +``` + +Rule id: `type-argument-comment` (`standard` rule set) + +## Type parameter comment + +Disallows comments to be placed at certain locations inside a type parameter (list). A KDoc is not allowed. + +=== "[:material-heart:](#) Ktlint" + + ```kotlin + class Foo2< + /* some comment */ + out Bar + > + class Foo3< + // some comment + out Bar + > + ``` + +=== "[:material-heart-off-outline:](#) Disallowed" + + ```kotlin + class Foo1< + /** some comment */ + in Bar + > + class Foo2 + class Foo3< + in Bar, // some comment + > + ``` + +Note: Although code sample below might look ok, it is semantically and programmatically unclear to which element `some comment 1` refers. As of that comments are only allowed when starting on a separate line. +```kotlin +class Foo< + out Bar1, // some comment 1 + // some comment 2 + out Bar2, // some comment + > +``` + +Rule id: `type-parameter-comment` (`standard` rule set) + ## Unnecessary parenthesis before trailing lambda An empty parentheses block before a lambda is redundant. @@ -2040,6 +2102,96 @@ An empty parentheses block before a lambda is redundant. Rule id: `unnecessary-parentheses-before-trailing-lambda` (`standard` rule set) +## Value argument comment + +Disallows comments to be placed at certain locations inside a value argument (list). A KDoc is not allowed. + +=== "[:material-heart:](#) Ktlint" + + ```kotlin + val foo2 = + foo( + /* some comment */ + bar = "bar" + ) + val foo3 = + foo( + // some comment + bar = "bar" + ) + ``` + +=== "[:material-heart-off-outline:](#) Disallowed" + + ```kotlin + val foo1 = foo(bar /** some comment */ = "bar") + val foo2 = foo(bar /* some comment */ = "bar") + val foo3 = + foo( + bar = // some comment + "bar" + ) + ``` + +Note: Although code sample below might look ok, it is semantically and programmatically unclear to which element `some comment 1` refers. As of that comments are only allowed when starting on a separate line. +```kotlin +class Foo< + out Bar1, // some comment 1 + // some comment 2 + out Bar2, // some comment + > +``` + +Rule id: `value-argument-comment` (`standard` rule set) + +## Value parameter comment + +Disallows comments to be placed at certain locations inside a value argument (list). A KDoc is allowed but must start on a separate line. + +=== "[:material-heart:](#) Ktlint" + + ```kotlin + class Foo1( + /** some comment */ + bar = "bar" + ) + class Foo2( + /* some comment */ + bar = "bar" + ) + class Foo3( + // some comment + bar = "bar" + ) + ``` + +=== "[:material-heart-off-outline:](#) Disallowed" + + ```kotlin + class Foo2( + bar /** some comment */ = "bar" + ) + class Foo2( + bar = /* some comment */ "bar" + ) + class Foo3( + bar = + // some comment + "bar" + ) + ``` + +Note: Although code sample below might look ok, it is semantically and programmatically unclear to which element `some comment 1` refers. As of that comments are only allowed when starting on a separate line. +```kotlin +class Foo( + bar: Bar1, // some comment 1 + // some comment 2 + bar2: Bar2, // some comment +) +``` + +Rule id: `value-parameter-comment` (`standard` rule set) + ## Wrapping ### Argument list wrapping From 0213ca29877fba5201cea444c6afc6f11646ce57 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Wed, 22 Nov 2023 18:36:27 +0100 Subject: [PATCH 7/7] Remove logic DiscouragedCommentLocationRule Logic of this rule has been moved to normal rule classes in case a discouraged comment location was added for one specific rule only. Comment locations that are more generic (e.g. used by at least two different rules) are moved to separate rules as this provides more clarity about rule dependencies. Logic from this class has been removed, except for what is needed to avoid a breaking change. --- .../rules/DiscouragedCommentLocationRule.kt | 151 +-- .../DiscouragedCommentLocationRuleTest.kt | 918 ------------------ 2 files changed, 8 insertions(+), 1061 deletions(-) delete mode 100644 ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRuleTest.kt diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt index 2e982cfabb..bd9b948976 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRule.kt @@ -1,31 +1,9 @@ package com.pinterest.ktlint.ruleset.standard.rules -import com.pinterest.ktlint.rule.engine.core.api.ElementType.BLOCK_COMMENT -import com.pinterest.ktlint.rule.engine.core.api.ElementType.DOT -import com.pinterest.ktlint.rule.engine.core.api.ElementType.ELSE -import com.pinterest.ktlint.rule.engine.core.api.ElementType.ELSE_KEYWORD -import com.pinterest.ktlint.rule.engine.core.api.ElementType.EOL_COMMENT -import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC -import com.pinterest.ktlint.rule.engine.core.api.ElementType.RPAR -import com.pinterest.ktlint.rule.engine.core.api.ElementType.SAFE_ACCESS -import com.pinterest.ktlint.rule.engine.core.api.ElementType.THEN -import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_ARGUMENT_LIST -import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_PARAMETER -import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_PARAMETER_LIST -import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_PROJECTION -import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT -import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT_LIST -import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER -import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER_LIST import com.pinterest.ktlint.rule.engine.core.api.RuleId import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE -import com.pinterest.ktlint.rule.engine.core.api.afterCodeSibling -import com.pinterest.ktlint.rule.engine.core.api.betweenCodeSiblings -import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment -import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline -import com.pinterest.ktlint.rule.engine.core.api.prevLeaf import com.pinterest.ktlint.ruleset.standard.StandardRule import org.jetbrains.kotlin.com.intellij.lang.ASTNode @@ -34,137 +12,24 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode * Disallowing comments at certain positions in the AST makes development of a rule easier as they have not to be taken * into account in that rule. * - * In examples below, a comment is placed between a type parameter list and the function name. Such comments are badly - * handled by default IntelliJ IDEA code formatter. We should put no effort in making and keeping ktlint in sync with - * such bad code formatting. - * - * ```kotlin - * fun - * // some comment - * foo(t: T) = "some-result" - * - * fun - * /* some comment - * * - * */ - * foo(t: T) = "some-result" - * ``` + * As of Ktlint 1.1.0 the logic of this rule is moved to normal rule classes in case a discouraged comment location was added for one + * specific rule only. Comment locations that are more generic (e.g. used by at least two different rules) are moved to separate rules as + * this provides more clarity about rule dependencies. + * The [DiscouragedCommentLocationRule] no longer contains any functionality to avoid duplication and conflicts with other rules. The rule + * will only be removed in Ktlint 2.0 to avoid breaking changes in 1.x. */ @SinceKtlint("0.45", EXPERIMENTAL) @SinceKtlint("1.0", STABLE) @Deprecated("Marked for removal in ktlint 2.0. See https://github.com/pinterest/ktlint/issues/2367") public class DiscouragedCommentLocationRule : StandardRule("discouraged-comment-location") { + // Prevent binary incompatibility not changing the public functions of the class. + @Suppress("RedundantOverride") override fun beforeVisitChildNodes( node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, ) { - if (node.isPartOfComment()) { - // Be restrictive when adding new locations at which comments are discouraged. Always run against major - // open source projects first to verify whether valid cases are found to comment at this location. - if (node.afterCodeSibling(TYPE_PARAMETER_LIST) || - node.betweenCodeSiblings(RPAR, THEN) || - node.betweenCodeSiblings(THEN, ELSE_KEYWORD) || - node.betweenCodeSiblings(ELSE_KEYWORD, ELSE) || - node.afterCodeSibling(DOT) || - node.afterCodeSibling(SAFE_ACCESS) - ) { - emit(node.startOffset, "No comment expected at this location", false) - } - when (node.treeParent.elementType) { - VALUE_ARGUMENT, VALUE_PARAMETER, TYPE_PROJECTION, TYPE_PARAMETER -> - visitListElement(node, emit) - VALUE_ARGUMENT_LIST, VALUE_PARAMETER_LIST, TYPE_ARGUMENT_LIST, TYPE_PARAMETER_LIST -> - visitList(node, emit) - } - } - } - - private fun visitListElement( - node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - ) { - when (node.elementType) { - EOL_COMMENT, BLOCK_COMMENT -> { - // Disallow a comment inside a VALUE_PARAMETER. Note that an EOL comment which is placed on a separate line is a direct - // child of the list. - // class Foo - emit( - node.startOffset, - "A (block or EOL) comment inside or on same line after a '${node.treeParentElementTypeName()}' is not allowed. It " + - "may be placed on a separate line above.", - false, - ) - } - KDOC -> { - if (node.treeParent.elementType == VALUE_PARAMETER) { - // Contrary to other comments, a kdoc which is placed on a separate line before the val keyword is part of the - // VALUE_PARAMETER ast node and is to be allowed. - if (node != node.treeParent.firstChildNode) { - // Disallow a kdoc inside a VALUE_PARAMETER. - // class Foo( - // val bar: - // /** some comment */ - // Bar, - // ) - // or - // class Foo( - // val bar: Bar /** some comment */ - // ) - emit( - node.startOffset, - "A kdoc in a '${node.treeParentElementTypeName()}' is only allowed when placed on a new line before this " + - "element", - false, - ) - } - } else { - emit( - node.startOffset, - "A KDoc is not allowed inside a '${node.treeParentElementTypeName()}'", - false, - ) - } - } - } - } - - private fun ASTNode.treeParentElementTypeName() = - treeParent - .elementType - .toString() - .lowercase() - - private fun visitList( - node: ASTNode, - emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - ) { - when (node.elementType) { - EOL_COMMENT, BLOCK_COMMENT -> { - if (!node.prevLeaf().isWhiteSpaceWithNewline()) { - // Disallow - // class Foo( - // val bar1: Bar, // some comment 1 - // // some comment 2 - // val bar2: Bar, - // ) - // It is not clear whether "some comment 2" belongs to bar1 as a continuation of "some comment 1" or that is belongs to - // bar2. - emit( - node.startOffset, - "A comment in a '${node.treeParentElementTypeName()}' is only allowed when placed on a separate line", - false, - ) - } - } - KDOC -> { - emit( - node.startOffset, - "A KDoc is not allowed on a '${node.treeParentElementTypeName()}'", - false, - ) - } - } + super.beforeVisitChildNodes(node, autoCorrect, emit) } } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRuleTest.kt deleted file mode 100644 index bc2edcbd7c..0000000000 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommentLocationRuleTest.kt +++ /dev/null @@ -1,918 +0,0 @@ -package com.pinterest.ktlint.ruleset.standard.rules - -import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule -import com.pinterest.ktlint.test.LintViolation -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test - -class DiscouragedCommentLocationRuleTest { - private val discouragedCommentLocationRuleAssertThat = assertThatRule { DiscouragedCommentLocationRule() } - - @Nested - inner class `Given a comment after a type parameter then report a discouraged comment location` { - @Test - fun `Given an EOL comment`() { - val code = - """ - fun // some comment - foo(t: T) = "some-result" - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(1, 9, "No comment expected at this location") - } - - @Test - fun `Given an EOL comment on a newline`() { - val code = - """ - fun - // some comment - foo(t: T) = "some-result" - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(2, 1, "No comment expected at this location") - } - - @Test - fun `Given a block comment`() { - val code = - """ - fun /* some comment */ - foo(t: T) = "some-result" - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(1, 9, "No comment expected at this location") - } - - @Test - fun `Given a block comment on a newline`() { - val code = - """ - fun - /* some comment */ - foo(t: T) = "some-result" - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(2, 1, "No comment expected at this location") - } - - @Test - fun `Given a KDOC comment`() { - val code = - """ - fun /** some comment */ - foo(t: T) = "some-result" - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(1, 9, "No comment expected at this location") - } - - @Test - fun `Given a KDOC comment on a newline`() { - val code = - """ - fun - /** - * some comment - */ - foo(t: T) = "some-result" - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(2, 1, "No comment expected at this location") - } - } - - @Nested - inner class `Given a comment between IF CONDITION and THEN` { - @Test - fun `Given EOL comment on same line as CONDITION`() { - val code = - """ - fun foo() { - if (true) // some comment - bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(2, 15, "No comment expected at this location") - } - - @Test - fun `Given EOL comment on line below CONDITION`() { - val code = - """ - fun foo() { - if (true) - // some comment - bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(3, 9, "No comment expected at this location") - } - - @Test - fun `Given block comment on same line as CONDITION`() { - val code = - """ - fun foo() { - if (true) /* some comment */ - bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(2, 15, "No comment expected at this location") - } - - @Test - fun `Given block comment on line below CONDITION`() { - val code = - """ - fun foo() { - if (true) - /* some comment */ - bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(3, 9, "No comment expected at this location") - } - - @Test - fun `Given KDOC on same line as CONDITION`() { - val code = - """ - fun foo() { - if (true) /** some comment */ - bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(2, 15, "No comment expected at this location") - } - - @Test - fun `Given KDOC on line below CONDITION`() { - val code = - """ - fun foo() { - if (true) - /** some comment */ - bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(3, 9, "No comment expected at this location") - } - } - - @Nested - inner class `Given a comment between THEN and ELSE` { - @Test - fun `Given EOL comment on same line as THEN`() { - val code = - """ - fun foobar() { - if (true) - foo() // some comment - else bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(3, 15, "No comment expected at this location") - } - - @Test - fun `Given EOL comment on line below THEN`() { - val code = - """ - fun foobar() { - if (true) - foo() - // some comment - else bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(4, 5, "No comment expected at this location") - } - - @Test - fun `Given block comment on same line as THEN`() { - val code = - """ - fun foobar() { - if (true) - foo() /* some comment */ - else bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(3, 15, "No comment expected at this location") - } - - @Test - fun `Given block comment on line below THEN`() { - val code = - """ - fun foobar() { - if (true) - foo() - /* some comment */ - else bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(4, 5, "No comment expected at this location") - } - - @Test - fun `Given KDOC on same line as THEN`() { - val code = - """ - fun foobar() { - if (true) - foo() /** some comment */ - else bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(3, 15, "No comment expected at this location") - } - - @Test - fun `Given KDOC on line below THEN`() { - val code = - """ - fun foobar() { - if (true) - foo() - /** some comment */ - else bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(4, 5, "No comment expected at this location") - } - } - - @Nested - inner class `Given a comment between ELSE KEYWORD and ELSE block` { - @Test - fun `Given EOL comment on same line as THEN`() { - val code = - """ - fun foobar() { - if (true) - foo() - else // some comment - if (false) bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(4, 10, "No comment expected at this location") - } - - @Test - fun `Given EOL comment on line below THEN`() { - val code = - """ - fun foobar() { - if (true) - foo() - else - // some comment - if (false) bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(5, 5, "No comment expected at this location") - } - - @Test - fun `Given block comment on same line as THEN`() { - val code = - """ - fun foobar() { - if (true) - foo() - else /* some comment */ - if (false) bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(4, 10, "No comment expected at this location") - } - - @Test - fun `Given block comment on line below THEN`() { - val code = - """ - fun foobar() { - if (true) - foo() - else - /* some comment */ - if (false) bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(5, 5, "No comment expected at this location") - } - - @Test - fun `Given KDOC on same line as THEN`() { - val code = - """ - fun foobar() { - if (true) - foo() - else /** some comment */ - if (false) bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(4, 10, "No comment expected at this location") - } - - @Test - fun `Given KDOC on line below THEN`() { - val code = - """ - fun foobar() { - if (true) - foo() - else - /** some comment */ - if (false) bar() - } - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(5, 5, "No comment expected at this location") - } - } - - @Nested - inner class `Given a value argument list ast node` { - @Test - fun `Given a kdoc as child of value argument list`() { - val code = - """ - val foo = foo( - /** some comment */ - ) - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(2, 5, "A KDoc is not allowed on a 'value_argument_list'") - } - - @Test - fun `Given a comment as only child of value argument list`() { - val code = - """ - val foo1 = foo( - // some comment - ) - val foo2 = foo( - /* some comment */ - ) - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code).hasNoLintViolations() - } - - @Test - fun `Given a comment on separate line before value argument ast node`() { - val code = - """ - val foo1 = foo( - // some comment - "bar" - ) - val foo2 = foo( - /* some comment */ - "bar" - ) - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code).hasNoLintViolations() - } - - @Test - fun `Given a comment as last node of value argument ast node`() { - val code = - """ - val foo1 = foo( - "bar" // some comment - ) - val foo2 = foo( - "bar" /* some comment */ - ) - """.trimIndent() - @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationsWithoutAutoCorrect( - LintViolation(2, 11, "A comment in a 'value_argument_list' is only allowed when placed on a separate line"), - LintViolation(5, 11, "A comment in a 'value_argument_list' is only allowed when placed on a separate line"), - ) - } - - @Test - fun `Given a comment after a comma on the same line as an value argument ast node`() { - val code = - """ - val foo1 = foo( - "bar", // some comment - ) - val foo2 = foo( - "bar", /* some comment */ - ) - """.trimIndent() - @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationsWithoutAutoCorrect( - LintViolation(2, 12, "A comment in a 'value_argument_list' is only allowed when placed on a separate line"), - LintViolation(5, 12, "A comment in a 'value_argument_list' is only allowed when placed on a separate line"), - ) - } - - @Test - fun `Given a comment as last node of value argument list`() { - val code = - """ - val foo1 = foo( - "bar" - // some comment - ) - val foo1 = foo( - "bar" - /* some comment */ - ) - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code).hasNoLintViolations() - } - } - - @Nested - inner class `Given a value parameter list ast node` { - @Test - fun `Given a kdoc as child of value parameter list`() { - val code = - """ - class Foo( - /** some comment */ - ) - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(2, 5, "A KDoc is not allowed on a 'value_parameter_list'") - } - - @Test - fun `Given a kdoc as only child of value parameter list`() { - val code = - """ - class Foo1( - // some comment - ) - class Foo2( - /* some comment */ - ) - - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code).hasNoLintViolations() - } - - @Test - fun `Given a comment as only child of value parameter list`() { - val code = - """ - class Foo1( - // some comment - ) - class Foo2( - /* some comment */ - ) - - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code).hasNoLintViolations() - } - - @Test - fun `Given a comment on separate line before value parameter ast node`() { - val code = - """ - class Foo1( - // some comment - val bar: Bar, - // some comment - val bar: Bar, - ) - class Foo2( - /* some comment */ - val bar: Bar, - /* some comment */ - val bar: Bar, - ) - class Foo3( - /** some comment */ - val bar: Bar, - /** some comment */ - val bar: Bar, - ) - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code).hasNoLintViolations() - } - - @Test - fun `Given a comment inside value parameter ast node`() { - val code = - """ - class Foo1( - val bar: - // some comment - Bar - ) - class Foo2( - val bar: /* some comment */ Bar - ) - class Foo3( - val bar: /** some comment */ Bar - ) - """.trimIndent() - @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationsWithoutAutoCorrect( - LintViolation(3, 9, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above."), - LintViolation(7, 14, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above."), - LintViolation(10, 14, "A kdoc in a 'value_parameter' is only allowed when placed on a new line before this element"), - ) - } - - @Test - fun `Given a comment as last node of value parameter ast node`() { - val code = - """ - class Foo1( - val bar: Bar // some comment - ) - class Foo2( - val bar: Bar /* some comment */ - ) - class Foo3( - val bar: Bar /* some comment */ - ) - """.trimIndent() - @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationsWithoutAutoCorrect( - LintViolation(2, 18, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above."), - LintViolation(5, 18, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above."), - LintViolation(8, 18, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above."), - ) - } - - @Test - fun `Given a comment after a comma on the same line as an value parameter ast node`() { - val code = - """ - class Foo1( - val bar: Bar, // some comment - ) - class Foo2( - val bar: Bar, /* some comment */ - ) - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationsWithoutAutoCorrect( - LintViolation(2, 19, "A comment in a 'value_parameter_list' is only allowed when placed on a separate line"), - LintViolation(5, 19, "A comment in a 'value_parameter_list' is only allowed when placed on a separate line"), - ) - } - - @Test - fun `Given a comment as last node of value parameter list`() { - val code = - """ - class Foo( - val bar: Bar - // some comment - ) - class Foo( - val bar: Bar - /* some comment */ - ) - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code).hasNoLintViolations() - } - } - - @Nested - inner class `Given a type parameter list ast node` { - @Test - fun `Given a kdoc inside a type parameter`() { - val code = - """ - class Foo - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(1, 14, "A KDoc is not allowed inside a 'type_parameter'") - } - - @Test - fun `Given a block comment inside a type parameter`() { - val code = - """ - class Foo - """.trimIndent() - @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(1, 14, "A (block or EOL) comment inside or on same line after a 'type_parameter' is not allowed. It may be placed on a separate line above.") - } - - @Test - fun `Given an EOL comment inside a type parameter`() { - val code = - """ - class Foo - """.trimIndent() - @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(1, 14, "A (block or EOL) comment inside or on same line after a 'type_parameter' is not allowed. It may be placed on a separate line above.") - } - - @Test - fun `Given a kdoc as child of type parameter list`() { - val code = - """ - class Foo< - /** some comment */ - Bar> - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(2, 5, "A KDoc is not allowed on a 'type_parameter_list'") - } - - @Test - fun `Given a comment on separate line before type parameter ast node`() { - val code = - """ - class Foo1< - // some comment - Bar> - class Foo2< - /* some comment */ - Bar> - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code).hasNoLintViolations() - } - - @Test - fun `Given a comment after, but on the same line as an type parameter ast node`() { - val code = - """ - class Foo1< - Bar // some comment - > - class Foo2 - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationsWithoutAutoCorrect( - LintViolation(2, 9, "A comment in a 'type_parameter_list' is only allowed when placed on a separate line"), - LintViolation(4, 16, "A comment in a 'type_parameter_list' is only allowed when placed on a separate line"), - ) - } - - @Test - fun `Given a comment after a comma on the same line as an type parameter ast node`() { - val code = - """ - class FooBar1< - Foo, // some comment - Bar> - class FooBar2 - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationsWithoutAutoCorrect( - LintViolation(2, 10, "A comment in a 'type_parameter_list' is only allowed when placed on a separate line"), - LintViolation(4, 20, "A comment in a 'type_parameter_list' is only allowed when placed on a separate line"), - ) - } - - @Test - fun `Given a comment as last node of type parameter list`() { - val code = - """ - class FooBar1< - Foo - // some comment - > - class FooBar2< - Foo - /* some comment */ - > - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code).hasNoLintViolations() - } - } - - @Nested - inner class `Given a type argument list ast node` { - @Test - fun `Given a kdoc inside a type projection`() { - val code = - """ - fun Foo.foo() {} - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(1, 13, "A KDoc is not allowed inside a 'type_projection'") - } - - @Test - fun `Given a block comment inside a type projection`() { - val code = - """ - fun Foo.foo() {} - """.trimIndent() - @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(1, 13, "A (block or EOL) comment inside or on same line after a 'type_projection' is not allowed. It may be placed on a separate line above.") - } - - @Test - fun `Given a EOL comment inside type projection`() { - val code = - """ - fun Foo.foo() {} - """.trimIndent() - @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(1, 13, "A (block or EOL) comment inside or on same line after a 'type_projection' is not allowed. It may be placed on a separate line above.") - } - - @Test - fun `Given a kdoc as child of type argument list`() { - val code = - """ - val fooBar: FooBar< - /** some comment */ - Foo, Bar> - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationWithoutAutoCorrect(2, 5, "A KDoc is not allowed on a 'type_argument_list'") - } - - @Test - fun `Given a comment on separate line before type projection ast node`() { - val code = - """ - val fooBar1: FooBar< - // some comment - Foo, Bar> - val fooBar2: FooBar< - /* some comment */ - Foo, Bar> - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code).hasNoLintViolations() - } - - @Test - fun `Given a comment after a comma on the same line as an type projection ast node`() { - val code = - """ - val fooBar1: FooBar - val fooBar2: FooBar - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationsWithoutAutoCorrect( - LintViolation(1, 26, "A comment in a 'type_argument_list' is only allowed when placed on a separate line"), - LintViolation(3, 26, "A comment in a 'type_argument_list' is only allowed when placed on a separate line"), - ) - } - - @Test - fun `Given a comment as last node of type argument list`() { - val code = - """ - val fooBar: FooBar - val fooBar: FooBar - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code).hasNoLintViolations() - } - } - - @Nested - inner class `Given a DOT operator in a method chain` { - @Test - fun `Given a comment between the DOT and the next expression on same line`() { - val code = - """ - val foo1 = listOf("foo")./** some comment */size - val foo2 = listOf("foo")./** some comment */single() - val foo3 = listOf("foo")./* some comment */size - val foo4 = listOf("foo")./* some comment */single() - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationsWithoutAutoCorrect( - LintViolation(1, 26, "No comment expected at this location"), - LintViolation(2, 26, "No comment expected at this location"), - LintViolation(3, 26, "No comment expected at this location"), - LintViolation(4, 26, "No comment expected at this location"), - ) - } - - @Test - fun `Given a comment between the DOT and the next expression on a separate line`() { - val code = - """ - val foo1 = listOf("foo"). - /** some comment */ - size - val foo2 = listOf("foo"). - /** some comment */ - single() - val foo3 = listOf("foo"). - /* some comment */ - size - val foo4 = listOf("foo"). - /* some comment */ - single() - val foo5 = listOf("foo"). - // some comment - size - val foo6 = listOf("foo"). - // some comment - single() - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationsWithoutAutoCorrect( - LintViolation(2, 5, "No comment expected at this location"), - LintViolation(5, 5, "No comment expected at this location"), - LintViolation(8, 5, "No comment expected at this location"), - LintViolation(11, 5, "No comment expected at this location"), - LintViolation(14, 5, "No comment expected at this location"), - LintViolation(17, 5, "No comment expected at this location"), - ) - } - - @Test - fun `Given a comment after the DOT and the next expression on a separate line`() { - val code = - """ - val foo1 = listOf("foo"). /** some comment */ - size - val foo2 = listOf("foo"). /** some comment */ - single() - val foo3 = listOf("foo"). /* some comment */ - size - val foo4 = listOf("foo"). /* some comment */ - single() - val foo5 = listOf("foo"). // some comment - size - val foo6 = listOf("foo"). // some comment - single() - """.trimIndent() - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationsWithoutAutoCorrect( - LintViolation(1, 27, "No comment expected at this location"), - LintViolation(3, 27, "No comment expected at this location"), - LintViolation(5, 27, "No comment expected at this location"), - LintViolation(7, 27, "No comment expected at this location"), - LintViolation(9, 27, "No comment expected at this location"), - LintViolation(11, 27, "No comment expected at this location"), - ) - } - } - - @Test - fun `Given value argument preceded by a KDOC plus an EOL or BLOCK comments on separate lines above`() { - val code = - """ - class Foo( - /** Some kdoc */ - /* Some comment */ - val bar1: Int, - /** Some kdoc */ - // Some comment - val bar2: Int, - /* Some comment */ - /** Some kdoc */ - val bar3: Int, - // Some comment - /** Some kdoc */ - val bar4: Int, - ) - """.trimIndent() - @Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length") - discouragedCommentLocationRuleAssertThat(code) - .hasLintViolationsWithoutAutoCorrect( - LintViolation(3, 5, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above."), - LintViolation(6, 5, "A (block or EOL) comment inside or on same line after a 'value_parameter' is not allowed. It may be placed on a separate line above."), - ) - } -}