From 5b9587de3f2636b02ba0d146ab2334a5e1d94b8b Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Sun, 13 Mar 2022 17:55:05 +0100 Subject: [PATCH] Enforce consistent spacing (e.g. no whitespace element at all) between function name and the opening parenthesis Includes cleanup of AnnotationSpacingRuleTest as it broke due to existence of test which was not related to annotation spacing. Also removed the unneeded blank final line in code examples which are linted and formatted. This rule is required for implementing the function signature rewrite rule #1341 --- CHANGELOG.md | 16 +++- README.md | 1 + .../ExperimentalRuleSetProvider.kt | 3 +- ...enFunctionNameAndOpeningParenthesisRule.kt | 27 ++++++ .../experimental/AnnotationSpacingRuleTest.kt | 94 ------------------- ...nctionNameAndOpeningParenthesisRuleTest.kt | 52 ++++++++++ 6 files changed, 97 insertions(+), 96 deletions(-) create mode 100644 ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenFunctionNameAndOpeningParenthesisRule.kt create mode 100644 ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenFunctionNameAndOpeningParenthesisRuleTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ddb75cfa8..b3538a160b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,21 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/). -## Unreleased +## Unreleased (0.46.0) + +### API Changes & RuleSet providers + +### Added +- Add experimental rule for unexpected spacing between function name and opening parenthesis (`spacing-between-function-name-and-opening-parenthesis`) ([#1341](https://github.com/pinterest/ktlint/issues/1341)) + +### Fixed + +### Changed + +### Removed + + +## Unreleased (0.45.0) ### API Changes & RuleSet providers diff --git a/README.md b/README.md index 89d066b56f..1faba4d1c5 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ by passing the `--experimental` flag to `ktlint`. - `experimental:annotation-spacing`: Annotations should be separated by the annotated declaration by a single line break - `experimental:double-colon-spacing`: No spaces around `::` - `experimental:fun-keyword-spacing`: Consistent spacing after the fun keyword +- `experimental:spacing-between-function-name-and-opening-parenthesis`: Consistent spacing between function name and opening parenthesis - `experimental:function-type-reference-spacing`: Consistent spacing in the type reference before a function - `experimental:modifier-list-spacing`: Consistent spacing between modifiers in and after the last modifier in a modifier list - `experimental:spacing-around-angle-brackets`: No spaces around angle brackets diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ExperimentalRuleSetProvider.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ExperimentalRuleSetProvider.kt index 7a4f4decab..08e37f945a 100644 --- a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ExperimentalRuleSetProvider.kt +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ExperimentalRuleSetProvider.kt @@ -30,6 +30,7 @@ public class ExperimentalRuleSetProvider : RuleSetProvider { FunctionTypeReferenceSpacingRule(), ModifierListSpacingRule(), CommentWrappingRule(), - KdocWrappingRule() + KdocWrappingRule(), + SpacingBetweenFunctionNameAndOpeningParenthesisRule() ) } diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenFunctionNameAndOpeningParenthesisRule.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenFunctionNameAndOpeningParenthesisRule.kt new file mode 100644 index 0000000000..ab5dbe71b8 --- /dev/null +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenFunctionNameAndOpeningParenthesisRule.kt @@ -0,0 +1,27 @@ +package com.pinterest.ktlint.ruleset.experimental + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType +import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE +import com.pinterest.ktlint.core.ast.nextSibling +import org.jetbrains.kotlin.com.intellij.lang.ASTNode + +public class SpacingBetweenFunctionNameAndOpeningParenthesisRule : Rule("spacing-between-function-name-and-opening-parenthesis") { + override fun visit( + node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit + ) { + node + .takeIf { node.elementType == ElementType.FUN } + ?.findChildByType(ElementType.IDENTIFIER) + ?.nextSibling { true } + ?.takeIf { it.elementType == WHITE_SPACE } + ?.let { whiteSpace -> + emit(whiteSpace.startOffset, "Unexpected whitespace", true) + if (autoCorrect) { + whiteSpace.treeParent.removeChild(whiteSpace) + } + } + } +} diff --git a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/AnnotationSpacingRuleTest.kt b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/AnnotationSpacingRuleTest.kt index a1e37429a5..221f6b86a6 100644 --- a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/AnnotationSpacingRuleTest.kt +++ b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/AnnotationSpacingRuleTest.kt @@ -1,15 +1,12 @@ package com.pinterest.ktlint.ruleset.experimental -import com.pinterest.ktlint.core.KtLint import com.pinterest.ktlint.core.LintError import com.pinterest.ktlint.test.format import com.pinterest.ktlint.test.lint -import java.util.ArrayList import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class AnnotationSpacingRuleTest { - @Test fun `lint no empty lines between an annotation and object`() { assertThat( @@ -17,7 +14,6 @@ class AnnotationSpacingRuleTest { """ @JvmField fun foo() {} - """.trimIndent() ) ).isEmpty() @@ -31,7 +27,6 @@ class AnnotationSpacingRuleTest { @JvmField fun foo() {} - """.trimIndent() ) ).isEqualTo( @@ -48,7 +43,6 @@ class AnnotationSpacingRuleTest { @JvmField fun foo() {} - """.trimIndent() assertThat( @@ -57,7 +51,6 @@ class AnnotationSpacingRuleTest { """ @JvmField fun foo() {} - """.trimIndent() ) } @@ -72,7 +65,6 @@ class AnnotationSpacingRuleTest { @JvmStatic val r = A() } - """.trimIndent() assertThat( @@ -84,7 +76,6 @@ class AnnotationSpacingRuleTest { @JvmStatic val r = A() } - """.trimIndent() ) } @@ -98,7 +89,6 @@ class AnnotationSpacingRuleTest { fun foo() {} - """.trimIndent() assertThat( @@ -107,7 +97,6 @@ class AnnotationSpacingRuleTest { """ @JvmField fun foo() {} - """.trimIndent() ) } @@ -117,7 +106,6 @@ class AnnotationSpacingRuleTest { val code = """ @JvmField fun foo() {} - """.trimIndent() assertThat( @@ -125,7 +113,6 @@ class AnnotationSpacingRuleTest { ).isEqualTo( """ @JvmField fun foo() {} - """.trimIndent() ) } @@ -137,7 +124,6 @@ class AnnotationSpacingRuleTest { @JvmField @JvmStatic fun foo() = Unit - """.trimIndent() assertThat( @@ -146,7 +132,6 @@ class AnnotationSpacingRuleTest { """ @JvmField @JvmStatic fun foo() = Unit - """.trimIndent() ) } @@ -159,7 +144,6 @@ class AnnotationSpacingRuleTest { @JvmStatic fun foo() = Unit - """.trimIndent() assertThat( @@ -169,7 +153,6 @@ class AnnotationSpacingRuleTest { @JvmField @JvmStatic fun foo() = Unit - """.trimIndent() ) } @@ -183,7 +166,6 @@ class AnnotationSpacingRuleTest { @JvmName @JvmStatic fun foo() = Unit - """.trimIndent() assertThat( @@ -193,7 +175,6 @@ class AnnotationSpacingRuleTest { @JvmField @JvmName @JvmStatic fun foo() = Unit - """.trimIndent() ) } @@ -211,7 +192,6 @@ class AnnotationSpacingRuleTest { fun foo() = Unit - """.trimIndent() assertThat( @@ -222,7 +202,6 @@ class AnnotationSpacingRuleTest { @JvmName @JvmStatic fun foo() = Unit - """.trimIndent() ) } @@ -240,7 +219,6 @@ class AnnotationSpacingRuleTest { val foo = Foo() } - """.trimIndent() assertThat( @@ -252,82 +230,10 @@ class AnnotationSpacingRuleTest { @JvmStatic val foo = Foo() } - """.trimIndent() ) } - @Test - fun `lint there should not be an error on multiple lines assertion while additional formatting ongoing`() { - val code = - """ - package a.b.c - - class Test { - fun bloop() { - asdfadsf(asdfadsf, asdfasdf, asdfasdfasdfads, - asdfasdf, asdfasdf, asdfasdf) - } - - @Blah - val test: Int - } - - """.trimIndent() - assertThat( - AnnotationSpacingRule().format(code) - ).isEqualTo( - """ - package a.b.c - - class Test { - fun bloop() { - asdfadsf(asdfadsf, asdfasdf, asdfasdfasdfads, - asdfasdf, asdfasdf, asdfasdf) - } - - @Blah - val test: Int - } - - """.trimIndent() - ) - } - - @Test - fun `lint there should not be an error on multiline assertion while additional formatting ongoing from file`() { - val code = - """ - package a.b.c - - class Test { - fun bloop() { - asdfadsf(asdfadsf, asdfasdf, asdfasdfasdfads, - asdfasdf, asdfasdf, asdfasdf) - } - - @Blah - val test: Int - } - - """.trimIndent() - assertThat( - ArrayList().apply { - KtLint.lint( - KtLint.Params( - text = code, - ruleSets = mutableListOf( - ExperimentalRuleSetProvider().get() - ), - cb = { e, _ -> add(e) } - ) - ) - } - ).allMatch { - it.ruleId == "experimental:argument-list-wrapping" - } - } - @Test fun `annotations should not be separated by comments from the annotated construct`() { val code = diff --git a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenFunctionNameAndOpeningParenthesisRuleTest.kt b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenFunctionNameAndOpeningParenthesisRuleTest.kt new file mode 100644 index 0000000000..5afa4aa7ea --- /dev/null +++ b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/SpacingBetweenFunctionNameAndOpeningParenthesisRuleTest.kt @@ -0,0 +1,52 @@ +package com.pinterest.ktlint.ruleset.experimental + +import com.pinterest.ktlint.core.LintError +import com.pinterest.ktlint.test.format +import com.pinterest.ktlint.test.lint +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class SpacingBetweenFunctionNameAndOpeningParenthesisRuleTest { + @Test + fun `Given a function signature without whitespace between function name and opening parenthesis then do not reformat`() { + val code = + """ + fun foo() = "foo" + """.trimIndent() + assertThat(SpacingBetweenFunctionNameAndOpeningParenthesisRule().lint(code)).isEmpty() + assertThat(SpacingBetweenFunctionNameAndOpeningParenthesisRule().format(code)).isEqualTo(code) + } + + @Test + fun `Given a function signature with one or more spaces between function name and opening parenthesis then do not reformat`() { + val code = + """ + fun foo () = "foo" + """.trimIndent() + val formattedCode = + """ + fun foo() = "foo" + """.trimIndent() + assertThat(SpacingBetweenFunctionNameAndOpeningParenthesisRule().lint(code)).containsExactly( + LintError(1, 8, "spacing-between-function-name-and-opening-parenthesis", "Unexpected whitespace") + ) + assertThat(SpacingBetweenFunctionNameAndOpeningParenthesisRule().format(code)).isEqualTo(formattedCode) + } + + @Test + fun `Given a function signature with one or more new lines between function name and opening parenthesis then do not reformat`() { + val code = + """ + fun foo + () = "foo" + """.trimIndent() + val formattedCode = + """ + fun foo() = "foo" + """.trimIndent() + assertThat(SpacingBetweenFunctionNameAndOpeningParenthesisRule().lint(code)).containsExactly( + LintError(1, 8, "spacing-between-function-name-and-opening-parenthesis", "Unexpected whitespace") + ) + assertThat(SpacingBetweenFunctionNameAndOpeningParenthesisRule().format(code)).isEqualTo(formattedCode) + } +}