Skip to content

Commit

Permalink
Fix false positive FunctionTypeReferenceSpacingRule in anonymous func…
Browse files Browse the repository at this point in the history
…tion

Rule should not depend on the existence of the function identifier
as it is also possible to define anonymous functions. Checking of
anonymous and named function should be identical.

Closes pinterest#1440
  • Loading branch information
paul-dingemans committed Apr 2, 2022
1 parent 7487b47 commit 12eb688
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ 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.VALUE_PARAMETER_LIST
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import com.pinterest.ktlint.core.ast.nextSibling
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
Expand All @@ -17,26 +17,26 @@ public class FunctionTypeReferenceSpacingRule : Rule("function-type-reference-sp
) {
if (node.elementType == FUN) {
node
.findTypeReferenceBeforeFunctionIdentifier()
.findFunctionReceiverTypeReference()
?.let { typeReference ->
typeReference
.firstChildNode
.takeIf { it.elementType == NULLABLE_TYPE }
?.let { nullableTypeElement ->
visitNodesUntilIdentifier(nullableTypeElement.firstChildNode, emit, autoCorrect)
visitNodesUntilStartOfValueParameterList(nullableTypeElement.firstChildNode, emit, autoCorrect)
}

if (typeReference.elementType != NULLABLE_TYPE) {
visitNodesUntilIdentifier(typeReference, emit, autoCorrect)
visitNodesUntilStartOfValueParameterList(typeReference, emit, autoCorrect)
}
}
}
}

private fun ASTNode.findTypeReferenceBeforeFunctionIdentifier(): ASTNode? {
private fun ASTNode.findFunctionReceiverTypeReference(): ASTNode? {
require(elementType == FUN)
var currentNode: ASTNode? = firstChildNode
while (currentNode != null && currentNode.elementType != IDENTIFIER) {
while (currentNode != null && currentNode.elementType != VALUE_PARAMETER_LIST) {
if (currentNode.elementType == TYPE_REFERENCE) {
return currentNode
}
Expand All @@ -45,13 +45,13 @@ public class FunctionTypeReferenceSpacingRule : Rule("function-type-reference-sp
return null
}

private fun visitNodesUntilIdentifier(
private fun visitNodesUntilStartOfValueParameterList(
node: ASTNode,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
autoCorrect: Boolean
) {
var currentNode: ASTNode? = node
while (currentNode != null && currentNode.elementType != IDENTIFIER) {
while (currentNode != null && currentNode.elementType != VALUE_PARAMETER_LIST) {
val nextNode = currentNode.nextSibling { true }
removeIfNonEmptyWhiteSpace(currentNode, emit, autoCorrect)
currentNode = nextNode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,54 @@ class FunctionTypeReferenceSpacingRuleTest {
FunctionTypeReferenceSpacingRule().format(code)
).isEqualTo(code)
}

@Test
fun `Issue 1440 - Given an anonymous function without receiver type then do not reformat`() {
val code =
"""
val anonymousFunction = fun(foo: Boolean): String? = if (foo) "Test string" else null
""".trimIndent()
Assertions.assertThat(
FunctionTypeReferenceSpacingRule().lint(code)
).isEmpty()
Assertions.assertThat(
FunctionTypeReferenceSpacingRule().format(code)
).isEqualTo(code)
}

@Test
fun `Given an anonymous function with receiver type then do not reformat`() {
val code =
"""
val anonymousFunction = fun Boolean.(): String? = if (this) "Test string" else null
""".trimIndent()
Assertions.assertThat(
FunctionTypeReferenceSpacingRule().lint(code)
).isEmpty()
Assertions.assertThat(
FunctionTypeReferenceSpacingRule().format(code)
).isEqualTo(code)
}

@Test
fun `Given an anonymous function with receiver type followed by an unexpected space then do reformat`() {
val code =
"""
val anonymousFunction = fun Boolean ? . (): String? = this?.let { "Test string" }
""".trimIndent()
val formattedCode =
"""
val anonymousFunction = fun Boolean?.(): String? = this?.let { "Test string" }
""".trimIndent()
Assertions.assertThat(
FunctionTypeReferenceSpacingRule().lint(code)
).containsExactly(
LintError(1, 36, "function-type-reference-spacing", "Unexpected whitespace"),
LintError(1, 38, "function-type-reference-spacing", "Unexpected whitespace"),
LintError(1, 40, "function-type-reference-spacing", "Unexpected whitespace")
)
Assertions.assertThat(
FunctionTypeReferenceSpacingRule().format(code)
).isEqualTo(formattedCode)
}
}

0 comments on commit 12eb688

Please sign in to comment.