Repository: analysis-dev/diktat (kotlin)

Issue:
NPE in Long Line Rule
## Describe the bug
There is NPE in Long Line Rule in `fix` mode for this line

```kotlin
var headerKdoc = firstCodeNode.prevSibling { it.elementType == KDOC } ?: if (firstCodeNode == packageDirectiveNode) importsList?.prevSibling { it.elementType == KDOC } else null
```
## Observed behavior

```kotlin
[ERROR] 2021-07-12 15:56:44 Internal error has occurred in rule [line-length]. Please make an issue on this bug at https://github.com/cqfn/diKTat/. 
                       As a workaround you can disable these inspections in yml config: <[LONG_LINE]>.
                       Root cause of the problem is in [D:\IDEAProjects\diKTat\diktat-rules\target\test-classes\test\paragraph3\long_line\LongLineRValueTest.kt] file.
java.lang.NullPointerException
	at org.cqfn.diktat.ruleset.rules.chapter3.LineLength.fixLongBinaryExpression(LineLength.kt:335)
	at org.cqfn.diktat.ruleset.rules.chapter3.LineLength.fixError(LineLength.kt:267)
	at org.cqfn.diktat.ruleset.rules.chapter3.LineLength.access$fixError(LineLength.kt:63)
	at org.cqfn.diktat.ruleset.rules.chapter3.LineLength$checkLength$1$1.invoke(LineLength.kt:99)
	at org.cqfn.diktat.ruleset.rules.chapter3.LineLength$checkLength$1$1.invoke(LineLength.kt:96)
	at org.cqfn.diktat.ruleset.constants.Warnings.warnAndFix(Warnings.kt:260)
	at org.cqfn.diktat.ruleset.rules.chapter3.LineLength.checkLength(LineLength.kt:94)
	at org.cqfn.diktat.ruleset.rules.chapter3.LineLength.logic(LineLength.kt:77)

```

## Steps to Reproduce
It could be reproduced in the diktat demo:
```kotlin
class Example {
    var headerKdoc = firstCodeNode.prevSibling { it.elementType == KDOC } ?: if (firstCodeNode == packageDirectiveNode) importsList?.prevSibling { it.elementType == KDOC } else null
}
```
 or this problematic line can be put in some test, e.g.: LongLineRValueTest.kt

### The NPE appears in both cases, despite the fact that diktat demo and tests uses different values for line limit

## Environment information
* diktat version: 1.0.0-rc.2

Pull request:
### What's done:
* Fix NPE in long binary expressions: previously in complex cases `binList` could hold incorrect nodes, which had no `binary expression` as first common parent
* Fix out of range error in long string templates
* Add tests

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 4b948826e2..6c7d94301f 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
@@ -30,6 +30,7 @@ import com.pinterest.ktlint.core.ast.ElementType.KDOC_TEXT
 import com.pinterest.ktlint.core.ast.ElementType.LITERAL_STRING_TEMPLATE_ENTRY
 import com.pinterest.ktlint.core.ast.ElementType.LONG_STRING_TEMPLATE_ENTRY
 import com.pinterest.ktlint.core.ast.ElementType.LPAR
+import com.pinterest.ktlint.core.ast.ElementType.NULL
 import com.pinterest.ktlint.core.ast.ElementType.OPERATION_REFERENCE
 import com.pinterest.ktlint.core.ast.ElementType.PACKAGE_DIRECTIVE
 import com.pinterest.ktlint.core.ast.ElementType.PARENTHESIZED
@@ -177,12 +178,15 @@ class LineLength(configRules: List<RulesConfig>) : DiktatRule(
             return LongLineFixableCases.None
         }
         // minus 2 here as we are inserting ` +` and we don't want it to exceed line length
-        val shouldAddTwoSpaces = multiLineOffset == 0 && 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
         }
+        if (correcterDelimiter == -1) {
+            return LongLineFixableCases.None
+        }
         return LongLineFixableCases.StringTemplate(node, correcterDelimiter, multiLineOffset == 0)
     }
 
@@ -330,7 +334,7 @@ class LineLength(configRules: List<RulesConfig>) : DiktatRule(
         binList.forEachIndexed { index, astNode ->
             binaryText += findAllText(astNode)
             if (leftOffset + binaryText.length > wrongBinaryExpression.maximumLineLength && index != 0) {
-                val commonParent = astNode.parent({ it in binList[index - 1].parents() })!!
+                val commonParent = astNode.parent({ it.elementType == BINARY_EXPRESSION && it in binList[index - 1].parents() })!!
                 val nextNode = commonParent.findChildByType(OPERATION_REFERENCE)!!.treeNext
                 if (!nextNode.text.contains("\n")) {
                     commonParent.appendNewlineMergingWhiteSpace(nextNode, nextNode)
@@ -429,10 +433,18 @@ class LineLength(configRules: List<RulesConfig>) : DiktatRule(
         }
     }
 
+    /**
+     * Collect by Depth-first search (DFS) all children to the right side of the equal sign with specific type [propertyList],
+     * by which we can split expression.
+     * Such logic needed, because AST representation of complex conditions is quite loaded
+     *
+     * @param node target node to be processed
+     * @param binList where to store the corresponding results
+     */
     private fun dfsForProperty(node: ASTNode, binList: MutableList<ASTNode>) {
         node.getChildren(null).forEach {
             if (it.elementType in propertyList) {
-                if (it.elementType == REFERENCE_EXPRESSION && it.treeParent.elementType == CALL_EXPRESSION) {
+                if (it.elementType == REFERENCE_EXPRESSION && it.treeParent?.elementType == CALL_EXPRESSION) {
                     binList.add(it.treeParent)
                 } else {
                     binList.add(it)
@@ -479,7 +491,7 @@ class LineLength(configRules: List<RulesConfig>) : DiktatRule(
 
         /**
          * @property node node
-         * @property hasNewLineBefore flag to handle type of comment: ordinary comment(long part of which should be moved to the next line)
+         * @property hasNewLineBefore flag to handle type of comment: ordinary comment (long part of which should be moved to the next line)
          * and inline comments (which should be moved entirely to the previous line)
          * @property indexLastSpace index of last space to substring comment
          */
@@ -525,6 +537,6 @@ class LineLength(configRules: List<RulesConfig>) : DiktatRule(
         private const val STRING_PART_OFFSET = 4
         private val propertyList = listOf(INTEGER_CONSTANT, LITERAL_STRING_TEMPLATE_ENTRY, FLOAT_CONSTANT,
             CHARACTER_CONSTANT, REFERENCE_EXPRESSION, BOOLEAN_CONSTANT, LONG_STRING_TEMPLATE_ENTRY,
-            SHORT_STRING_TEMPLATE_ENTRY)
+            SHORT_STRING_TEMPLATE_ENTRY, NULL)
     }
 }


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/LineLengthFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/LineLengthFixTest.kt
index 6325ed139d..3b987b50a5 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/LineLengthFixTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/LineLengthFixTest.kt
@@ -40,6 +40,11 @@ class LineLengthFixTest : FixTestBase("test/paragraph3/long_line", ::LineLength)
         fixAndCompare("LongLineExpressionExpected.kt", "LongLineExpressionTest.kt", rulesConfigListLineLength)
     }
 
+    @Test
+    fun `should fix complex long binary expressions`() {
+        fixAndCompare("LongBinaryExpressionExpected.kt", "LongBinaryExpressionTest.kt", rulesConfigListLineLength)
+    }
+
     @Test
     fun `should fix long function`() {
         fixAndCompare("LongLineFunExpected.kt", "LongLineFunTest.kt", rulesConfigListLineLength)
diff --git a/diktat-rules/src/test/resources/test/paragraph3/long_line/LongBinaryExpressionExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongBinaryExpressionExpected.kt
new file mode 100644
index 0000000000..94c5f6f895
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongBinaryExpressionExpected.kt
@@ -0,0 +1,35 @@
+package test.paragraph3.long_line
+
+fun foo() {
+    val veryLongExpression = Methoooooooooooooooood() +
+ 12345
+
+    val veryLongExpression = Methoooooooooooooooood() ?:
+ null
+
+    val veryLongExpression = a.Methooooood() +
+ b.field
+
+    val variable = someField.filter { it.elementType == KDOC }
+
+    // limit at the left side
+    val variable = a?.filter { it.elementType == KDOC } ?:
+ null
+
+    // limit at the right side
+    val variable = bar?.filter { it.b == c } ?:
+ null
+
+    // limit at the operation reference
+    val variable = field?.filter { bar == foo } ?:
+ null
+
+    val variable = Methoooooooooooooooooooooooooood() ?:
+ "some loooooong string"
+
+    val variable = Methooooood() ?: "some" +
+" looong string"
+
+    var headerKdoc = firstCodeNode.prevSibling { it.elementType == KDOC } ?:
+ if (firstCodeNode == packageDirectiveNode) importsList?.prevSibling { it.elementType == KDOC } else null
+}
diff --git a/diktat-rules/src/test/resources/test/paragraph3/long_line/LongBinaryExpressionTest.kt b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongBinaryExpressionTest.kt
new file mode 100644
index 0000000000..2f14386194
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongBinaryExpressionTest.kt
@@ -0,0 +1,26 @@
+package test.paragraph3.long_line
+
+fun foo() {
+    val veryLongExpression = Methoooooooooooooooood() + 12345
+
+    val veryLongExpression = Methoooooooooooooooood() ?: null
+
+    val veryLongExpression = a.Methooooood() + b.field
+
+    val variable = someField.filter { it.elementType == KDOC }
+
+    // limit at the left side
+    val variable = a?.filter { it.elementType == KDOC } ?: null
+
+    // limit at the right side
+    val variable = bar?.filter { it.b == c } ?: null
+
+    // limit at the operation reference
+    val variable = field?.filter { bar == foo } ?: null
+
+    val variable = Methoooooooooooooooooooooooooood() ?: "some loooooong string"
+
+    val variable = Methooooood() ?: "some looong string"
+
+    var headerKdoc = firstCodeNode.prevSibling { it.elementType == KDOC } ?: if (firstCodeNode == packageDirectiveNode) importsList?.prevSibling { it.elementType == KDOC } else null
+}
diff --git a/diktat-rules/src/test/resources/test/paragraph3/long_line/LongLineRValueExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongLineRValueExpected.kt
index a2c047bbd7..2fd0dfcf09 100644
--- a/diktat-rules/src/test/resources/test/paragraph3/long_line/LongLineRValueExpected.kt
+++ b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongLineRValueExpected.kt
@@ -24,6 +24,8 @@ fun foo() {
     val longStringExpression = "First part" +
 "second Part"
 
+    val longStringExpression = "First" + "second Part"
+
     val longStringExpression = "First very long" +
 " part" + "second Part"
 
diff --git a/diktat-rules/src/test/resources/test/paragraph3/long_line/LongLineRValueTest.kt b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongLineRValueTest.kt
index 346e4838a1..fa6d6c1881 100644
--- a/diktat-rules/src/test/resources/test/paragraph3/long_line/LongLineRValueTest.kt
+++ b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongLineRValueTest.kt
@@ -19,6 +19,8 @@ fun foo() {
 
     val longStringExpression = "First part" + "second Part"
 
+    val longStringExpression = "First" + "second Part"
+
     val longStringExpression = "First very long part" + "second Part"
 
     val longStringExpression2 =                   "String starts at the line len limit"