diff --git a/documentation/release-latest/docs/rules/experimental.md b/documentation/release-latest/docs/rules/experimental.md index 6b084dd85b..bfcdeaff2a 100644 --- a/documentation/release-latest/docs/rules/experimental.md +++ b/documentation/release-latest/docs/rules/experimental.md @@ -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. diff --git a/documentation/snapshot/docs/rules/experimental.md b/documentation/snapshot/docs/rules/experimental.md index 8f60deb326..b92c170b4c 100644 --- a/documentation/snapshot/docs/rules/experimental.md +++ b/documentation/snapshot/docs/rules/experimental.md @@ -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. diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRule.kt index 4ef5f5fbbd..486c136aba 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRule.kt @@ -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) } @@ -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, @@ -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, diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleTest.kt index 8141a92350..3ed2f88938 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionLiteralRuleTest.kt @@ -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) + } }