Skip to content

Commit

Permalink
Insert suppression for class-signature, function-signature and parame…
Browse files Browse the repository at this point in the history
…ter-list-wrapping at a class or function level

Closes #2588
  • Loading branch information
paul-dingemans committed Mar 31, 2024
1 parent 41ae540 commit 1188519
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 8 deletions.
Expand Up @@ -3,8 +3,10 @@ package com.pinterest.ktlint.rule.engine.api
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]
Expand Down Expand Up @@ -39,13 +41,14 @@ private fun ASTNode.findLeafElementAt(suppression: KtlintSuppression): ASTNode =

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)
}
Expand Down
@@ -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?
}
Expand Up @@ -3,9 +3,12 @@ package com.pinterest.ktlint.rule.engine.api
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
Expand Down Expand Up @@ -362,6 +365,85 @@ class KtlintRuleEngineSuppressionKtTest {
}
}

@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")
}
Expand Down
Expand Up @@ -9,7 +9,7 @@ import org.assertj.core.api.Assertions.assertThat
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
Expand Down

0 comments on commit 1188519

Please sign in to comment.