Repository: analysis-dev/diktat (kotlin)

Issue:
FP ANNOTATION_NEW_LINE when there is a comment after an annotation
## Describe the bug
```kotlin
@ExperimentalStdlibApi  // to use `scan` on sequence
    @Suppress("WRONG_NEWLINES")
    override fun checkNode() {}
```

## Observed behavior
`[ANNOTATION_NEW_LINE] annotations must be on new line: @ExperimentalStdlibApi not on a single line`

## Environment information
* diktat version: 0.1.6-SNAPSHOT

Pull request:
### What's done:
   Fixed bug and added tests
   closes #609 

## Fixme
  The fix is ​​not working correctly.

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AnnotationNewLineRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AnnotationNewLineRule.kt
index f0adebc899..8cc0c7be50 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AnnotationNewLineRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AnnotationNewLineRule.kt
@@ -3,10 +3,7 @@ package org.cqfn.diktat.ruleset.rules
 import org.cqfn.diktat.common.config.rules.RulesConfig
 import org.cqfn.diktat.ruleset.constants.EmitType
 import org.cqfn.diktat.ruleset.constants.Warnings
-import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType
-import org.cqfn.diktat.ruleset.utils.getFirstChildWithType
-import org.cqfn.diktat.ruleset.utils.isBeginByNewline
-import org.cqfn.diktat.ruleset.utils.isFollowedByNewline
+import org.cqfn.diktat.ruleset.utils.*
 
 import com.pinterest.ktlint.core.Rule
 import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION_ENTRY
@@ -51,12 +48,13 @@ class AnnotationNewLineRule(private val configRules: List<RulesConfig>) : Rule("
         }
 
         node.getAllChildrenWithType(ANNOTATION_ENTRY).forEach {
-            if (!it.isFollowedByNewline() || !it.isBeginByNewline()) {
-                deleteSpaces(it, !it.isFollowedByNewline(), !it.isBeginByNewline())
+            if (!it.isFollowedByNewlineWithComment() || !it.isBeginByNewline()) {
+                deleteSpaces(it, !it.isFollowedByNewlineWithComment(), !it.isBeginByNewline())
             }
         }
     }
 
+    // fixme added handle for left side!
     private fun deleteSpaces(node: ASTNode,
                              rightSide: Boolean,
                              leftSide: Boolean) {
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 e4f1f59590..5f01b2e926 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
@@ -12,14 +12,19 @@ import org.cqfn.diktat.ruleset.rules.PackageNaming
 import com.pinterest.ktlint.core.KtLint
 import com.pinterest.ktlint.core.ast.ElementType
 import com.pinterest.ktlint.core.ast.ElementType.ANNOTATION_ENTRY
+import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
 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.FILE
 import com.pinterest.ktlint.core.ast.ElementType.FILE_ANNOTATION_LIST
+import com.pinterest.ktlint.core.ast.ElementType.IMPORT_LIST
 import com.pinterest.ktlint.core.ast.ElementType.INTERNAL_KEYWORD
+import com.pinterest.ktlint.core.ast.ElementType.KDOC
 import com.pinterest.ktlint.core.ast.ElementType.LBRACE
 import com.pinterest.ktlint.core.ast.ElementType.MODIFIER_LIST
 import com.pinterest.ktlint.core.ast.ElementType.OPERATION_REFERENCE
 import com.pinterest.ktlint.core.ast.ElementType.OVERRIDE_KEYWORD
+import com.pinterest.ktlint.core.ast.ElementType.PACKAGE_DIRECTIVE
 import com.pinterest.ktlint.core.ast.ElementType.PRIVATE_KEYWORD
 import com.pinterest.ktlint.core.ast.ElementType.PROTECTED_KEYWORD
 import com.pinterest.ktlint.core.ast.ElementType.PUBLIC_KEYWORD
@@ -138,12 +143,30 @@ fun ASTNode.isFollowedByNewline() =
             it.treeNext.elementType == WHITE_SPACE && it.treeNext.text.contains("\n")
         } ?: false
 
+/**
+ * This function is similar to isFollowedByNewline(), but there may be a comment after the node
+ */
+fun ASTNode.isFollowedByNewlineWithComment() =
+        parent({ it.treeNext != null }, strict = false)
+            ?.treeNext?.run {
+            when (elementType) {
+                WHITE_SPACE -> text.contains("\n")
+                EOL_COMMENT, BLOCK_COMMENT, KDOC -> isFollowedByNewline()
+                else -> false
+            } ||
+                    parent({ it.treeNext != null }, strict = false)?.let {
+                        it.treeNext.elementType == EOL_COMMENT && it.treeNext.isFollowedByNewline()
+                    } ?: false
+        } ?: false
+
 /**
  * Checks if there is a newline before this element. See [isFollowedByNewline] for motivation on parents check.
+ * Or if there is nothing before, it cheks, that there are empty imports and package before (Every FILE node has children of type IMPORT_LIST and PACKAGE)
  */
 fun ASTNode.isBeginByNewline() =
         parent({ it.treePrev != null }, strict = false)?.let {
-            it.treePrev.elementType == WHITE_SPACE && it.treePrev.text.contains("\n")
+            it.treePrev.elementType == WHITE_SPACE && it.treePrev.text.contains("\n") ||
+                    (it.treePrev.elementType == IMPORT_LIST && it.treePrev.isLeaf() && it.treePrev.treePrev.isLeaf())
         } ?: false
 
 /**


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/AnnotationNewLineRuleFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/AnnotationNewLineRuleFixTest.kt
index 312686d13f..c0f72bf533 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/AnnotationNewLineRuleFixTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/AnnotationNewLineRuleFixTest.kt
@@ -19,4 +19,10 @@ class AnnotationNewLineRuleFixTest : FixTestBase("test/paragraph3/annotations",
     fun `should fix constructor annotations`() {
         fixAndCompare("AnnotationConstructorSingleLineExpected.kt", "AnnotationConstructorSingleLineTest.kt")
     }
+
+    @Test
+    @Tag(WarningNames.ANNOTATION_NEW_LINE)
+    fun `shouldn't fix correct annotation with comment`() {
+        fixAndCompare("AnnotationCommentExpected.kt", "AnnotationCommentTest.kt")
+    }
 }
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/AnnotationNewLineRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/AnnotationNewLineRuleWarnTest.kt
index c0c3f31d04..83effae5eb 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/AnnotationNewLineRuleWarnTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/AnnotationNewLineRuleWarnTest.kt
@@ -18,7 +18,6 @@ class AnnotationNewLineRuleWarnTest : LintTestBase(::AnnotationNewLineRule) {
     fun `annotation class test good`() {
         lintMethod(
             """
-                    |
                     |@SomeAnnotation
                     |@SecondAnnotation
                     |class A {
@@ -267,4 +266,42 @@ class AnnotationNewLineRuleWarnTest : LintTestBase(::AnnotationNewLineRule) {
                 """.trimMargin()
         )
     }
+
+    @Test
+    @Tag(WarningNames.ANNOTATION_NEW_LINE)
+    fun `no warn in correct annotation with comment`() {
+        lintMethod(
+            """
+                    |@ExperimentalStdlibApi  // to use `scan` on sequence
+                    |   @Suppress("WRONG_NEWLINES")
+                    |   override fun checkNode() {}
+                """.trimMargin()
+        )
+    }
+
+    @Test
+    @Tag(WarningNames.ANNOTATION_NEW_LINE)
+    fun `should fix annotation on same line with import`() {
+        lintMethod(
+            """
+                    |import qwe.qwe;@ExperimentalStdlibApi
+                    |   @Hello
+                    |   override fun checkNode() {}
+                """.trimMargin(),
+            LintError(1, 16, ruleId, "${Warnings.ANNOTATION_NEW_LINE.warnText()} @ExperimentalStdlibApi not on a single line", true),
+        )
+    }
+
+    @Test
+    @Tag(WarningNames.ANNOTATION_NEW_LINE)
+    fun `should fix annotation on same line with package`() {
+        lintMethod(
+            """
+                    |package dfgh.dfgh;@ExperimentalStdlibApi
+                    |   @Hello
+                    |   override fun checkNode() {}
+                """.trimMargin(),
+            LintError(1, 19, ruleId, "${Warnings.ANNOTATION_NEW_LINE.warnText()} @ExperimentalStdlibApi not on a single line", true),
+        )
+    }
 }
diff --git a/diktat-rules/src/test/resources/test/paragraph3/annotations/AnnotationCommentExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/annotations/AnnotationCommentExpected.kt
new file mode 100644
index 0000000000..98feb967a1
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph3/annotations/AnnotationCommentExpected.kt
@@ -0,0 +1,4 @@
+@Hello() // hello
+@Hi
+class A {
+}
\ No newline at end of file
diff --git a/diktat-rules/src/test/resources/test/paragraph3/annotations/AnnotationCommentTest.kt b/diktat-rules/src/test/resources/test/paragraph3/annotations/AnnotationCommentTest.kt
new file mode 100644
index 0000000000..98feb967a1
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph3/annotations/AnnotationCommentTest.kt
@@ -0,0 +1,4 @@
+@Hello() // hello
+@Hi
+class A {
+}
\ No newline at end of file