Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add function-type-modifier-spacing rule #2216

Merged
merged 5 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}
}