Insert suppression for class-signature, function-signature and parameter-list-wrapping at a class or function level
Closes #2588
diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtlintRuleEngineSuppression.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtlintRuleEngineSuppression.kt
index 8ba6536..9a4a275 100644
--- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtlintRuleEngineSuppression.kt
+++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/api/KtlintRuleEngineSuppression.kt
@@ -3,8 +3,10 @@
import com.pinterest.ktlint.rule.engine.core.api.RuleId
import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace
import com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext
+import com.pinterest.ktlint.rule.engine.internal.findSuppressionTargetNodeFinder
import com.pinterest.ktlint.rule.engine.internal.insertKtlintRuleSuppression
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.utils.addToStdlib.applyIf
/**
* A [Suppress] annotation can only be inserted at specific locations. This function is intended for API Consumers. It updates given [code]
@@ -39,13 +41,14 @@
is KtlintSuppressionAtOffset ->
findLeafElementAt(suppression.offsetFromStartOf(text))
- ?.let {
- if (it.isWhiteSpace()) {
- // A suppression can not be added at a whitespace element. Insert it at the parent instead
- it.treeParent
- } else {
- it
- }
+ ?.let { leafElement ->
+ leafElement
+ .applyIf(leafElement.isWhiteSpace()) {
+ // A suppression can not be added at a whitespace element. Insert it at the parent instead
+ leafElement.treeParent
+ }
+ }?.let { leafElement ->
+ suppression.findSuppressionTargetNodeFinder().findSuppressionTargetNode(leafElement)
}
?: throw KtlintSuppressionNoElementFoundException(suppression)
}
diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppressionTargetNodeFinder.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppressionTargetNodeFinder.kt
new file mode 100644
index 0000000..2e16a7d
--- /dev/null
+++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppressionTargetNodeFinder.kt
@@ -0,0 +1,52 @@
+package com.pinterest.ktlint.rule.engine.internal
+
+import com.pinterest.ktlint.rule.engine.api.KtlintSuppression
+import com.pinterest.ktlint.rule.engine.core.api.ElementType
+import com.pinterest.ktlint.rule.engine.core.api.RuleId
+import com.pinterest.ktlint.rule.engine.core.api.parent
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+
+private class DefaultSuppressionTargetNodeFinder : SuppressionTargetNodeFinder {
+ override fun findSuppressionTargetNode(astNode: ASTNode): ASTNode = astNode
+}
+
+private val defaultSuppressionTargetNodeFinder = DefaultSuppressionTargetNodeFinder()
+
+private class FunctionSuppressionTargetNodeFinder : SuppressionTargetNodeFinder {
+ override fun findSuppressionTargetNode(astNode: ASTNode) =
+ if (astNode.elementType == ElementType.FUN) {
+ astNode
+ } else {
+ astNode.parent { it.elementType == ElementType.FUN }
+ }
+}
+
+private val functionSuppressionTargetNodeFinder = FunctionSuppressionTargetNodeFinder()
+
+private class ClassSuppressionTargetNodeFinder : SuppressionTargetNodeFinder {
+ override fun findSuppressionTargetNode(astNode: ASTNode) =
+ if (astNode.elementType == ElementType.CLASS) {
+ astNode
+ } else {
+ astNode.parent { it.elementType == ElementType.CLASS }
+ }
+}
+
+private val classSuppressionTargetNodeFinder = ClassSuppressionTargetNodeFinder()
+
+// TODO: Decide in Ktlint 2.x whether it is worth to move the SuppressionTargetNodeFinder into the Rule class. The KtlintRuleEngine should
+// not have any knowledge about how to suppress specific rules.
+private val ruleSuppressionTargetNodeFinder: Map<RuleId, SuppressionTargetNodeFinder> =
+ mapOf(
+ RuleId("standard:class-signature") to classSuppressionTargetNodeFinder,
+ RuleId("standard:function-signature") to functionSuppressionTargetNodeFinder,
+ RuleId("standard:parameter-list-wrapping") to functionSuppressionTargetNodeFinder,
+ )
+
+internal fun KtlintSuppression.findSuppressionTargetNodeFinder() =
+ ruleSuppressionTargetNodeFinder[ruleId]
+ ?: defaultSuppressionTargetNodeFinder
+
+internal interface SuppressionTargetNodeFinder {
+ fun findSuppressionTargetNode(astNode: ASTNode): ASTNode?
+}
diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtlintRuleEngineSuppressionKtTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtlintRuleEngineSuppressionKtTest.kt
index c196f8f..69ead82 100644
--- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtlintRuleEngineSuppressionKtTest.kt
+++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtlintRuleEngineSuppressionKtTest.kt
@@ -3,9 +3,12 @@
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.rules.CLASS_SIGNATURE_RULE_ID
import com.pinterest.ktlint.ruleset.standard.rules.CONDITION_WRAPPING_RULE_ID
+import com.pinterest.ktlint.ruleset.standard.rules.FUNCTION_SIGNATURE_RULE_ID
import com.pinterest.ktlint.ruleset.standard.rules.NO_CONSECUTIVE_BLANK_LINES_RULE_ID
import com.pinterest.ktlint.ruleset.standard.rules.NO_LINE_BREAK_BEFORE_ASSIGNMENT_RULE_ID
+import com.pinterest.ktlint.ruleset.standard.rules.PARAMETER_LIST_WRAPPING_RULE_ID
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
@@ -362,6 +365,85 @@
}
}
+ @Test
+ fun `Issue 2588 - Given a class-signature violation then add the suppression at the class`() {
+ val code =
+ """
+ class FooBar : Foo, Bar {
+ // some body
+ }
+ """.trimIndent()
+ val formattedCode =
+ """
+ @Suppress("ktlint:standard:class-signature")
+ class FooBar : Foo, Bar {
+ // some body
+ }
+ """.trimIndent()
+ val actual =
+ ktLintRuleEngine
+ .insertSuppression(
+ Code.fromSnippet(code, false),
+ KtlintSuppressionAtOffset(1, 16, CLASS_SIGNATURE_RULE_ID),
+ )
+
+ assertThat(actual).isEqualTo(formattedCode)
+ }
+
+ @Test
+ fun `Issue 2588 - Given a function-signature violation then add the suppression at the function`() {
+ val code =
+ """
+ fun foo(
+ row1: Int, col1: Int,
+ row2: Int, col2: Int,
+ ) = "foo"
+ """.trimIndent()
+ val formattedCode =
+ """
+ @Suppress("ktlint:standard:function-signature")
+ fun foo(
+ row1: Int, col1: Int,
+ row2: Int, col2: Int,
+ ) = "foo"
+ """.trimIndent()
+ val actual =
+ ktLintRuleEngine
+ .insertSuppression(
+ Code.fromSnippet(code, false),
+ KtlintSuppressionAtOffset(2, 15, FUNCTION_SIGNATURE_RULE_ID),
+ )
+
+ assertThat(actual).isEqualTo(formattedCode)
+ }
+
+ @Test
+ fun `Issue 2588 - Given a parameter-list-wrapping violation then add the suppression at the function`() {
+ val code =
+ """
+ fun foo(
+ row1: Int, col1: Int,
+ row2: Int, col2: Int,
+ ) = "foo"
+ """.trimIndent()
+ val formattedCode =
+ """
+ @Suppress("ktlint:standard:parameter-list-wrapping")
+ fun foo(
+ row1: Int, col1: Int,
+ row2: Int, col2: Int,
+ ) = "foo"
+ """.trimIndent()
+ val actual =
+ ktLintRuleEngine
+ .insertSuppression(
+ Code.fromSnippet(code, false),
+ KtlintSuppressionAtOffset(2, 15, PARAMETER_LIST_WRAPPING_RULE_ID),
+ )
+
+ assertThat(actual).isEqualTo(formattedCode)
+ }
+
private companion object {
val SOME_RULE_ID = RuleId("standard:some-rule-id")
}
diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppressionKtTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppressionTest.kt
similarity index 99%
rename from ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppressionKtTest.kt
rename to ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppressionTest.kt
index 85630d7..9a75334 100644
--- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppressionKtTest.kt
+++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/internal/KtlintSuppressionTest.kt
@@ -9,7 +9,7 @@
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
-class KtlintSuppressionKtTest {
+class KtlintSuppressionTest {
@Nested
inner class `Given a file suppression to be inserted` {
@Test