Skip to content

Commit

Permalink
Add rule FunctionTypeReferenceSpacingRule (pinterest#1341)
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Dingemans committed Jan 22, 2022
1 parent 77c60e5 commit 8063688
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class ExperimentalRuleSetProvider : RuleSetProvider {
SpacingBetweenDeclarationsWithAnnotationsRule(),
SpacingAroundAngleBracketsRule(),
SpacingAroundUnaryOperatorRule(),
AnnotationSpacingRule()
AnnotationSpacingRule(),
FunctionTypeReferenceSpacingRule()
)
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit 8063688

Please sign in to comment.