Repository: analysis-dev/diktat (kotlin)

Issue:
Incrrect movement of inline comments
# Bug description

An inline comment that is too long is moved to a the line containing the code instead of creating an empty line above.  The brace in the line above is commented out. In case of two long inline comments both of them are moved to the same place rather than one line above their original positions. Also, a redundant comma is added.

## Example 1

## Source

```kotlin
fun foobar() {
    foo(
        bar() // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
    )
}

```

## Expected behavior

```kotlin
fun foobar() {
    foo(
        // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
        bar(),
    )
}
```


## Observed behavior

```kotlin
fun foobar() {
    foo// ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff(
        bar(),
    )
}
```

# Example 2

## Source 

```kotlin
fun foobar() {
    foo(
        bar(), // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
        baz()  // uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
    )
}
```

## Expected behavior


```kotlin
fun foobar() {
    foo(
        // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
        bar(),
         // uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
        baz()
    )
}
```

## Observed behavior

```kotlin
fun foobar() {
    foo// ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
    // uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu(
        bar(),
        baz(),
    )
}

```

# Example 3

## Source 

```kotlin
fun foobar() {
    bar()  // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
    baz()  // uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
}
```

## Expected behavior

```kotlin
fun foobar() {
    // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
    bar()
    // uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
    baz()
}
```

## Observed behavior

```kotlin
// uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
fun foobar() // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff {
    bar()
    baz()
}
```

# Steps to Reproduce

Use examples above.

# Environment information

http://ktlint-demo.herokuapp.com/

Pull request:
### What's done:
* Fix logic of moving long inline comments
* Expand tests
* Add utilities for processing the nodes on specific line

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 9b141f3aba..b07bee05fb 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
@@ -8,7 +8,9 @@ import org.cqfn.diktat.ruleset.rules.DiktatRule
 import org.cqfn.diktat.ruleset.utils.KotlinParser
 import org.cqfn.diktat.ruleset.utils.appendNewlineMergingWhiteSpace
 import org.cqfn.diktat.ruleset.utils.calculateLineColByOffset
+import org.cqfn.diktat.ruleset.utils.findAllNodesWithConditionOnLine
 import org.cqfn.diktat.ruleset.utils.findParentNodeWithSpecificType
+import org.cqfn.diktat.ruleset.utils.getLineNumber
 import org.cqfn.diktat.ruleset.utils.hasChildOfType
 
 import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION_ENTRY
@@ -290,14 +292,19 @@ class LineLength(configRules: List<RulesConfig>) : DiktatRule(
                 removeChild(wrongNode)
             }
         } else {
-            wrongNode.treeParent.treeParent?.let {
+            if (wrongNode.treePrev.isWhiteSpace()) {
+                wrongNode.treeParent.removeChild(wrongNode.treePrev)
+            }
+
+            val newLineNodeOnPreviousLine = wrongNode.findAllNodesWithConditionOnLine(wrongNode.getLineNumber() - 1) {
+                it.elementType == WHITE_SPACE && it.textContains('\n')
+            }?.lastOrNull()
+
+            newLineNodeOnPreviousLine?.let {
                 val parent = wrongNode.treeParent
-                if (wrongNode.treePrev.isWhiteSpace()) {
-                    parent.removeChild(wrongNode.treePrev)
-                }
                 parent.removeChild(wrongNode)
-                it.addChild(wrongNode, parent)
-                it.addChild(PsiWhiteSpaceImpl("\n"), parent)
+                newLineNodeOnPreviousLine.treeParent.addChild(wrongNode, newLineNodeOnPreviousLine.treeNext)
+                newLineNodeOnPreviousLine.treeParent.addChild(PsiWhiteSpaceImpl("\n"), newLineNodeOnPreviousLine.treeNext.treeNext)
             }
         }
     }
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt
index d6dca4009c..93def29f70 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt
@@ -729,6 +729,41 @@ fun ASTNode.lastLineNumber() = getLineNumber() + text.count { it == '\n' }
  */
 fun ASTNode.calculateLineColByOffset() = buildPositionInTextLocator(text)
 
+/**
+ * Return all nodes located at the specific line
+ *
+ * @param line
+ * @return corresponding list of nodes
+ */
+fun ASTNode.findAllNodesOnLine(
+    line: Int
+): List<ASTNode>? {
+    val rootNode = this.getRootNode()
+    val fileLines = rootNode.text.lines()
+
+    if (line <= 0 || line > fileLines.size) {
+        return null
+    }
+
+    val positionByOffset = rootNode.calculateLineColByOffset()
+
+    val lineOffset = this.findOffsetByLine(line, positionByOffset)
+
+    return this.getAllNodesOnLine(lineOffset, line, positionByOffset).toList()
+}
+
+/**
+ * Return all nodes located at the specific line satisfying [condition]
+ *
+ * @param line
+ * @param condition
+ * @return corresponding list of nodes
+ */
+fun ASTNode.findAllNodesWithConditionOnLine(
+    line: Int,
+    condition: (ASTNode) -> Boolean
+): List<ASTNode>? = this.findAllNodesOnLine(line)?.filter(condition)
+
 /**
  * Retrieves file name from user data of this node
  *
@@ -767,6 +802,66 @@ fun ASTNode.hasEqBinaryExpression(): Boolean =
 fun ASTNode.getLineNumber(): Int =
         calculateLineNumber()
 
+private fun ASTNode.findOffsetByLine(line: Int, positionByOffset: (Int) -> Pair<Int, Int>): Int {
+    val currentLine = this.getLineNumber()
+    val currentOffset = this.startOffset
+
+    var forwardDirection = true
+    // We additionaly consider the offset and line for current node in aim to speed up the search
+    // and not start any time from beginning of file, if possible
+    // 
+    // If current line is closer to the requested line, start search from it
+    // otherwise search from the beginning of file
+    var lineOffset = if (line < currentLine) {
+        if ((currentLine - line) < line) {
+            forwardDirection = false
+            currentOffset
+        } else {
+            0
+        }
+    } else {
+        currentOffset
+    }
+
+    while (positionByOffset(lineOffset).first != line) {
+        if (forwardDirection) {
+            lineOffset++
+        } else {
+            lineOffset--
+        }
+    }
+    return lineOffset
+}
+
+@Suppress("UnsafeCallOnNullableType")
+private fun ASTNode.getAllNodesOnLine(
+    lineOffset: Int,
+    line: Int,
+    positionByOffset: (Int) -> Pair<Int, Int>
+): MutableSet<ASTNode> {
+    val rootNode = this.getRootNode()
+    var beginningOfLineOffset = lineOffset
+    var endOfLineOffset = lineOffset
+
+    while (beginningOfLineOffset > 0 && positionByOffset(beginningOfLineOffset - 1).first == line) {
+        beginningOfLineOffset--
+    }
+
+    while (endOfLineOffset < rootNode.text.length && positionByOffset(endOfLineOffset + 1).first == line) {
+        endOfLineOffset++
+    }
+
+    val nodesSet: MutableSet<ASTNode> = mutableSetOf()
+
+    var currentOffset = beginningOfLineOffset
+    while (currentOffset <= endOfLineOffset) {
+        nodesSet.add(rootNode.psi.findElementAt(currentOffset)!!.node)
+        currentOffset++
+    }
+
+    return nodesSet
+}
+
 /**
  * This function calculates line number instead of using cached values.
  * It should be used when AST could be previously mutated by auto fixers.


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 3b987b50a5..4271d9a117 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
@@ -25,6 +25,11 @@ class LineLengthFixTest : FixTestBase("test/paragraph3/long_line", ::LineLength)
         fixAndCompare("LongLineCommentExpected.kt", "LongLineCommentTest.kt", rulesConfigListLineLength)
     }
 
+    @Test
+    fun `should fix long inline comments`() {
+        fixAndCompare("LongInlineCommentsExpected.kt", "LongInlineCommentsTest.kt", rulesConfigListLineLength)
+    }
+
     @Test
     fun `should not fix long comment which located on the line length limit`() {
         fixAndCompare("LongLineCommentExpected2.kt", "LongLineCommentTest2.kt", rulesConfigListDefaultLineLength)
diff --git a/diktat-rules/src/test/resources/test/paragraph3/long_line/LongInlineCommentsExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongInlineCommentsExpected.kt
new file mode 100644
index 0000000000..4e9bf7519b
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongInlineCommentsExpected.kt
@@ -0,0 +1,69 @@
+package test.paragraph3.long_line
+
+fun foobar() {
+    foo(
+        // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+bar()
+    )
+}
+
+fun foobar() {
+    foo(
+        //ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+bar()
+    )
+}
+
+fun foobar() {
+    foo(
+        // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+bar(),
+        // uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
+baz()
+    )
+}
+
+fun foobar() {
+    // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+bar()
+    // uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
+baz()
+}
+
+enum class SomeEnum {
+    // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+FOO,
+    // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+BAR,
+    // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+BAZ,
+};
+
+fun foo() {
+    when (node.elementType) {
+        ElementType.CLASS, ElementType.FUN, ElementType.PROPERTY -> checkBlankLineAfterKdoc(node)
+        // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ElementType.IF -> handleIfElse(node)
+        ElementType.EOL_COMMENT, ElementType.BLOCK_COMMENT -> handleEolAndBlockComments(node, configuration)
+        ElementType.KDOC -> handleKdocComments(node, configuration)
+        // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+else -> {
+            // this is a generated else block
+        }
+    }
+}
+
+// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+fun foo() {
+    // bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+if (a) {
+        // cccccccccccccccccccccccccccccccccccccccccc
+a()
+    // dddddddddddddddddddddddddddddddddddddddddd
+} else {
+        // eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
+b()
+    // ffffffffffffffffffffffffffffffffffffffffff
+}
+// gggggggggggggggggggggggggggggggggggggggggggggggggggg
+}
\ No newline at end of file
diff --git a/diktat-rules/src/test/resources/test/paragraph3/long_line/LongInlineCommentsTest.kt b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongInlineCommentsTest.kt
new file mode 100644
index 0000000000..9868fc751a
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph3/long_line/LongInlineCommentsTest.kt
@@ -0,0 +1,51 @@
+package test.paragraph3.long_line
+
+fun foobar() {
+    foo(
+        bar() // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+    )
+}
+
+fun foobar() {
+    foo(
+        bar()//ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+    )
+}
+
+fun foobar() {
+    foo(
+        bar(), // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+        baz()  // uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
+    )
+}
+
+fun foobar() {
+    bar()  // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+    baz()  // uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
+}
+
+enum class SomeEnum {
+    FOO, // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+    BAR, // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+    BAZ, // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+};
+
+fun foo() {
+    when (node.elementType) {
+        ElementType.CLASS, ElementType.FUN, ElementType.PROPERTY -> checkBlankLineAfterKdoc(node)
+        ElementType.IF -> handleIfElse(node) // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+        ElementType.EOL_COMMENT, ElementType.BLOCK_COMMENT -> handleEolAndBlockComments(node, configuration)
+        ElementType.KDOC -> handleKdocComments(node, configuration)
+        else -> { // ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+            // this is a generated else block
+        }
+    }
+}
+
+fun foo() { // aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+    if (a) { // bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+        a() // cccccccccccccccccccccccccccccccccccccccccc
+    } else { // dddddddddddddddddddddddddddddddddddddddddd
+        b() // eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
+    } // ffffffffffffffffffffffffffffffffffffffffff
+} // gggggggggggggggggggggggggggggggggggggggggggggggggggg
\ No newline at end of file