Skip to content

Commit

Permalink
Remove redundant arrow in function literal without parameters / fix d…
Browse files Browse the repository at this point in the history
…ocumentation (#2365)

Kotlin allows a function literal to be defined with an empty parameter list and a redundant arrow symbol to divide the parameter list from the body.

Fix documentation, `function-literal` rule is experimental and not bound to `ktlint_official` code style.

Closes #2331
  • Loading branch information
paul-dingemans committed Nov 19, 2023
1 parent 7c56501 commit f61a5ff
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 23 deletions.
3 changes: 0 additions & 3 deletions documentation/release-latest/docs/rules/experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,6 @@ If the function literal contains multiple parameter and at least one parameter o

Rule id: `function-literal` (`standard` rule set)

!!! Note
This rule is only run when `ktlint_code_style` is set to `ktlint_official` or when the rule is enabled explicitly.

## Function type modifier spacing

Enforce a single whitespace between the modifier list and the function type.
Expand Down
3 changes: 0 additions & 3 deletions documentation/snapshot/docs/rules/experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,6 @@ If the function literal contains multiple parameter and at least one parameter o

Rule id: `function-literal` (`standard` rule set)

!!! Note
This rule is only run when `ktlint_code_style` is set to `ktlint_official` or when the rule is enabled explicitly.

## Function type modifier spacing

Enforce a single whitespace between the modifier list and the function type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ public class FunctionLiteralRule :
node
.findChildByType(VALUE_PARAMETER_LIST)
?.let { visitValueParameterList(it, autoCorrect, emit) }
node
.findChildByType(ARROW)
?.let { visitArrow(it, autoCorrect, emit) }
node
.findChildByType(BLOCK)
?.let { visitBlock(it, autoCorrect, emit) }
Expand Down Expand Up @@ -196,27 +199,33 @@ public class FunctionLiteralRule :
private fun ASTNode.exceedsMaxLineLength() = lineLengthWithoutNewlinePrefix() > maxLineLength

private fun ASTNode.wrapFirstParameterToNewline() =
takeIf { it.treeParent.elementType == FUNCTION_LITERAL }
if (isFunctionLiteralLambdaWithNonEmptyValueParameterList()) {
// Disallow when max line is exceeded:
// val foo = someCallExpression { someLongParameterName ->
// bar()
// }
val stopAtLeaf =
children()
.first { it.elementType == VALUE_PARAMETER }
.lastChildLeafOrSelf()
.nextLeaf { !it.isWhiteSpaceWithoutNewline() && !it.isPartOfComment() }
leavesOnLine()
.takeWhile { it.prevLeaf() != stopAtLeaf }
.lineLengthWithoutNewlinePrefix()
.let { it > maxLineLength }
} else {
false
}

private fun ASTNode.isFunctionLiteralLambdaWithNonEmptyValueParameterList() =
takeIf { it.elementType == VALUE_PARAMETER_LIST }
?.takeIf { it.findChildByType(VALUE_PARAMETER) != null }
?.takeIf { it.treeParent.elementType == FUNCTION_LITERAL }
?.treeParent
?.takeIf { it.treeParent.elementType == LAMBDA_EXPRESSION }
?.treeParent
?.takeIf { it.treeParent.elementType == LAMBDA_ARGUMENT }
?.let {
// Disallow when max line is exceeded:
// val foo = someCallExpression { someLongParameterName ->
// bar()
// }
val stopAtLeaf =
children()
.first { it.elementType == VALUE_PARAMETER }
.lastChildLeafOrSelf()
.nextLeaf { !it.isWhiteSpaceWithoutNewline() && !it.isPartOfComment() }
leavesOnLine()
.takeWhile { it.prevLeaf() != stopAtLeaf }
.lineLengthWithoutNewlinePrefix()
.let { it > maxLineLength }
}
?: false
.let { it != null }

private fun rewriteToMultilineParameterList(
parameterList: ASTNode,
Expand Down Expand Up @@ -357,6 +366,27 @@ public class FunctionLiteralRule :
}
}

private fun visitArrow(
arrow: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
) {
require(arrow.elementType == ARROW)
arrow
.prevSibling { it.elementType == VALUE_PARAMETER_LIST }
?.takeIf { it.findChildByType(VALUE_PARAMETER) == null }
?.let {
emit(arrow.startOffset, "Arrow is redundant when parameter list is empty", true)
if (autoCorrect) {
arrow
.nextSibling()
.takeIf { it.isWhiteSpace() }
?.let { it.treeParent.removeChild(it) }
arrow.treeParent.removeChild(arrow)
}
}
}

private fun visitBlock(
block: ASTNode,
autoCorrect: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,4 +449,19 @@ class FunctionLiteralRuleTest {
LintViolation(3, 77, "Newline expected before closing brace"),
).isFormattedAs(formattedCode)
}

@Test
fun `Issue 2331 - Given a function literal with redundant arrow`() {
val code =
"""
fun foo(block: () -> Unit): Unit = foo { -> block() }
""".trimIndent()
val formattedCode =
"""
fun foo(block: () -> Unit): Unit = foo { block() }
""".trimIndent()
functionLiteralRuleAssertThat(code)
.hasLintViolation(1, 42, "Arrow is redundant when parameter list is empty")
.isFormattedAs(formattedCode)
}
}

0 comments on commit f61a5ff

Please sign in to comment.