Repository: analysis-dev/diktat (kotlin)

Issue:
Messy reordering by DiktatRuleSet
## Describe the bug
```kotlin
class Example {
companion object {
        private val keywordsWithSpaceAfter = TokenSet.create(
                // these keywords are followed by {
                ELSE_KEYWORD, TRY_KEYWORD, DO_KEYWORD, FINALLY_KEYWORD, INIT_KEYWORD,
                // these keywords are followed by (
                FOR_KEYWORD, IF_KEYWORD, WHILE_KEYWORD, CATCH_KEYWORD,
                // these keywords can be followed by either { or (
                WHEN_KEYWORD
        )

        val operatorsWithNoWhitespace = TokenSet.create(DOT, RANGE, COLONCOLON, SAFE_ACCESS, EXCLEXCL)

        // this is the number of parent nodes needed to check if this node is lambda from argument list
        private const val numParentsForLambda = 3
    }
}
```
is autocorrected into
```kotlin
class Example {
    companion object {
        val operatorsWithNoWhitespace = TokenSet.create(DOT, RANGE, COLONCOLON, SAFE_ACCESS, EXCLEXCL)// this is the number of parent nodes needed to check if this node is lambda
        // from argument list
        private const val NUM_PARENTS_FOR_LAMBDA = 3
        private val keywordsWithSpaceAfter = TokenSet.create(
                // these keywords are followed by {
                ELSE_KEYWORD, TRY_KEYWORD, DO_KEYWORD, FINALLY_KEYWORD, INIT_KEYWORD,
                // these keywords are followed by (
                FOR_KEYWORD, IF_KEYWORD, WHILE_KEYWORD, CATCH_KEYWORD,
                // these keywords can be followed by either { or (
                WHEN_KEYWORD
        )

        
    }
}
```
and it has a whole lot of problems:
- comment is getting split and it's parts are shuffled
- blank lines are left
- comments are incorrectly formatted after move

## Environment information
* diktat version: 0.1.3

Pull request:
### What's done:
   Fixed bug and added tests
   Closes #482

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ClassLikeStructuresOrderRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ClassLikeStructuresOrderRule.kt
index 4cc9a22e20..f00811b353 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ClassLikeStructuresOrderRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ClassLikeStructuresOrderRule.kt
@@ -4,14 +4,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig
 import org.cqfn.diktat.ruleset.constants.EmitType
 import org.cqfn.diktat.ruleset.constants.Warnings.BLANK_LINE_BETWEEN_PROPERTIES
 import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES
-import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType
-import org.cqfn.diktat.ruleset.utils.findLeafWithSpecificType
-import org.cqfn.diktat.ruleset.utils.getIdentifierName
-import org.cqfn.diktat.ruleset.utils.handleIncorrectOrder
-import org.cqfn.diktat.ruleset.utils.isFollowedByNewline
-import org.cqfn.diktat.ruleset.utils.leaveExactlyNumNewLines
-import org.cqfn.diktat.ruleset.utils.loggerPropertyRegex
-import org.cqfn.diktat.ruleset.utils.moveChildBefore
+import org.cqfn.diktat.ruleset.utils.*
 
 import com.pinterest.ktlint.core.Rule
 import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
@@ -22,22 +15,29 @@ import com.pinterest.ktlint.core.ast.ElementType.COMPANION_KEYWORD
 import com.pinterest.ktlint.core.ast.ElementType.CONST_KEYWORD
 import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT
 import com.pinterest.ktlint.core.ast.ElementType.FUN
+import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER
 import com.pinterest.ktlint.core.ast.ElementType.KDOC
 import com.pinterest.ktlint.core.ast.ElementType.LATEINIT_KEYWORD
+import com.pinterest.ktlint.core.ast.ElementType.LBRACE
 import com.pinterest.ktlint.core.ast.ElementType.MODIFIER_LIST
 import com.pinterest.ktlint.core.ast.ElementType.OBJECT_DECLARATION
 import com.pinterest.ktlint.core.ast.ElementType.PRIVATE_KEYWORD
 import com.pinterest.ktlint.core.ast.ElementType.PROPERTY
+import com.pinterest.ktlint.core.ast.ElementType.RBRACE
 import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
 import com.pinterest.ktlint.core.ast.ElementType.SECONDARY_CONSTRUCTOR
 import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
+import com.pinterest.ktlint.core.ast.children
+import com.pinterest.ktlint.core.ast.isPartOfComment
 import com.pinterest.ktlint.core.ast.nextSibling
 import com.pinterest.ktlint.core.ast.parent
 import com.pinterest.ktlint.core.ast.prevSibling
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
 import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
 import org.jetbrains.kotlin.psi.KtProperty
 import org.jetbrains.kotlin.psi.psiUtil.parents
+import org.jetbrains.kotlin.psi.psiUtil.siblings
 
 /**
  * Rule that checks order of declarations inside classes, interfaces and objects.
@@ -59,7 +59,7 @@ class ClassLikeStructuresOrderRule(private val configRules: List<RulesConfig>) :
         }
     }
 
-    @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION")
+    @Suppress("UnsafeCallOnNullableType", "LongMethod", "ComplexMethod", "TOO_LONG_FUNCTION")
     private fun checkDeclarationsOrderInClass(node: ASTNode) {
         val allProperties = node.getChildren(TokenSet.create(PROPERTY))
         val constProperties = allProperties.filter { it.findLeafWithSpecificType(CONST_KEYWORD) != null }.toMutableList()
@@ -88,28 +88,35 @@ class ClassLikeStructuresOrderRule(private val configRules: List<RulesConfig>) :
                 } ?: false
             }
             .let { it.first.toMutableList() to it.second.toMutableList() }
-        val companion = node.getChildren(TokenSet.create(OBJECT_DECLARATION))
-            .find { it.findChildByType(MODIFIER_LIST)?.findLeafWithSpecificType(COMPANION_KEYWORD) != null }
-        val blocks = Blocks(AllProperties(loggers, constProperties, properties, lateInitProperties),
-            initBlocks, constructors, methods, usedClasses, listOfNotNull(companion).toMutableList(),
+        val (companionObject, objects) = node.getChildren(TokenSet.create(OBJECT_DECLARATION))
+            .partition { it.findChildByType(MODIFIER_LIST)?.findLeafWithSpecificType(COMPANION_KEYWORD) != null }
+        val blocks = Blocks(AllProperties(loggers, constProperties, properties, lateInitProperties), objects.toMutableList(),
+            initBlocks, constructors, methods, usedClasses, companionObject.toMutableList(),
             unusedClasses)
-
-        blocks
             .allBlockFlattened()
-            .reversed()
-            .handleIncorrectOrder(blocks::getSiblingBlocks) { astNode, beforeThisNode ->
-                WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES.warnAndFix(configRules, emitWarn, isFixMode, "${astNode.elementType}: ${astNode.text}", astNode.startOffset, astNode) {
-                    val replacement = node.moveChildBefore(astNode, beforeThisNode, true)
-                    replacement.oldNodes.forEachIndexed { idx, oldNode ->
+            .map { astNode ->
+                Pair(astNode, astNode.siblings(false).takeWhile { it.elementType == WHITE_SPACE || it.isPartOfComment()}.toList())
+            }
+
+        val classChildren = node.children().filter { it.elementType in childrenTypes }.toList()
+        if (classChildren != blocks.map { it.first }) {
+            blocks.filterIndexed { index, pair -> classChildren[index] != pair.first }
+                .forEach { listOfChildren ->
+                    val astNode = listOfChildren.first
+                    WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES.warnAndFix(configRules, emitWarn, isFixMode,
+                        "${astNode.elementType}: ${astNode.findChildByType(IDENTIFIER)?.text ?: astNode.text}", astNode.startOffset, astNode) {
+                        node.removeRange(node.findChildByType(LBRACE)!!.treeNext, node.findChildByType(RBRACE)!!)
                         blocks
-                            .allBlocks()
-                            .find { oldNode in it }
-                            ?.apply {
-                                this[indexOf(oldNode)] = replacement.newNodes[idx]
+                            .reversed()
+                            .map { bodyChild ->
+                                node.addChild(bodyChild.first, node.children().toList()[1])
+                                bodyChild.second.map { node.addChild(it, node.children().toList()[1]) }
                             }
+                        node.addChild(PsiWhiteSpaceImpl("\n"), node.lastChildNode)
+                        // fixme maybe wrong space between nodes
                     }
                 }
-            }
+        }
     }
 
     @Suppress("UnsafeCallOnNullableType")
@@ -137,9 +144,8 @@ class ClassLikeStructuresOrderRule(private val configRules: List<RulesConfig>) :
         val actualNewLines = whiteSpaceBefore.text.count { it == '\n' }
         // for some cases (now - if this or previous property has custom accessors), blank line is allowed before it
         if (!hasCustomAccessors && actualNewLines != numRequiredNewLines ||
-                hasCustomAccessors && actualNewLines > numRequiredNewLines
-        ) {
-            BLANK_LINE_BETWEEN_PROPERTIES.warnAndFix(configRules, emitWarn, isFixMode, node.getIdentifierName()!!.text, node.startOffset, node) {
+                hasCustomAccessors && actualNewLines > numRequiredNewLines) {
+            BLANK_LINE_BETWEEN_PROPERTIES.warnAndFix(configRules, emitWarn, isFixMode, node.getIdentifierName()?.text ?: node.text, node.startOffset, node) {
                 whiteSpaceBefore.leaveExactlyNumNewLines(numRequiredNewLines)
             }
         }
@@ -160,6 +166,7 @@ class ClassLikeStructuresOrderRule(private val configRules: List<RulesConfig>) :
 
     /**
      * @property allProperties an instance of [AllProperties]
+     * @property objects objects
      * @property initBlocks `init` blocks
      * @property constructors constructors
      * @property methods functions
@@ -168,6 +175,7 @@ class ClassLikeStructuresOrderRule(private val configRules: List<RulesConfig>) :
      * @property unusedClasses nested classes that are *not* used in the enclosing class
      */
     private data class Blocks(val allProperties: AllProperties,
+                              val objects: MutableList<ASTNode>,
                               val initBlocks: MutableList<ASTNode>,
                               val constructors: MutableList<ASTNode>,
                               val methods: MutableList<ASTNode>,
@@ -182,7 +190,7 @@ class ClassLikeStructuresOrderRule(private val configRules: List<RulesConfig>) :
          * @return all groups of structures in the class
          */
         fun allBlocks() = with(allProperties) {
-            listOf(loggers, constProperties, properties, lateInitProperties,
+            listOf(loggers, constProperties, properties, lateInitProperties, objects,
                 initBlocks, constructors, methods, usedClasses, companion, unusedClasses)
         }
 
@@ -190,18 +198,9 @@ class ClassLikeStructuresOrderRule(private val configRules: List<RulesConfig>) :
          * @return all blocks as a flattened list of [ASTNode]s
          */
         fun allBlockFlattened() = allBlocks().flatten()
+    }
 
-        /**
-         * @param node
-         * @return nodes between which the [node] should be placed
-         */
-        fun getSiblingBlocks(node: ASTNode): Pair<ASTNode?, ASTNode> {
-            require(node in allBlockFlattened())
-            val lastElement = allBlockFlattened().last()
-            val idx = allBlocks().indexOfFirst { node in it }
-            return (allBlocks().subList(0, idx) to allBlocks().subList(idx + 1, allBlocks().size))
-                .let { it.first.flatten() to it.second.flatten() }
-                .let { it.first.firstOrNull() to (it.second.firstOrNull() ?: lastElement.treeNext) }
-        }
+    companion object {
+        private val childrenTypes = listOf(PROPERTY, CLASS, CLASS_INITIALIZER, SECONDARY_CONSTRUCTOR, FUN, OBJECT_DECLARATION)
     }
 }


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/ClassLikeStructuresOrderFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/ClassLikeStructuresOrderFixTest.kt
index 67027cc912..61608d642e 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/ClassLikeStructuresOrderFixTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/ClassLikeStructuresOrderFixTest.kt
@@ -14,4 +14,10 @@ class ClassLikeStructuresOrderFixTest : FixTestBase("test/paragraph3/file_struct
     fun `should fix order and newlines between properties`() {
         fixAndCompare("DeclarationsInClassOrderExpected.kt", "DeclarationsInClassOrderTest.kt")
     }
+
+    @Test
+    @Tags(Tag(WarningNames.BLANK_LINE_BETWEEN_PROPERTIES), Tag(WarningNames.WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES))
+    fun `should fix order and newlines with comment`() {
+        fixAndCompare("OrderWithCommentExpected.kt", "OrderWithCommentTest.kt")
+    }
 }
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/ClassLikeStructuresOrderRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/ClassLikeStructuresOrderRuleWarnTest.kt
index 42fe08b392..e49ed03014 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/ClassLikeStructuresOrderRuleWarnTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/ClassLikeStructuresOrderRuleWarnTest.kt
@@ -58,8 +58,8 @@ class ClassLikeStructuresOrderRuleWarnTest : LintTestBase(::ClassLikeStructuresO
                     |    ${modifier}val log = LoggerFactory.getLogger(Example.javaClass)
                     |}
                 """.trimMargin(),
-                LintError(2, 5, ruleId, "${WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES.warnText()} PROPERTY: private val FOO = 42", true),
-                LintError(3, 5, ruleId, "${WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES.warnText()} PROPERTY: ${modifier}val log = LoggerFactory.getLogger(Example.javaClass)", true)
+                LintError(2, 5, ruleId, "${WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES.warnText()} PROPERTY: FOO", true),
+                LintError(3, 5, ruleId, "${WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES.warnText()} PROPERTY: log", true)
             )
         }
     }
@@ -179,4 +179,23 @@ class ClassLikeStructuresOrderRuleWarnTest : LintTestBase(::ClassLikeStructuresO
                 """.trimMargin()
         )
     }
+
+    @Test
+    @Tag(WarningNames.WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES)
+    fun `should warn if order is incorrect and property with comment`() {
+        lintMethod(
+            """
+                    class Example {
+                        companion object {
+                            val b = "q"
+                            
+                            // this
+                            private const val a = 3                                                   
+                        }
+                    }
+            """.trimMargin(),
+            LintError(3, 29, ruleId, "${WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES.warnText()} PROPERTY: b", true),
+            LintError(5, 29, ruleId, "${WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES.warnText()} PROPERTY: a", true)
+        )
+    }
 }
diff --git a/diktat-rules/src/test/resources/test/paragraph3/file_structure/DeclarationsInClassOrderExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/file_structure/DeclarationsInClassOrderExpected.kt
index 6b7c417bef..c2429b41a7 100644
--- a/diktat-rules/src/test/resources/test/paragraph3/file_structure/DeclarationsInClassOrderExpected.kt
+++ b/diktat-rules/src/test/resources/test/paragraph3/file_structure/DeclarationsInClassOrderExpected.kt
@@ -3,14 +3,14 @@ package test.paragraph3.file_structure
 class Example {
     private val log = LoggerFactory.getLogger(Example.javaClass)
 
+    // lorem ipsum
+    private val FOO = 42
+    private val BAR = 43
+
     /**
      * Dolor sit amet
      */
     private val BAZ = 44
-private val BAR = 43
-
-    // lorem ipsum
-    private val FOO = 42
     private lateinit var lateFoo: Int
     init {
         bar()
@@ -32,5 +32,4 @@ private val BAR = 43
     }
 
     class UnusedNested { }
-
-    }
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/diktat-rules/src/test/resources/test/paragraph3/file_structure/OrderWithCommentExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/file_structure/OrderWithCommentExpected.kt
new file mode 100644
index 0000000000..570f645544
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph3/file_structure/OrderWithCommentExpected.kt
@@ -0,0 +1,18 @@
+package test.paragraph3.file_structure
+
+class Example {
+    companion object {
+        private val log = LoggerFactory.getLogger(Example.javaClass)
+        val q = 10
+
+        /*
+         * Dolor sit amet
+         */
+
+
+        /**
+         * hi
+         */
+        private val qq = 10
+}
+}
\ No newline at end of file
diff --git a/diktat-rules/src/test/resources/test/paragraph3/file_structure/OrderWithCommentTest.kt b/diktat-rules/src/test/resources/test/paragraph3/file_structure/OrderWithCommentTest.kt
new file mode 100644
index 0000000000..622c68f314
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph3/file_structure/OrderWithCommentTest.kt
@@ -0,0 +1,17 @@
+package test.paragraph3.file_structure
+
+class Example {
+    companion object {
+        val q = 10
+        private val log = LoggerFactory.getLogger(Example.javaClass)
+        /*
+         * Dolor sit amet
+         */
+
+
+        /**
+         * hi
+         */
+        private val qq = 10
+    }
+}
\ No newline at end of file