Repository: analysis-dev/diktat (kotlin)

Issue:
KotlinParseException: Your text is not valid
The exception occurs on the project okhttp

[ERROR] 2021-06-09 16:15:57 Internal error has occurred in class-compact-initialization. Please make an issue on this bug at https://github.com/cqfn/diKTat/.
org.cqfn.diktat.ruleset.utils.KotlinParseException: Your text is not valid
        at org.cqfn.diktat.ruleset.utils.KotlinParser.createNode(KotlinParser.kt:75)
        at org.cqfn.diktat.ruleset.utils.KotlinParser.createNode$default(KotlinParser.kt:75)
        at org.cqfn.diktat.ruleset.rules.chapter6.classes.CompactInitialization.getOrCreateApplyBlock(CompactInitialization.kt:124)
        at org.cqfn.diktat.ruleset.rules.chapter6.classes.CompactInitialization.moveAssignmentIntoApply(CompactInitialization.kt:80)
        at org.cqfn.diktat.ruleset.rules.chapter6.classes.CompactInitialization.access$moveAssignmentIntoApply(CompactInitialization.kt:30)
        at org.cqfn.diktat.ruleset.rules.chapter6.classes.CompactInitialization$handleProperty$2$1.invoke(CompactInitialization.kt:72)
        at org.cqfn.diktat.ruleset.rules.chapter6.classes.CompactInitialization$handleProperty$2$1.invoke(CompactInitialization.kt:71)
        at org.cqfn.diktat.ruleset.constants.Warnings.warnAndFix(Warnings.kt:259)
        at org.cqfn.diktat.ruleset.constants.Warnings.warnAndFix$default(Warnings.kt:201)

Pull request description:
### What's done:
- Fixed bug
- Added comment and tests
- Fixed #914

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/LineLength.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/LineLength.kt
index 5430a362b5..4b948826e2 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/LineLength.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/LineLength.kt
@@ -142,9 +142,25 @@ class LineLength(configRules: List<RulesConfig>) : DiktatRule(
      *         BinaryExpression - if there is two concatenated strings and new line should be inserted after `+`
      *         None - if the string can't be split
      */
+    @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION")
     private fun checkStringTemplate(node: ASTNode, configuration: LineLengthConfiguration): LongLineFixableCases {
-        val leftOffset = positionByOffset(node.startOffset).second
-        val delimiterIndex = node.text.substring(0, configuration.lineLength.toInt() - leftOffset).lastIndexOf(' ')
+        var multiLineOffset = 0
+        val leftOffset = if (node.text.lines().size > 1) {
+            node
+                .text
+                .lines()
+                .takeWhile { it.length < configuration.lineLength }
+                .forEach { multiLineOffset += it.length }
+            node
+                .text
+                .lines()
+                .first { it.length > configuration.lineLength }
+                .takeWhile { it.isWhitespace() }
+                .count()
+        } else {
+            positionByOffset(node.startOffset).second
+        }
+        val delimiterIndex = node.text.substring(0, multiLineOffset + configuration.lineLength.toInt() - leftOffset).lastIndexOf(' ')
         if (delimiterIndex == -1) {
             // we can't split this string, however may be we can move it entirely:
             // case when new line should be inserted after `+`. Example: "first" + "second"
@@ -154,13 +170,20 @@ class LineLength(configRules: List<RulesConfig>) : DiktatRule(
             // can't fix this case
             return LongLineFixableCases.None
         }
+        // check, that space to split is a part of text - not code
+        // If the space split is part of the code, then there is a chance of breaking the code when fixing, that why we should ignore it
+        val isSpaceIsWhiteSpace = node.psi.findElementAt(delimiterIndex)!!.node.isWhiteSpace()
+        if (isSpaceIsWhiteSpace) {
+            return LongLineFixableCases.None
+        }
         // minus 2 here as we are inserting ` +` and we don't want it to exceed line length
-        val correcterDelimiter = if (leftOffset + delimiterIndex > configuration.lineLength.toInt() - 2) {
+        val shouldAddTwoSpaces = multiLineOffset == 0 && leftOffset + delimiterIndex > configuration.lineLength.toInt() - 2
+        val correcterDelimiter = if (shouldAddTwoSpaces) {
             node.text.substring(0, delimiterIndex - 2).lastIndexOf(' ')
         } else {
             delimiterIndex
         }
-        return LongLineFixableCases.StringTemplate(node, correcterDelimiter)
+        return LongLineFixableCases.StringTemplate(node, correcterDelimiter, multiLineOffset == 0)
     }
 
     private fun checkFun(wrongNode: ASTNode) =
@@ -278,11 +301,19 @@ class LineLength(configRules: List<RulesConfig>) : DiktatRule(
         node.replaceChild(whiteSpaceAfterPlus, PsiWhiteSpaceImpl("\n"))
     }
 
+    @Suppress("UnsafeCallOnNullableType", "COMMENT_WHITE_SPACE")
     private fun fixStringTemplate(wrongStringTemplate: LongLineFixableCases.StringTemplate) {
         val incorrectText = wrongStringTemplate.node.text
         val firstPart = incorrectText.substring(0, wrongStringTemplate.delimiterIndex)
         val secondPart = incorrectText.substring(wrongStringTemplate.delimiterIndex, incorrectText.length)
-        wrongStringTemplate.node.treeParent.replaceChild(wrongStringTemplate.node, KotlinParser().createNode("$firstPart\" +\n\"$secondPart"))
+        val textBetwenParts =
+                if (wrongStringTemplate.isOneLineString) {
+                    "\" +\n\""
+                } else {
+                    "\n"
+                }
+        val correctNode = KotlinParser().createNode("$firstPart$textBetwenParts$secondPart")
+        wrongStringTemplate.node.treeParent.replaceChild(wrongStringTemplate.node, correctNode)
     }
 
     /**
@@ -457,7 +488,15 @@ class LineLength(configRules: List<RulesConfig>) : DiktatRule(
             val hasNewLineBefore: Boolean,
             val indexLastSpace: Int = 0) : LongLineFixableCases()
 
-        class StringTemplate(val node: ASTNode, val delimiterIndex: Int) : LongLineFixableCases()
+        /**
+         * @property node node
+         * @property delimiterIndex index to split
+         * @property isOneLineString flag is string is one line
+         */
+        class StringTemplate(
+            val node: ASTNode,
+            val delimiterIndex: Int,
+            val isOneLineString: Boolean) : LongLineFixableCases()
 
         class BinaryExpression(val node: ASTNode) : LongLineFixableCases()
 


Test patch:
diff --git a/diktat-rules/src/test/resources/test/paragraph3/long_line/LongStringTemplateExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongStringTemplateExpected.kt
index 68b5433d79..74bbdd341c 100644
--- a/diktat-rules/src/test/resources/test/paragraph3/long_line/LongStringTemplateExpected.kt
+++ b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongStringTemplateExpected.kt
@@ -11,3 +11,36 @@ val someCode = 15
     @SchedulerSupport(SchedulerSupport.NONE)
     inline fun <T1 : Any, T2 : Any, T3 : Any, T4 : Any, R : Any> combineLatest() {}
 }
+
+class Foo() {
+
+    fun Fuu() {
+        logger.log("<-- ${response.code} ${ if (response.message.isEmpty()) "skfnvkdjdfvd" else "dfjvndkjnbvif" + response.message}")
+        logger.log("<-- ${response.code} ${ if (response.message.isEmpty()) "skfnvsdcsdcscskdjdfvd" else "dfjvndsdcsdcsdcskjnbvif" + response.message}")
+    }
+
+    val q = """
+        <--${respodcnsee.ccode}${if (response.mesdscsage.isEmpty()) "skfnvkdjeeeeeee" else "dfjvndksdcjnbvif" + response.mecsssdcage}
+    """.trimIndent()
+
+    val w = """
+        first line
+    veryy looooooooooooooooong second line didfuhyg djfghdf gjh erughdjf gbdpfughb ergen r fgdngjkdg e g s g d g
+ d bd jk;g,e,r gd bnsfg e
+    """.trimIndent()
+
+    val e = """
+        another line
+        <--${respodcnse.ccode}${if (response.mesdscsage.isEmpty()) "skfnvkdjdsdcfvd" else "dfjvndksdcjnbvif" + response.mecsssdcage}
+    """.trimIndent()
+
+    fun foo() {
+        val q = """
+        re
+        ${
+            if (( x >  "436587365486374568343658736548637456834365873654863745683436587365486374568343658736548637456834365873654863745683") ||
+ y<238479283749238 && !isFoo()){}
+        }
+    """.trimIndent()
+    }
+}
diff --git a/diktat-rules/src/test/resources/test/paragraph3/long_line/LongStringTemplateTest.kt b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongStringTemplateTest.kt
index bff4d2ad64..005cf1a91c 100644
--- a/diktat-rules/src/test/resources/test/paragraph3/long_line/LongStringTemplateTest.kt
+++ b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongStringTemplateTest.kt
@@ -8,3 +8,34 @@ object Observables {
     @SchedulerSupport(SchedulerSupport.NONE)
     inline fun <T1 : Any, T2 : Any, T3 : Any, T4 : Any, R : Any> combineLatest() {}
 }
+
+class Foo() {
+
+    fun Fuu() {
+        logger.log("<-- ${response.code} ${ if (response.message.isEmpty()) "skfnvkdjdfvd" else "dfjvndkjnbvif" + response.message}")
+        logger.log("<-- ${response.code} ${ if (response.message.isEmpty()) "skfnvsdcsdcscskdjdfvd" else "dfjvndsdcsdcsdcskjnbvif" + response.message}")
+    }
+
+    val q = """
+        <--${respodcnsee.ccode}${if (response.mesdscsage.isEmpty()) "skfnvkdjeeeeeee" else "dfjvndksdcjnbvif" + response.mecsssdcage}
+    """.trimIndent()
+
+    val w = """
+        first line
+    veryy looooooooooooooooong second line didfuhyg djfghdf gjh erughdjf gbdpfughb ergen r fgdngjkdg e g s g d g d bd jk;g,e,r gd bnsfg e
+    """.trimIndent()
+
+    val e = """
+        another line
+        <--${respodcnse.ccode}${if (response.mesdscsage.isEmpty()) "skfnvkdjdsdcfvd" else "dfjvndksdcjnbvif" + response.mecsssdcage}
+    """.trimIndent()
+
+    fun foo() {
+        val q = """
+        re
+        ${
+            if (( x >  "436587365486374568343658736548637456834365873654863745683436587365486374568343658736548637456834365873654863745683") || y<238479283749238 && !isFoo()){}
+        }
+    """.trimIndent()
+    }
+}