Skip to content

Commit

Permalink
Add function-type-modifier-spacing rule (#2216)
Browse files Browse the repository at this point in the history
Closes #2202

Co-authored-by: paul-dingemans <paul-dingemans@users.noreply.github.com>
  • Loading branch information
t-kameyama and paul-dingemans authored Aug 28, 2023
1 parent be54aa4 commit 231ad6f
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ If an `EditorConfigProperty` is defined in a `Rule` that is only provided via a
* Add experimental rule `function-expression-body`. This rule rewrites function bodies only contain a `return` or `throw` expression to an expression body. [#2150](https://github.com/pinterest/ktlint/issues/2150)
* Add new experimental rule `statement-wrapping` which ensures function, class, or other blocks statement body doesn't start or end at starting or ending braces of the block ([#1938](https://github.com/pinterest/ktlint/issues/1938)). This rule was added in `0.50` release, but was never executed outside the unit tests. The rule is now added to the `StandardRuleSetProvider` ([#2170](https://github.com/pinterest/ktlint/issues/2170))
* Add experimental rule `chain-method-continuation` to the `ktlint_official` code style, but it can be enabled explicitly for the other code styles as well. This rule requires the operators (`.` or `?.`) for chaining method calls, to be aligned with each other. This rule is enabled by ([#1953](https://github.com/pinterest/ktlint/issues/1953))
* Add experimental rule `function-type-modifier-spacing`. This rule enforces a single whitespace between the modifier list and the function type. [#2202](https://github.com/pinterest/ktlint/issues/2202)
* Add EditorConfigPropertyRegistry to assist API Consumers that load rulesets at runtime to define the EditorConfigOverride ([#2190](https://github.com/pinterest/ktlint/issues/2190))

### Removed
Expand Down
20 changes: 20 additions & 0 deletions documentation/snapshot/docs/rules/experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,26 @@ Rule id: `no-single-line-block-comment` (`standard` rule set)

## Spacing

### Function type modifier spacing

Enforce a single whitespace between the modifier list and the function type.

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

```kotlin
val foo: suspend () -> Unit = {}
suspend fun bar(baz: suspend () -> Unit) = baz()
```

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

```kotlin
val foo: suspend() -> Unit = {}
suspend fun bar(baz: suspend () -> Unit) = baz()
```

Rule id: `function-type-modifier-spacing` (`standard` rule set)

### No blank lines in list

Disallow blank lines to be used in lists before the first element, between elements, and after the last element.
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 @@ -272,6 +272,15 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionStartOfBo
public static final fun getFUNCTION_START_OF_BODY_SPACING_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId;
}

public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeModifierSpacingRule : com/pinterest/ktlint/ruleset/standard/StandardRule, com/pinterest/ktlint/rule/engine/core/api/Rule$Experimental {
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/FunctionTypeModifierSpacingRuleKt {
public static final fun getFUNCTION_TYPE_MODIFIER_SPACING_RULE ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId;
}

public final class com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeReferenceSpacingRule : 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 @@ -28,6 +28,7 @@ import com.pinterest.ktlint.ruleset.standard.rules.FunctionNamingRule
import com.pinterest.ktlint.ruleset.standard.rules.FunctionReturnTypeSpacingRule
import com.pinterest.ktlint.ruleset.standard.rules.FunctionSignatureRule
import com.pinterest.ktlint.ruleset.standard.rules.FunctionStartOfBodySpacingRule
import com.pinterest.ktlint.ruleset.standard.rules.FunctionTypeModifierSpacingRule
import com.pinterest.ktlint.ruleset.standard.rules.FunctionTypeReferenceSpacingRule
import com.pinterest.ktlint.ruleset.standard.rules.IfElseBracingRule
import com.pinterest.ktlint.ruleset.standard.rules.IfElseWrappingRule
Expand Down Expand Up @@ -116,6 +117,7 @@ public class StandardRuleSetProvider : RuleSetProviderV3(RuleSetId.STANDARD) {
RuleProvider { FunctionReturnTypeSpacingRule() },
RuleProvider { FunctionSignatureRule() },
RuleProvider { FunctionStartOfBodySpacingRule() },
RuleProvider { FunctionTypeModifierSpacingRule() },
RuleProvider { FunctionTypeReferenceSpacingRule() },
RuleProvider { FunKeywordSpacingRule() },
RuleProvider { IfElseBracingRule() },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.pinterest.ktlint.ruleset.standard.rules

import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUNCTION_TYPE
import com.pinterest.ktlint.rule.engine.core.api.ElementType.MODIFIER_LIST
import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHITE_SPACE
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.nextCodeSibling
import com.pinterest.ktlint.rule.engine.core.api.prevSibling
import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe
import com.pinterest.ktlint.ruleset.standard.StandardRule
import org.jetbrains.kotlin.com.intellij.lang.ASTNode

/**
* Lints and formats a single space between the modifier list and the function type
*/
public class FunctionTypeModifierSpacingRule :
StandardRule("function-type-modifier-spacing"),
Rule.Experimental {
override fun beforeVisitChildNodes(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
) {
node
.takeIf { it.elementType == MODIFIER_LIST }
?.nextCodeSibling()
?.takeIf { it.elementType == FUNCTION_TYPE }
?.takeUnless { it.isPrecededBySingleSpace() }
?.let { functionTypeNode ->
emit(functionTypeNode.startOffset, "Expected a single space between the modifier list and the function type", true)
if (autoCorrect) {
functionTypeNode.upsertWhitespaceBeforeMe(" ")
}
}
}

private fun ASTNode.isPrecededBySingleSpace(): Boolean =
prevSibling()
?.let { it.elementType == WHITE_SPACE && it.text == " " }
?: false
}

public val FUNCTION_TYPE_MODIFIER_SPACING_RULE: RuleId = FunctionTypeModifierSpacingRule().ruleId
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.pinterest.ktlint.ruleset.standard.rules

import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test

class FunctionTypeModifierSpacingRuleTest {
private val assertThatRule = assertThatRule { FunctionTypeModifierSpacingRule() }

@Nested
inner class `Missing space before the function type` {
@Test
fun `Given no space between the modifier list and the function property type`() {
val code =
"""
val foo: suspend() -> Unit = {}
""".trimIndent()
val formattedCode =
"""
val foo: suspend () -> Unit = {}
""".trimIndent()
assertThatRule(code)
.hasLintViolation(1, 17, "Expected a single space between the modifier list and the function type")
.isFormattedAs(formattedCode)
}

@Test
fun `Given no space between the modifier list and the function parameter type`() {
val code =
"""
suspend fun bar(baz: suspend() -> Unit) = baz()
""".trimIndent()
val formattedCode =
"""
suspend fun bar(baz: suspend () -> Unit) = baz()
""".trimIndent()
assertThatRule(code)
.hasLintViolation(1, 29, "Expected a single space between the modifier list and the function type")
.isFormattedAs(formattedCode)
}
}

@Nested
inner class `Exactly one space before the function type` {
@Test
fun `Given a single space between the modifier list and the function property type`() {
val code =
"""
val foo: suspend () -> Unit = {}
""".trimIndent()
assertThatRule(code).hasNoLintViolations()
}

@Test
fun `Given a single space between the modifier list and the function parameter type`() {
val code =
"""
suspend fun bar(baz: suspend () -> Unit) = baz()
""".trimIndent()
assertThatRule(code).hasNoLintViolations()
}
}

@Nested
inner class `Too many spaces before the function type` {
@Test
fun `Given multiple spaces between the modifier list and the function property type`() {
val code =
"""
val foo: suspend () -> Unit = {}
""".trimIndent()
val formattedCode =
"""
val foo: suspend () -> Unit = {}
""".trimIndent()
assertThatRule(code)
.hasLintViolation(1, 19, "Expected a single space between the modifier list and the function type")
.isFormattedAs(formattedCode)
}

@Test
fun `Given multiple spaces between the modifier list and the function parameter type`() {
val code =
"""
suspend fun bar(baz: suspend () -> Unit) = baz()
""".trimIndent()
val formattedCode =
"""
suspend fun bar(baz: suspend () -> Unit) = baz()
""".trimIndent()
assertThatRule(code)
.hasLintViolation(1, 33, "Expected a single space between the modifier list and the function type")
.isFormattedAs(formattedCode)
}
}

@Nested
inner class `Given unexpected newline before the function type` {
@Test
fun `Given unexpected newline between the modifier list and the function property type`() {
val code =
"""
val foo: suspend
() -> Unit = {}
""".trimIndent()
val formattedCode =
"""
val foo: suspend () -> Unit = {}
""".trimIndent()
assertThatRule(code)
.hasLintViolation(2, 10, "Expected a single space between the modifier list and the function type")
.isFormattedAs(formattedCode)
}

@Test
fun `Given unexpected newline between the modifier list and the function parameter type`() {
val code =
"""
suspend fun bar(
baz: suspend
() -> Unit
) = baz()
""".trimIndent()
val formattedCode =
"""
suspend fun bar(
baz: suspend () -> Unit
) = baz()
""".trimIndent()
assertThatRule(code)
.hasLintViolation(3, 10, "Expected a single space between the modifier list and the function type")
.isFormattedAs(formattedCode)
}
}
}

0 comments on commit 231ad6f

Please # to comment.