Skip to content

Commit

Permalink
Add new rule for disallowing KDoc at non-whitelisted locations (pinte…
Browse files Browse the repository at this point in the history
…rest#2548)

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 pinterest#2547
  • Loading branch information
paul-dingemans authored 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
Loading

0 comments on commit 7501f72

Please sign in to comment.