Skip to content

Commit

Permalink
Add new rule for disallowing KDoc at non-whitelisted locations (#2548)
Browse files Browse the repository at this point in the history
KDocs should only be used at locations for which it makes sense to provide (e.g. generate) documentation to be used by other consumers.

Closes #2547
  • Loading branch information
paul-dingemans committed Feb 11, 2024
1 parent a6e6b55 commit 7501f72
Show file tree
Hide file tree
Showing 12 changed files with 399 additions and 116 deletions.
2 changes: 1 addition & 1 deletion build-logic/src/main/kotlin/ktlint-publication.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ publishing {
}
}

/**
/*
* Following signing parameters must be configured in `$HOME/.gradle/gradle.properties`:
* ```properties
* signing.keyId=12345678
Expand Down
57 changes: 57 additions & 0 deletions documentation/snapshot/docs/rules/experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,63 @@ Conditions should not use a both `&&` and `||` operators between operators at th

Rule id: `mixed-condition-operators` (`standard` rule set)

## KDoc

KDoc's should only be used on elements for which KDoc is to be transformed to documentation. Normal block comments should be used in other cases.

!!! note:
Access modifiers are ignored. Strictly speaking, one could argue that private declarations should not have a KDoc as no documentation will be generated for it. However, for internal use of developers the KDoc still serves documentation purposes.

=== "[:material-heart:](#) Ktlint"

```kotlin
/** some KDoc */
class FooBar(
/** some KDoc */
val foo: Foo
) {
/**
* Some bar KDoc
*/
constructor() : this()

/** some KDoc */
val bar: Bar
}

enum class Foo {
/** some KDoc */
BAR
}

/** some KDoc */
interface Foo
/** some KDoc */
fun foo()
/** some KDoc */
val foo: Foo
/** some KDoc */
object foo: Foo
/** some KDoc */
typealias FooBar = (Foo) -> Bar
```

=== "[:material-heart-off-outline:](#) Disallowed"

```kotlin
/**
* Some dangling Kdoc (e.g. not followed by a declaration)
*/

val foo /** Some KDoc */ = "foo"

class Foo(
/** some dangling KDoc inside a parameter list */
)
```

Rule id: `kdoc` (`standard` rule set)

## Multiline loop

Braces required for multiline for, while, and do statements.
Expand Down
47 changes: 15 additions & 32 deletions documentation/snapshot/docs/rules/standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -2122,7 +2122,7 @@ 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.
Disallows comments to be placed at certain locations inside a type argument.

=== "[:material-heart:](#) Ktlint"

Expand All @@ -2144,11 +2144,6 @@ Disallows comments to be placed at certain locations inside a type argument (lis
fun Foo<
out Any, // some comment
>.foo() {}
val fooBar: FooBar<
/** some comment */
Foo,
Bar
>
```

!!! note
Expand All @@ -2165,16 +2160,16 @@ 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.
Disallows comments to be placed at certain locations inside a type parameter.

=== "[:material-heart:](#) Ktlint"

```kotlin
class Foo2<
class Foo1<
/* some comment */
out Bar
>
class Foo3<
class Foo2<
// some comment
out Bar
>
Expand All @@ -2183,12 +2178,8 @@ Disallows comments to be placed at certain locations inside a type parameter (li
=== "[:material-heart-off-outline:](#) Disallowed"

```kotlin
class Foo1<
/** some comment */
in Bar
>
class Foo2<in /* some comment */ Bar>
class Foo3<
class Foo1<in /* some comment */ Bar>
class Foo2<
in Bar, // some comment
>
```
Expand Down Expand Up @@ -2225,17 +2216,17 @@ 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.
Disallows comments to be placed at certain locations inside a value argument.

=== "[:material-heart:](#) Ktlint"

```kotlin
val foo2 =
val foo1 =
foo(
/* some comment */
bar = "bar"
)
val foo3 =
val foo2 =
foo(
// some comment
bar = "bar"
Expand All @@ -2245,9 +2236,8 @@ Disallows comments to be placed at certain locations inside a value argument (li
=== "[:material-heart-off-outline:](#) Disallowed"

```kotlin
val foo1 = foo(bar /** some comment */ = "bar")
val foo2 = foo(bar /* some comment */ = "bar")
val foo3 =
val foo1 = foo(bar /* some comment */ = "bar")
val foo2 =
foo(
bar = // some comment
"bar"
Expand All @@ -2268,20 +2258,16 @@ 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.
Disallows comments to be placed at certain locations inside a value argument.

=== "[:material-heart:](#) Ktlint"

```kotlin
class Foo1(
/** some comment */
bar = "bar"
)
class Foo2(
/* some comment */
bar = "bar"
)
class Foo3(
class Foo2(
// some comment
bar = "bar"
)
Expand All @@ -2290,13 +2276,10 @@ Disallows comments to be placed at certain locations inside a value argument (li
=== "[:material-heart-off-outline:](#) Disallowed"

```kotlin
class Foo2(
bar /** some comment */ = "bar"
)
class Foo2(
class Foo1(
bar = /* some comment */ "bar"
)
class Foo3(
class Foo2(
bar =
// some comment
"bar"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public val MAX_LINE_LENGTH_PROPERTY: EditorConfigProperty<Int> =
codeStyleValue.defaultValue()
}

/**
/*
* Internally, Ktlint uses integer 'Int.MAX_VALUE' to indicate that the max line length has to be ignored as this is easier
* in comparisons to check whether the maximum length of a line is exceeded.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ public class KtLintRuleEngine(
ruleExecutionContext.executeRule(rule, true) { offset, errorMessage, canBeAutoCorrected ->
if (canBeAutoCorrected) {
mutated = true
/**
* Rebuild the suppression locator after each change in the AST as the offsets of the
* suppression hints might have changed.
/*
* Rebuild the suppression locator after each change in the AST as the offsets of the suppression hints might
* have changed.
*/
ruleExecutionContext.rebuildSuppressionLocator()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ class KtLintTest {

@Test
fun `Given that format is started using the ruleProviders parameter then NO exception is thrown`() {
/**
/*
* Formatting some code with the [WithStateRule] does not result in a [KtLintRuleException] because [KtLintRuleEngine.format] is
* able to request a new instance of the rule whenever the instance has been used before to traverse the AST.
*/
Expand Down
9 changes: 9 additions & 0 deletions ktlint-ruleset-standard/api/ktlint-ruleset-standard.api
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,15 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/IndentationRuleKt
public static final fun getINDENTATION_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId;
}

public final class com/pinterest/ktlint/ruleset/standard/rules/KdocRule : com/pinterest/ktlint/ruleset/standard/StandardRule {
public fun <init> ()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/KdocRuleKt {
public static final fun getKDOC_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId;
}

public final class com/pinterest/ktlint/ruleset/standard/rules/KdocWrappingRule : com/pinterest/ktlint/ruleset/standard/StandardRule {
public fun <init> ()V
public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;ZLkotlin/jvm/functions/Function3;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.pinterest.ktlint.ruleset.standard.rules.IfElseBracingRule
import com.pinterest.ktlint.ruleset.standard.rules.IfElseWrappingRule
import com.pinterest.ktlint.ruleset.standard.rules.ImportOrderingRule
import com.pinterest.ktlint.ruleset.standard.rules.IndentationRule
import com.pinterest.ktlint.ruleset.standard.rules.KdocRule
import com.pinterest.ktlint.ruleset.standard.rules.KdocWrappingRule
import com.pinterest.ktlint.ruleset.standard.rules.MaxLineLengthRule
import com.pinterest.ktlint.ruleset.standard.rules.MixedConditionOperatorsRule
Expand Down Expand Up @@ -132,6 +133,7 @@ public class StandardRuleSetProvider : RuleSetProviderV3(RuleSetId.STANDARD) {
RuleProvider { IfElseWrappingRule() },
RuleProvider { ImportOrderingRule() },
RuleProvider { IndentationRule() },
RuleProvider { KdocRule() },
RuleProvider { KdocWrappingRule() },
RuleProvider { MaxLineLengthRule() },
RuleProvider { MixedConditionOperatorsRule() },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.pinterest.ktlint.ruleset.standard.rules

import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS
import com.pinterest.ktlint.rule.engine.core.api.ElementType.ENUM_ENTRY
import com.pinterest.ktlint.rule.engine.core.api.ElementType.FILE
import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUN
import com.pinterest.ktlint.rule.engine.core.api.ElementType.KDOC
import com.pinterest.ktlint.rule.engine.core.api.ElementType.OBJECT_DECLARATION
import com.pinterest.ktlint.rule.engine.core.api.ElementType.PROPERTY
import com.pinterest.ktlint.rule.engine.core.api.ElementType.SECONDARY_CONSTRUCTOR
import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPEALIAS
import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER
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.editorconfig.INDENT_SIZE_PROPERTY
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_STYLE_PROPERTY
import com.pinterest.ktlint.ruleset.standard.StandardRule
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet

/**
* Disallow KDoc except of classes, functions and xxx
*/
@SinceKtlint("1.2.0", EXPERIMENTAL)
public class KdocRule :
StandardRule(
id = "kdoc",
usesEditorConfigProperties =
setOf(
INDENT_SIZE_PROPERTY,
INDENT_STYLE_PROPERTY,
),
) {
override fun beforeVisitChildNodes(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
) {
node
.takeIf { it.elementType == KDOC }
?.let {
if (it.treeParent.elementType in allowedParentElementTypes) {
if (it.treeParent.firstChildNode != it) {
emit(
node.startOffset,
"A KDoc is allowed only at start of '${it.treeParent.elementType.debugName.lowercase()}'",
false,
)
}
} else {
if (it.treeParent.elementType == FILE) {
emit(node.startOffset, "A dangling toplevel KDoc is not allowed", false)
} else {
emit(
node.startOffset,
"A KDoc is not allowed inside '${it.treeParent.elementType.debugName.lowercase()}'",
false,
)
}
}
}
}

private companion object {
val allowedParentElementTypes =
TokenSet.create(
CLASS,
ENUM_ENTRY,
FUN,
OBJECT_DECLARATION,
PROPERTY,
SECONDARY_CONSTRUCTOR,
TYPEALIAS,
VALUE_PARAMETER,
)
}
}

public val KDOC_RULE_ID: RuleId = KdocRule().ruleId

0 comments on commit 7501f72

Please sign in to comment.