From f37fdc9757f8dca8cd010e5f499068d48728e05a Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Mon, 13 Mar 2023 17:47:24 +0100 Subject: [PATCH] Wrapping false positive for line with max line length (#1854) * Fix false positive when a single line statement containing a block having exactly the maximum line length is preceded by a blank line Closes #1808 --- CHANGELOG.md | 1 + .../standard/rules/NoSemicolonsRule.kt | 1 - .../ruleset/standard/rules/WrappingRule.kt | 33 +++++++++++-------- .../standard/rules/WrappingRuleTest.kt | 14 ++++++++ 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62fd532301..ad7358ba4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -251,6 +251,7 @@ Previously the default value for `.editorconfig` property `max_line_length` was * Fix indentation of try-catch-finally when catch or finally starts on a newline `indent` ([#1788](https://github.com/pinterest/ktlint/issues/1788)) * Fix indentation of a multiline typealias `indent` ([#1788](https://github.com/pinterest/ktlint/issues/1788)) * Fix false positive when multiple KDOCs exists between a declaration and another annotated declaration `spacing-between-declarations-with-annotations` ([#1802](https://github.com/pinterest/ktlint/issues/1802)) +* Fix false positive when a single line statement containing a block having exactly the maximum line length is preceded by a blank line `wrapping` ([#1808](https://github.com/pinterest/ktlint/issues/1808)) ### Changed * Wrap the parameters of a function literal containing a multiline parameter list (only in `ktlint_official` code style) `parameter-list-wrapping` ([#1681](https://github.com/pinterest/ktlint/issues/1681)). diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt index 95d89225db..4fce7578a8 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoSemicolonsRule.kt @@ -34,7 +34,6 @@ public class NoSemicolonsRule : StandardRule("no-semi") { return } val nextLeaf = node.nextLeaf() - val prevCodeLeaf = node.prevCodeLeaf() if (nextLeaf.doesNotRequirePreSemi() && isNoSemicolonRequiredAfter(node)) { emit(node.startOffset, "Unnecessary semicolon", true) if (autoCorrect) { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt index cd173e065b..2630476df6 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRule.kt @@ -19,12 +19,12 @@ import com.pinterest.ktlint.rule.engine.core.api.ElementType.LAMBDA_EXPRESSION import com.pinterest.ktlint.rule.engine.core.api.ElementType.LBRACE import com.pinterest.ktlint.rule.engine.core.api.ElementType.LBRACKET import com.pinterest.ktlint.rule.engine.core.api.ElementType.LITERAL_STRING_TEMPLATE_ENTRY +import com.pinterest.ktlint.rule.engine.core.api.ElementType.LONG_STRING_TEMPLATE_ENTRY import com.pinterest.ktlint.rule.engine.core.api.ElementType.LPAR import com.pinterest.ktlint.rule.engine.core.api.ElementType.LT import com.pinterest.ktlint.rule.engine.core.api.ElementType.OBJECT_LITERAL import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACE import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACKET -import com.pinterest.ktlint.rule.engine.core.api.ElementType.REGULAR_STRING_PART import com.pinterest.ktlint.rule.engine.core.api.ElementType.RPAR import com.pinterest.ktlint.rule.engine.core.api.ElementType.STRING_TEMPLATE import com.pinterest.ktlint.rule.engine.core.api.ElementType.SUPER_TYPE_CALL_ENTRY @@ -49,11 +49,13 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PR import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MAX_LINE_LENGTH_PROPERTY_OFF import com.pinterest.ktlint.rule.engine.core.api.firstChildLeafOrSelf import com.pinterest.ktlint.rule.engine.core.api.indent +import com.pinterest.ktlint.rule.engine.core.api.isPartOf import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment 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.isWhiteSpaceWithoutNewline import com.pinterest.ktlint.rule.engine.core.api.lastChildLeafOrSelf +import com.pinterest.ktlint.rule.engine.core.api.leavesIncludingSelf import com.pinterest.ktlint.rule.engine.core.api.nextCodeLeaf import com.pinterest.ktlint.rule.engine.core.api.nextCodeSibling import com.pinterest.ktlint.rule.engine.core.api.nextLeaf @@ -140,25 +142,28 @@ public class WrappingRule : val blockIsFollowedByWhitespaceContainingNewline = endOfBlock?.prevLeaf().isWhiteSpaceWithNewline() val wrapBlock = when { + startOfBlock.isPartOf(LONG_STRING_TEMPLATE_ENTRY) -> { + // String template inside raw string literal may exceed the maximum line length + false + } blockIsPrecededByWhitespaceContainingNewline -> false node.textContains('\n') || blockIsFollowedByWhitespaceContainingNewline -> { // A multiline block should always be wrapped unless it starts with an EOL comment node.firstChildLeafOrSelf().elementType != EOL_COMMENT } maxLineLength != MAX_LINE_LENGTH_PROPERTY_OFF -> { - val startOfLine = node.prevLeaf { - it.isWhiteSpaceWithNewline() || (it.elementType == REGULAR_STRING_PART && it.text == "\n") - } - val endOfLine = node.nextLeaf { - it.isWhiteSpaceWithNewline() || (it.elementType == REGULAR_STRING_PART && it.text == "\n") - } - val line = - startOfLine - ?.leaves() - ?.takeWhile { it != endOfLine } - ?.joinToString(separator = "", prefix = startOfLine.text.removePrefix("\n")) { it.text } - .orEmpty() - line.length > maxLineLength + val lengthUntilBeginOfLine = + node + .leaves(false) + .takeWhile { !it.isWhiteSpaceWithNewline() } + .sumOf { it.textLength } + val lengthUntilEndOfLine = + node + .firstChildLeafOrSelf() + .leavesIncludingSelf() + .takeWhile { !it.isWhiteSpaceWithNewline() } + .sumOf { it.textLength } + lengthUntilBeginOfLine + lengthUntilEndOfLine > maxLineLength } else -> false } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRuleTest.kt index 70939d0019..672a7c9c6e 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/WrappingRuleTest.kt @@ -1834,6 +1834,20 @@ internal class WrappingRuleTest { .hasLintViolation(2, 8, "A newline was expected before '>'") .isFormattedAs(formattedCode) } + + @Test + fun `Issue 1808 - Given a line including a block with exact length of line maximum`() { + val code = + """ + // $MAX_LINE_LENGTH_MARKER $EOL_CHAR + val foo = "fooooooooooooooo".map { "bar" } + + // Keep blank line above + """.trimIndent() + wrappingRuleAssertThat(code) + .setMaxLineLength() + .hasNoLintViolations() + } } }