diff --git a/CHANGELOG.md b/CHANGELOG.md index 51577e604b..108c87ae42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,7 @@ The callback function provided as parameter to the format function is now called * When a glob is specified then ensure that it matches files in the current directory and not only in subdirectories of the current directory ([#1533](https://github.com/pinterest/ktlint/issue/1533)). * Execute `ktlint` cli on default kotlin extensions only when an (existing) path to a directory is given. ([#917](https://github.com/pinterest/ktlint/issue/917)). * Invoke callback on `format` function for all errors including errors that are autocorrected ([#1491](https://github.com/pinterest/ktlint/issues/1491)) +* Merge first line of body expression with function signature only when it fits on the same line `function-signature` ([#1527](https://github.com/pinterest/ktlint/issues/1527)) ### Changed diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/FunctionSignatureRule.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/FunctionSignatureRule.kt index 50b3a2b590..13c9ba6a08 100644 --- a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/FunctionSignatureRule.kt +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/FunctionSignatureRule.kt @@ -522,8 +522,11 @@ public class FunctionSignatureRule : .firstOrNull() ?.also { firstLineOfBodyExpression -> if (whiteSpaceBeforeFunctionBodyExpression.isWhiteSpaceWithNewline()) { - if (functionBodyExpressionWrapping == default || - (functionBodyExpressionWrapping == multiline && functionBodyExpressionLines.size == 1) || + val mergeWithFunctionSignature = + functionBodyExpressionWrapping.keepFirstLineOfBodyExpressionTogetherWithFunctionSignature( + firstLineOfBodyExpression.length < maxLengthRemainingForFirstLineOfBodyExpression + ) + if (mergeWithFunctionSignature || node.isMultilineFunctionSignatureWithoutExplicitReturnType(lastNodeOfFunctionSignatureWithBodyExpression) ) { emit( @@ -754,5 +757,12 @@ public class FunctionSignatureRule : * Always force the body expression to start on a separate line. */ always; + + internal fun keepFirstLineOfBodyExpressionTogetherWithFunctionSignature(fitOnSameLine: Boolean) = + if (this == default || this == multiline) { + fitOnSameLine + } else { + false + } } } diff --git a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/FunctionSignatureRuleTest.kt b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/FunctionSignatureRuleTest.kt index 877bbec66c..8341659730 100644 --- a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/FunctionSignatureRuleTest.kt +++ b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/FunctionSignatureRuleTest.kt @@ -786,12 +786,46 @@ class FunctionSignatureRuleTest { ).isFormattedAs(formattedCode) } + @ParameterizedTest(name = "bodyExpressionWrapping: {0}") + @EnumSource( + value = FunctionSignatureRule.FunctionBodyExpressionWrapping::class, + names = ["default", "multiline", "always"] + ) + fun `Given that the function signature and first line of a multi line body expression body do not fit on the same line then do reformat`( + bodyExpressionWrapping: FunctionSignatureRule.FunctionBodyExpressionWrapping + ) { + val code = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun f( + a: Any, + b: Any + ): String = "some-result" + """.trimIndent() + val formattedCode = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun f(a: Any, b: Any): String = + "some-result" + """.trimIndent() + functionSignatureWrappingRuleAssertThat(code) + .setMaxLineLength() + .withEditorConfigOverride(functionBodyExpressionWrappingProperty to bodyExpressionWrapping) + .addAdditionalRuleProvider { IndentationRule() } + .hasLintViolations( + LintViolation(3, 5, "No whitespace expected between opening parenthesis and first parameter name"), + LintViolation(4, 5, "Single whitespace expected before parameter"), + LintViolation(4, 11, "No whitespace expected between last parameter and closing parenthesis"), + LintViolation(5, 13, "Newline expected before expression body") + ).isFormattedAs(formattedCode) + } + @ParameterizedTest(name = "bodyExpressionWrapping: {0}") @EnumSource( value = FunctionSignatureRule.FunctionBodyExpressionWrapping::class, names = ["always"] ) - fun `Given that the function signature and a single line body expression body fit on the same line then reformat to single line signature but keep body expression on a separate line`( + fun `Given that the function signature and the first line of a multi line body expression body fit on the same line then reformat to single line signature but keep body expression on separate line`( bodyExpressionWrapping: FunctionSignatureRule.FunctionBodyExpressionWrapping ) { val code = @@ -802,12 +836,14 @@ class FunctionSignatureRuleTest { b: Any ): String = "some-result" + .trim() """.trimIndent() val formattedCode = """ // $MAX_LINE_LENGTH_MARKER $EOL_CHAR fun f(a: Any, b: Any): String = "some-result" + .trim() """.trimIndent() functionSignatureWrappingRuleAssertThat(code) .setMaxLineLength() @@ -823,7 +859,7 @@ class FunctionSignatureRuleTest { @ParameterizedTest(name = "bodyExpressionWrapping: {0}") @EnumSource( value = FunctionSignatureRule.FunctionBodyExpressionWrapping::class, - names = ["default"] + names = ["default", "multiline"] ) fun `Given that the function signature and first line of a multiline body expression body fit on the same line then do reformat as single line signature`( bodyExpressionWrapping: FunctionSignatureRule.FunctionBodyExpressionWrapping @@ -859,7 +895,7 @@ class FunctionSignatureRuleTest { @ParameterizedTest(name = "bodyExpressionWrapping: {0}") @EnumSource( value = FunctionSignatureRule.FunctionBodyExpressionWrapping::class, - names = ["multiline", "always"] + names = ["always"] ) fun `Given that the function signature and first line of a multiline body expression body fit on the same line then do reformat as single line signature, keep the body expression on a separate line`( bodyExpressionWrapping: FunctionSignatureRule.FunctionBodyExpressionWrapping @@ -897,7 +933,7 @@ class FunctionSignatureRuleTest { @EnumSource( value = FunctionSignatureRule.FunctionBodyExpressionWrapping::class ) - fun `Given a multiline function signature without explicit return type and start of body expression on next line then keep first line of body expression body on the same line as the last line og the function signature`( + fun `Given a multiline function signature without explicit return type and start of body expression on next line then keep first line of body expression body on the same line as the last line of the function signature`( bodyExpressionWrapping: FunctionSignatureRule.FunctionBodyExpressionWrapping ) { val code = @@ -996,6 +1032,19 @@ class FunctionSignatureRuleTest { ).isFormattedAs(formattedCode) } + @Test + fun `Issue 1527 - Given a function signature with an expression body which does not fit on the same line as the signature then do not reformat`() { + val code = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + fun foo(bar: String) = + "some-result" + """.trimIndent() + functionSignatureWrappingRuleAssertThat(code) + .setMaxLineLength() + .hasNoLintViolations() + } + private companion object { const val EOL_CHAR = '#' const val UNEXPECTED_SPACES = " "