Repository: analysis-dev/diktat (kotlin)

Issue:
[false-negative] CommentsRule doesn't detect commented out class declaration
## Describe the bug
```kotlin
// class Test: Exception()
```
should cause `CommentsRule` trigger

## Environment information
* diktat version: 0.1.3-SNAPSHOT

Pull request:
### What's done:
 * Added logic
 * Added tests

This pull request closes #462

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/CommentsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/CommentsRule.kt
index f16c73fd38..2be92b7709 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/CommentsRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/comments/CommentsRule.kt
@@ -93,7 +93,7 @@ class CommentsRule(private val configRules: List<RulesConfig>) : Rule("comments"
     private fun getOffsetsToTextBlocksFromEolComments(node: ASTNode): List<Pair<Int, String>> {
         val comments = node
             .findAllNodesWithSpecificType(EOL_COMMENT)
-            .filter { !it.text.contains(eolCommentStart) }
+            .filter { !it.text.contains(eolCommentStart) || isCodeAfterCommentStart(it.text) }
         return if (comments.isNotEmpty()) {
             val result = mutableListOf(mutableListOf(comments.first()))
             comments
@@ -116,10 +116,30 @@ class CommentsRule(private val configRules: List<RulesConfig>) : Rule("comments"
         }
     }
 
+    /**
+     * This is a very rare case. We should check this cases for 4 things:
+     *
+     * 1. If it is a class/object at the beginning of the line
+     * 2. If it is a function
+     * 3. If it is import/package implementation
+     * 4. If it is }. This case is used when } goes after one space and it is closing class or fun
+     */
+    private fun isCodeAfterCommentStart(text: String): Boolean {
+        val textWithoutCommentStartToken = text.removePrefix("//").trim()
+        return codeFileStartCases.any { textWithoutCommentStartToken.contains(it) }
+    }
+
+    @Suppress("MaxLineLength")
     companion object {
         private val importKeyword = KtTokens.IMPORT_KEYWORD.value
         private val packageKeyword = KtTokens.PACKAGE_KEYWORD.value
         private val importOrPackage = """($importKeyword|$packageKeyword) """.toRegex()
+        private val classRegex =
+                """^\s*(public|private|protected)*\s*(internal)*\s*(open|data|sealed)*\s*(internal)*\s*(class|object)\s+(\w+)(\(.*\))*(\s*:\s*\w+(\(.*\))*)?\s*\{*$""".toRegex()
+        private val importOrPackageRegex = """^(import|package)?\s+([a-zA-Z.])+;*$""".toRegex()
+        private val functionRegex = """^(public|private|protected)*\s*(override|abstract|actual|expect)*\s?fun\s+\w+(\(.*\))?(\s*:\s*\w+)?\s*[{=]${'$'}""".toRegex()
+        private val rightBraceRegex = """^\s*}$""".toRegex()
+        private val codeFileStartCases = listOf(classRegex, importOrPackageRegex, functionRegex, rightBraceRegex)
         private val eolCommentStart = """// \S""".toRegex()
     }
 }


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt
index 90dbffdf46..e7a0b8603e 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/comments/CommentedCodeTest.kt
@@ -96,7 +96,7 @@ class CommentedCodeTest : LintTestBase(::CommentsRule) {
 
     @Test
     @Tag(WarningNames.COMMENTED_OUT_CODE)
-    fun `Should warn if commented out function is detected (single line comments with surrounding text)`() {
+    fun `Should warn if commented out function is detected - single line comments with surrounding text`() {
         lintMethod(
             """
            |import org.junit.Test
@@ -153,7 +153,7 @@ class CommentedCodeTest : LintTestBase(::CommentsRule) {
 
     @Test
     @Tag(WarningNames.COMMENTED_OUT_CODE)
-    fun `Should warn if detects commented out code (example with IDEA style indents)`() {
+    fun `Should warn if detects commented out code example with IDEA style indents`() {
         lintMethod(
             """
                 |//import org.junit.Ignore
@@ -172,4 +172,88 @@ class CommentedCodeTest : LintTestBase(::CommentsRule) {
             LintError(6, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} fun foo(a: Int): Int {", false)
         )
     }
+
+    @Test
+    @Tag(WarningNames.COMMENTED_OUT_CODE)
+    fun `should trigger on class with one space after comment start token`() {
+        lintMethod(
+            """
+            |// class Test: Exception()
+            """.trimMargin(),
+            LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} class Test: Exception()", false))
+    }
+
+    @Test
+    @Tag(WarningNames.COMMENTED_OUT_CODE)
+    fun `should trigger on class with one space after comment start token and 2 modifiers #1`() {
+        lintMethod(
+            """
+            |// public data class Test(val some: Int): Exception()
+            """.trimMargin(),
+            LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} public data class Test(val some: Int): Exception()", false))
+    }
+
+    @Test
+    @Tag(WarningNames.COMMENTED_OUT_CODE)
+    fun `should trigger on class with one space after comment start token and 2 modifiers #2`() {
+        lintMethod(
+            """
+            |// internal sealed class Test: Exception()
+            """.trimMargin(),
+            LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} internal sealed class Test: Exception()", false))
+    }
+
+    @Test
+    @Tag(WarningNames.COMMENTED_OUT_CODE)
+    fun `should trigger on import with one space after comment start token`() {
+        lintMethod(
+            """
+            |// import some.org
+            """.trimMargin(),
+            LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} import some.org", false))
+    }
+
+    @Test
+    @Tag(WarningNames.COMMENTED_OUT_CODE)
+    fun `should trigger on package with one space after comment start token`() {
+        lintMethod(
+            """
+            |// package some.org
+            """.trimMargin(),
+            LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} package some.org", false))
+    }
+
+    @Test
+    @Tag(WarningNames.COMMENTED_OUT_CODE)
+    fun `should trigger on function with one space after comment start token - { sign`() {
+        lintMethod(
+            """
+            |// fun someFunc(name: String): Boolean {
+            |//     val a = 5
+            |// }
+            """.trimMargin(),
+            LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} fun someFunc(name: String): Boolean {", false))
+    }
+
+    @Test
+    @Tag(WarningNames.COMMENTED_OUT_CODE)
+    fun `should trigger on function with one space after comment start token - = sign`() {
+        lintMethod(
+            """
+            |// fun someFunc(name: String): Boolean =
+            |//     name.contains("a")
+            """.trimMargin(),
+            LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} fun someFunc(name: String): Boolean =", false))
+    }
+
+    @Test
+    @Tag(WarningNames.COMMENTED_OUT_CODE)
+    fun `should trigger on function with one space after comment start token pulbic modifier`() {
+        lintMethod(
+            """
+            |// public fun someFunc(name: String): Boolean =
+            |//     name.contains("a")
+            """.trimMargin(),
+            LintError(1, 1, ruleId, "${COMMENTED_OUT_CODE.warnText()} public fun someFunc(name: String): Boolean =", false))
+    }
 }
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt
index bbffa5b004..59f3191b01 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt
@@ -116,7 +116,8 @@ class DiktatSmokeTest : FixTestBase("test/smoke/src/main/kotlin",
         fixAndCompare("Example2Expected.kt", "Example2Test.kt")
         unfixedLintErrors.assertEquals(
             LintError(1, 1, "$DIKTAT_RULE_SET_ID:file-naming", "${FILE_NAME_INCORRECT.warnText()} Example2Test.kt_copy", true),  // todo this is a false one
-            LintError(1, 1, "$DIKTAT_RULE_SET_ID:header-comment", "${HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE.warnText()} there are 2 declared classes and/or objects", false)
+            LintError(1, 1, "$DIKTAT_RULE_SET_ID:header-comment", "${HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE.warnText()} there are 2 declared classes and/or objects", false),
+            LintError(14, 17, "$DIKTAT_RULE_SET_ID:comments", "${Warnings.COMMENTED_OUT_CODE.warnText()} private class Test : RuntimeException()", false)
         )
     }