Repository: analysis-dev/diktat (kotlin)

Issue:
ENUMS_SEPARATED fails to converge if last enum entry has comment on same line
## Describe the bug
The following code fails to converge with the ENUMS_SEPARATED inspection:
```
enum class Foo {
    A,
    B,
    C // some comment
}
```

It tries to ensure that the last enum entry has a trailing comma, but it is added at the end of the line.
Since the comma is part of the comment, this step will repeat and fails to converge.

## Expected behavior
Independently of whether a comment on the same line is considered a good style or not, the inspection should converge even with a comment present.

## Environment information
* diktat version: 1.1.0
* build tool (gradle): 7.4.2
* how is diktat run (CLI, plugin, etc.): spotless
* kotlin version: 1.6.20
* operating system: windows/ubuntu

Pull request:
### What's done:
* skip EOL_COMMENT, BLOCK_COMMENT, WHITE_SPACE to detect a location for COMMA

This pull request closes #1286

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EnumsSeparated.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EnumsSeparated.kt
index 30970edc44..d268b6b243 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EnumsSeparated.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EnumsSeparated.kt
@@ -9,10 +9,12 @@ import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType
 import org.cqfn.diktat.ruleset.utils.hasChildOfType
 import org.cqfn.diktat.ruleset.utils.isClassEnum
 
+import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
 import com.pinterest.ktlint.core.ast.ElementType.CLASS
 import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY
 import com.pinterest.ktlint.core.ast.ElementType.COMMA
 import com.pinterest.ktlint.core.ast.ElementType.ENUM_ENTRY
+import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT
 import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER
 import com.pinterest.ktlint.core.ast.ElementType.LBRACE
 import com.pinterest.ktlint.core.ast.ElementType.RBRACE
@@ -48,13 +50,22 @@ class EnumsSeparated(configRules: List<RulesConfig>) : DiktatRule(
             if (!it.treeNext.isWhiteSpaceWithNewline()) {
                 ENUMS_SEPARATED.warnAndFix(configRules, emitWarn, isFixMode, "enum entries must end with a line break",
                     it.startOffset, it) {
-                    it.appendNewlineMergingWhiteSpace(it.treeNext, it.treeNext)
+                    it.appendNewline()
                 }
             }
         }
         checkLastEnum(enumEntries.last())
     }
 
+    private fun ASTNode.appendNewline() {
+        val nextNode = this.treeNext
+        if (nextNode.elementType == WHITE_SPACE) {
+            (nextNode as LeafPsiElement).rawReplaceWithText("\n${nextNode.text}")
+        } else {
+            this.treeParent.addChild(PsiWhiteSpaceImpl("\n"), nextNode)
+        }
+    }
+
     private fun isEnumOneLine(nodes: List<ASTNode>) =
             nodes.dropLast(1).none { it.treeNext.isWhiteSpaceWithNewline() }
 
@@ -87,10 +98,19 @@ class EnumsSeparated(configRules: List<RulesConfig>) : DiktatRule(
         if (!node.hasChildOfType(COMMA)) {
             ENUMS_SEPARATED.warnAndFix(configRules, emitWarn, isFixMode, "last enum entry must end with a comma",
                 node.startOffset, node) {
-                node.addChild(LeafPsiElement(COMMA, ","), node.findChildByType(SEMICOLON)!!.treePrev)
+                val commaLocation = node.findChildByType(SEMICOLON)!!.findLatestTreePrevMatching {
+                    it.elementType !in setOf(EOL_COMMENT, BLOCK_COMMENT, WHITE_SPACE)
+                }
+                node.addChild(LeafPsiElement(COMMA, ","), commaLocation.treeNext)
             }
         }
     }
+
+    private fun ASTNode.findLatestTreePrevMatching(predicate: (ASTNode) -> Boolean): ASTNode {
+        val result = this.treePrev
+        return if (predicate(result)) result else result.findLatestTreePrevMatching(predicate)
+    }
+
     companion object {
         const val NAME_ID = "abq-enum-separated"
         private val simpleValue = listOf(IDENTIFIER, WHITE_SPACE, COMMA, SEMICOLON)


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/EnumsSeparatedFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/EnumsSeparatedFixTest.kt
index 94a65a92bd..0617b45687 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/EnumsSeparatedFixTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/EnumsSeparatedFixTest.kt
@@ -13,4 +13,10 @@ class EnumsSeparatedFixTest : FixTestBase("test/paragraph3/enum_separated", ::En
     fun `test enums split`() {
         fixAndCompare("EnumSeparatedExpected.kt", "EnumSeparatedTest.kt")
     }
+
+    @Test
+    @Tag(WarningNames.ENUMS_SEPARATED)
+    fun `last element with comment`() {
+        fixAndCompare("LastElementCommentExpected.kt", "LastElementCommentTest.kt")
+    }
 }
diff --git a/diktat-rules/src/test/resources/test/paragraph3/enum_separated/EnumSeparatedExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/enum_separated/EnumSeparatedExpected.kt
index 537470cb30..bd06af5c9f 100644
--- a/diktat-rules/src/test/resources/test/paragraph3/enum_separated/EnumSeparatedExpected.kt
+++ b/diktat-rules/src/test/resources/test/paragraph3/enum_separated/EnumSeparatedExpected.kt
@@ -7,7 +7,7 @@ enum class ENUM {
 enum class ENUM {
     RED(0xFF0000),
     GREEN(0x00FF00),
-    BLUE(0x0000FF) /*sdcsc*/,
+    BLUE(0x0000FF), /*sdcsc*/
 ;
 }
 
diff --git a/diktat-rules/src/test/resources/test/paragraph3/enum_separated/LastElementCommentExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/enum_separated/LastElementCommentExpected.kt
new file mode 100644
index 0000000000..bdc9943090
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph3/enum_separated/LastElementCommentExpected.kt
@@ -0,0 +1,96 @@
+package test.paragraph3.enum_separated
+
+enum class Foo0 {
+    A,
+    B,
+    C,
+;
+}
+
+enum class Foo11 {
+    A,
+    B,
+    C, // some comment 11
+;
+}
+
+enum class Foo12 {
+    A,
+    B,
+    C,// some comment 12
+;
+}
+
+enum class Foo13 {
+    A,
+    B,
+    C,
+;// some comment 13
+}
+
+enum class Foo14 {
+    A,
+    B,
+    C,
+; // some comment 14
+}
+
+enum class Foo21 {
+    A,
+    B,
+    C, /* some comment 21 */
+;
+}
+
+enum class Foo22 {
+    A,
+    B,
+    C,/* some comment 22 */
+;
+}
+
+enum class Foo23 {
+    A,
+    B,
+    C,
+;/* some comment 23 */
+}
+
+enum class Foo24 {
+    A,
+    B,
+    C,
+; /* some comment 24 */
+}
+
+enum class Foo31 {
+    A,
+    B,
+    C,
+;
+ /** some comment 31 */
+}
+
+enum class Foo32 {
+    A,
+    B,
+    C,
+;
+/** some comment 32 */
+}
+
+enum class Foo33 {
+    A,
+    B,
+    C,
+;
+/** some comment 33 */
+}
+
+enum class Foo34 {
+    A,
+    B,
+    C,
+;
+ /** some comment 34 */
+}
diff --git a/diktat-rules/src/test/resources/test/paragraph3/enum_separated/LastElementCommentTest.kt b/diktat-rules/src/test/resources/test/paragraph3/enum_separated/LastElementCommentTest.kt
new file mode 100644
index 0000000000..0705384cdd
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph3/enum_separated/LastElementCommentTest.kt
@@ -0,0 +1,79 @@
+package test.paragraph3.enum_separated
+
+enum class Foo0 {
+    A,
+    B,
+    C
+}
+
+enum class Foo11 {
+    A,
+    B,
+    C // some comment 11
+}
+
+enum class Foo12 {
+    A,
+    B,
+    C// some comment 12
+}
+
+enum class Foo13 {
+    A,
+    B,
+    C;// some comment 13
+}
+
+enum class Foo14 {
+    A,
+    B,
+    C; // some comment 14
+}
+
+enum class Foo21 {
+    A,
+    B,
+    C /* some comment 21 */
+}
+
+enum class Foo22 {
+    A,
+    B,
+    C/* some comment 22 */
+}
+
+enum class Foo23 {
+    A,
+    B,
+    C;/* some comment 23 */
+}
+
+enum class Foo24 {
+    A,
+    B,
+    C; /* some comment 24 */
+}
+
+enum class Foo31 {
+    A,
+    B,
+    C /** some comment 31 */
+}
+
+enum class Foo32 {
+    A,
+    B,
+    C/** some comment 32 */
+}
+
+enum class Foo33 {
+    A,
+    B,
+    C;/** some comment 33 */
+}
+
+enum class Foo34 {
+    A,
+    B,
+    C; /** some comment 34 */
+}
\ No newline at end of file