From 806368898e160d4f05e1b16b4b1d373f4da45cf5 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sat, 22 Jan 2022 12:53:40 +0100 Subject: [PATCH 1/4] Add rule FunctionTypeReferenceSpacingRule (#1341) --- CHANGELOG.md | 1 + .../ExperimentalRuleSetProvider.kt | 3 +- .../FunctionTypeReferenceSpacingRule.kt | 73 ++++++++++++++ .../NoSpacingAfterTypeReferenceRuleTest.kt | 95 +++++++++++++++++++ 4 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/FunctionTypeReferenceSpacingRule.kt create mode 100644 ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d79b84b8d..8ea8f76831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Added - Use Gradle JVM toolchain with language version 8 to compile the project - Basic tests for CLI ([#540](https://github.com/pinterest/ktlint/issues/540)) +- Add experimental rule for unexpected spaces in a type reference before a function identifier (`function-type-reference-spacing`) ([#1341](https://github.com/pinterest/ktlint/issues/1341)) ### Fixed - Fix indentation of function literal ([#1247](https://github.com/pinterest/ktlint/issues/1247)) 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 bf5dce4255..f877a73b8c 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 @@ -20,6 +20,7 @@ public class ExperimentalRuleSetProvider : RuleSetProvider { SpacingBetweenDeclarationsWithAnnotationsRule(), SpacingAroundAngleBracketsRule(), SpacingAroundUnaryOperatorRule(), - AnnotationSpacingRule() + AnnotationSpacingRule(), + FunctionTypeReferenceSpacingRule() ) } diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/FunctionTypeReferenceSpacingRule.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/FunctionTypeReferenceSpacingRule.kt new file mode 100644 index 0000000000..4955b11fa8 --- /dev/null +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/FunctionTypeReferenceSpacingRule.kt @@ -0,0 +1,73 @@ +package com.pinterest.ktlint.ruleset.experimental + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.FUN +import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER +import com.pinterest.ktlint.core.ast.ElementType.NULLABLE_TYPE +import com.pinterest.ktlint.core.ast.ElementType.TYPE_REFERENCE +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 FunctionTypeReferenceSpacingRule : Rule("function-type-reference-spacing") { + override fun visit( + node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit + ) { + if (node.elementType == FUN) { + node + .findTypeReferenceBeforeFunctionIdentifier() + ?.let { typeReference -> + typeReference + .firstChildNode + .takeIf { it.elementType == NULLABLE_TYPE } + ?.let { nullableTypeElement -> + visitNodesUntilIdentifier(nullableTypeElement.firstChildNode, emit, autoCorrect) + } + + if (typeReference.elementType != NULLABLE_TYPE) { + visitNodesUntilIdentifier(typeReference, emit, autoCorrect) + } + } + } + } + + private fun ASTNode.findTypeReferenceBeforeFunctionIdentifier(): ASTNode? { + require(elementType == FUN) + var currentNode: ASTNode? = firstChildNode + while (currentNode != null && currentNode.elementType != IDENTIFIER) { + if (currentNode.elementType == TYPE_REFERENCE) { + return currentNode + } + currentNode = currentNode.nextSibling { true } + } + return null + } + + private fun visitNodesUntilIdentifier( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + var currentNode: ASTNode? = node + while (currentNode != null && currentNode.elementType != IDENTIFIER) { + val nextNode = currentNode.nextSibling { true } + removeIfNonEmptyWhiteSpace(currentNode, emit, autoCorrect) + currentNode = nextNode + } + } + + private fun removeIfNonEmptyWhiteSpace( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean + ) { + if (node.elementType == WHITE_SPACE && node.text.isNotEmpty()) { + emit(node.startOffset, "Unexpected whitespace", true) + if (autoCorrect) { + node.treeParent.removeChild(node) + } + } + } +} diff --git a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt new file mode 100644 index 0000000000..d1e416d6f7 --- /dev/null +++ b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt @@ -0,0 +1,95 @@ +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 +import org.junit.Test + +class NoSpacingAfterTypeReferenceRuleTest { + @Test + fun `Given a function signature with whitespace after a non nullable type reference of an extension function then remove this whitespace`() { + val code = + """ + fun String .foo1() = "some-result" + fun String + .foo2() = "some-result" + """.trimIndent() + val formattedCode = + """ + fun String.foo1() = "some-result" + fun String.foo2() = "some-result" + """.trimIndent() + Assertions.assertThat( + FunctionTypeReferenceSpacingRule().lint(code) + ).containsExactly( + LintError(1, 11, "function-type-reference-spacing", "Unexpected whitespace"), + LintError(2, 11, "function-type-reference-spacing", "Unexpected whitespace"), + ) + Assertions.assertThat( + FunctionTypeReferenceSpacingRule().format(code) + ).isEqualTo(formattedCode) + } + + @Test + fun `Given a function signature with whitespace in a nullable type reference of an extension function`() { + val code = + """ + fun String ?.foo1() = "some-result" + fun String + ?.foo2() = "some-result" + """.trimIndent() + val formattedCode = + """ + fun String?.foo1() = "some-result" + fun String?.foo2() = "some-result" + """.trimIndent() + Assertions.assertThat( + FunctionTypeReferenceSpacingRule().lint(code) + ).containsExactly( + LintError(1, 11, "function-type-reference-spacing", "Unexpected whitespace"), + LintError(2, 11, "function-type-reference-spacing", "Unexpected whitespace"), + ) + Assertions.assertThat( + FunctionTypeReferenceSpacingRule().format(code) + ).isEqualTo(formattedCode) + } + + @Test + fun `Given a function signature with whitespace after a nullable type reference of an extension function`() { + val code = + """ + fun String? .foo1() = "some-result" + fun String? + .foo2() = "some-result" + """.trimIndent() + val formattedCode = + """ + fun String?.foo1() = "some-result" + fun String?.foo2() = "some-result" + """.trimIndent() + Assertions.assertThat( + FunctionTypeReferenceSpacingRule().lint(code) + ).containsExactly( + LintError(1, 12, "function-type-reference-spacing", "Unexpected whitespace"), + LintError(2, 12, "function-type-reference-spacing", "Unexpected whitespace"), + ) + Assertions.assertThat( + FunctionTypeReferenceSpacingRule().format(code) + ).isEqualTo(formattedCode) + } + + @Test + fun `Given a function signature without a type reference before the function name then do not change the signature`() { + val code = + """ + fun foo1() = "some-result" + """.trimIndent() + Assertions.assertThat( + FunctionTypeReferenceSpacingRule().lint(code) + ).isEmpty() + Assertions.assertThat( + FunctionTypeReferenceSpacingRule().format(code) + ).isEqualTo(code) + } +} From dd05f8a44d4c5fd732b681e5cf711e72bc88d7dc Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sat, 22 Jan 2022 17:42:23 +0100 Subject: [PATCH 2/4] Fix lint violation after merge "master" into "1341-function-type-reference-spacing-rule" --- .../experimental/NoSpacingAfterTypeReferenceRuleTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt index d1e416d6f7..62a8cb041d 100644 --- a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt +++ b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt @@ -24,7 +24,7 @@ class NoSpacingAfterTypeReferenceRuleTest { FunctionTypeReferenceSpacingRule().lint(code) ).containsExactly( LintError(1, 11, "function-type-reference-spacing", "Unexpected whitespace"), - LintError(2, 11, "function-type-reference-spacing", "Unexpected whitespace"), + LintError(2, 11, "function-type-reference-spacing", "Unexpected whitespace") ) Assertions.assertThat( FunctionTypeReferenceSpacingRule().format(code) @@ -48,7 +48,7 @@ class NoSpacingAfterTypeReferenceRuleTest { FunctionTypeReferenceSpacingRule().lint(code) ).containsExactly( LintError(1, 11, "function-type-reference-spacing", "Unexpected whitespace"), - LintError(2, 11, "function-type-reference-spacing", "Unexpected whitespace"), + LintError(2, 11, "function-type-reference-spacing", "Unexpected whitespace") ) Assertions.assertThat( FunctionTypeReferenceSpacingRule().format(code) @@ -72,7 +72,7 @@ class NoSpacingAfterTypeReferenceRuleTest { FunctionTypeReferenceSpacingRule().lint(code) ).containsExactly( LintError(1, 12, "function-type-reference-spacing", "Unexpected whitespace"), - LintError(2, 12, "function-type-reference-spacing", "Unexpected whitespace"), + LintError(2, 12, "function-type-reference-spacing", "Unexpected whitespace") ) Assertions.assertThat( FunctionTypeReferenceSpacingRule().format(code) From 8ff48ac70aa0b29d73974bc068ed544481f30bd9 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sat, 5 Feb 2022 20:45:53 +0100 Subject: [PATCH 3/4] Update readme for new rule "experimental:function-type-reference-spacing" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa9c4d7e03..9e4b1e1283 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ by passing the `--experimental` flag to `ktlint`. - No spaces around unary operators (id: `experimental:unary-op-spacing`) - Declarations with annotations should be separated by a blank line (id: `experimental:spacing-between-declarations-with-annotations`) - Declarations with comments should be separated by a blank line (id: `experimental:spacing-between-declarations-with-comments`) - +- Unnecessary spacing in the type reference before a function (`experimental:function-type-reference-spacing`) ## EditorConfig From 6ada484782563a5ff4529f870e986a5a228615f3 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Tue, 8 Mar 2022 20:35:08 +0100 Subject: [PATCH 4/4] Migrate to JUnit5 --- .../ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt index 62a8cb041d..e6f595fb50 100644 --- a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt +++ b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/NoSpacingAfterTypeReferenceRuleTest.kt @@ -4,7 +4,7 @@ import com.pinterest.ktlint.core.LintError import com.pinterest.ktlint.test.format import com.pinterest.ktlint.test.lint import org.assertj.core.api.Assertions -import org.junit.Test +import org.junit.jupiter.api.Test class NoSpacingAfterTypeReferenceRuleTest { @Test