Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not merge opening quotes of multiline string template with (single line) function signature #2609

Merged
merged 1 commit into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.pinterest.ktlint.ruleset.standard.rules

import com.pinterest.ktlint.rule.engine.core.api.ElementType
import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATED_EXPRESSION
import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION
import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY
Expand Down Expand Up @@ -33,6 +34,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.INDENT_STYLE_PROPE
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PROPERTY
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PROPERTY_OFF
import com.pinterest.ktlint.rule.engine.core.api.indent
import com.pinterest.ktlint.rule.engine.core.api.isCodeLeaf
import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace
import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline
import com.pinterest.ktlint.rule.engine.core.api.nextCodeLeaf
Expand Down Expand Up @@ -563,14 +565,16 @@ public class FunctionSignatureRule :
return
}
val mergeWithFunctionSignature =
if (firstLineOfBodyExpression.length < maxLengthRemainingForFirstLineOfBodyExpression) {
functionBodyExpressionWrapping == default ||
(functionBodyExpressionWrapping == multiline && functionBodyExpressionLines.size == 1) ||
node.isMultilineFunctionSignatureWithoutExplicitReturnType(
lastNodeOfFunctionSignatureWithBodyExpression,
)
} else {
false
when {
firstLineOfBodyExpression.length < maxLengthRemainingForFirstLineOfBodyExpression -> {
(functionBodyExpressionWrapping == default && !functionBodyExpressionNodes.isMultilineStringTemplate()) ||
(functionBodyExpressionWrapping == multiline && functionBodyExpressionLines.size == 1) ||
node.isMultilineFunctionSignatureWithoutExplicitReturnType(
lastNodeOfFunctionSignatureWithBodyExpression,
)
}

else -> false
}
if (mergeWithFunctionSignature) {
emit(
Expand Down Expand Up @@ -621,6 +625,17 @@ public class FunctionSignatureRule :
}
}

private fun List<ASTNode>.isMultilineStringTemplate() =
first { it.isCodeLeaf() }
.let {
it.elementType == ElementType.OPEN_QUOTE &&
it
.nextLeaf()
?.text
.orEmpty()
.startsWith("\n")
}

private fun ASTNode.isMultilineFunctionSignatureWithoutExplicitReturnType(lastNodeOfFunctionSignatureWithBodyExpression: ASTNode?) =
functionSignatureNodes()
.childrenBetween(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.pinterest.ktlint.test.KtLintAssertThat.Companion.EOL_CHAR
import com.pinterest.ktlint.test.KtLintAssertThat.Companion.MAX_LINE_LENGTH_MARKER
import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRuleBuilder
import com.pinterest.ktlint.test.LintViolation
import com.pinterest.ktlint.test.MULTILINE_STRING_QUOTE
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.jupiter.api.DisplayName
Expand Down Expand Up @@ -1313,6 +1314,56 @@ class FunctionSignatureRuleTest {
.hasNoLintViolations()
}

@Test
fun `Issue 2592 - Given a function signature with an expression body that is multiline raw string literal then do not join the first leaf with the function signature`() {
val code =
"""
// $MAX_LINE_LENGTH_MARKER $EOL_CHAR
fun foo1(): String =
$MULTILINE_STRING_QUOTE
some text
$MULTILINE_STRING_QUOTE.trimIndent()
fun foo2() =
$MULTILINE_STRING_QUOTE
some text
$MULTILINE_STRING_QUOTE.trimIndent()
""".trimIndent()
functionSignatureWrappingRuleAssertThat(code)
.setMaxLineLength()
.withEditorConfigOverride(FUNCTION_BODY_EXPRESSION_WRAPPING_PROPERTY to default)
.hasNoLintViolations()
}

@Test
fun `Issue 2592 - Given a multiline function signature with an expression body that is multiline raw string literal then do join the first leaf with the function signature`() {
val code =
"""
// $MAX_LINE_LENGTH_MARKER $EOL_CHAR
fun foo(
foo: Foo,
bar: Bar,
) =
$MULTILINE_STRING_QUOTE
some text
$MULTILINE_STRING_QUOTE.trimIndent()
""".trimIndent()
val formattedCode =
"""
// $MAX_LINE_LENGTH_MARKER $EOL_CHAR
fun foo(
foo: Foo,
bar: Bar,
) = $MULTILINE_STRING_QUOTE
some text
$MULTILINE_STRING_QUOTE.trimIndent()
""".trimIndent()
functionSignatureWrappingRuleAssertThat(code)
.setMaxLineLength()
.withEditorConfigOverride(FUNCTION_BODY_EXPRESSION_WRAPPING_PROPERTY to default)
.hasLintViolation(5, 4, "First line of body expression fits on same line as function signature")
.isFormattedAs(formattedCode)
}

private companion object {
const val UNEXPECTED_SPACES = " "
const val NO_SPACE = ""
Expand Down