Repository: analysis-dev/diktat (kotlin)

Issue:
`WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES` issue
## Describe the bug
After I try to run `diktatFix` of grade-plugin on a simple file (simplified example is in `Steps to reproduce` section) I get stack trace:
```
 ERROR org.cqfn.diktat.ruleset.rules.DiktatRule - Internal error has occurred in rule [class-like-structures]. Please make an issue on this bug at https://github.com/saveourtool/diKTat/.
                       As a workaround you can disable these inspections in yml config: <[BLANK_LINE_BETWEEN_PROPERTIES, WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES]>.
                       Root cause of the problem is in [/Users/sanyavertolet/StudioProjects/iomt-android1/app/src/main/java/com/iomt/android/SignupActivity.kt] file.
java.lang.IndexOutOfBoundsException: Index 7 out of bounds for length 7
        at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
        at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
        at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:266)
        at java.base/java.util.Objects.checkIndex(Objects.java:359)
        at java.base/java.util.ArrayList.get(ArrayList.java:427)
        at org.cqfn.diktat.ruleset.rules.chapter3.ClassLikeStructuresOrderRule.checkAndReorderBlocks(ClassLikeStructuresOrderRule.kt:141)
        at org.cqfn.diktat.ruleset.rules.chapter3.ClassLikeStructuresOrderRule.checkDeclarationsOrderInClass(ClassLikeStructuresOrderRule.kt:80)
        at org.cqfn.diktat.ruleset.rules.chapter3.ClassLikeStructuresOrderRule.logic(ClassLikeStructuresOrderRule.kt:53)
        at org.cqfn.diktat.ruleset.rules.DiktatRule.visit(DiktatRule.kt:52)
        at org.cqfn.diktat.ruleset.rules.OrderedRuleSet$Companion$OrderedRule.visit(OrderedRuleSet.kt:80)
        at com.pinterest.ktlint.core.KtLint$format$1.invoke(KtLint.kt:251)
        at com.pinterest.ktlint.core.KtLint$format$1.invoke(KtLint.kt:244)
        at com.pinterest.ktlint.core.internal.VisitorProvider$sequentialVisitor$1$1$1$1.invoke(VisitorProvider.kt:123)
        at com.pinterest.ktlint.core.internal.VisitorProvider$sequentialVisitor$1$1$1$1.invoke(VisitorProvider.kt:123)
        at com.pinterest.ktlint.core.ast.PackageKt.visit(package.kt:236)
        at com.pinterest.ktlint.core.ast.PackageKt.visit(package.kt:237)
        at com.pinterest.ktlint.core.ast.PackageKt.visit(package.kt:237)
        at com.pinterest.ktlint.core.ast.PackageKt.visit(package.kt:237)
        at com.pinterest.ktlint.core.ast.PackageKt.visit(package.kt:237)
        at com.pinterest.ktlint.core.internal.VisitorProvider$sequentialVisitor$1.invoke(VisitorProvider.kt:123)
        at com.pinterest.ktlint.core.internal.VisitorProvider$sequentialVisitor$1.invoke(VisitorProvider.kt:115)
        at com.pinterest.ktlint.core.KtLint.format(KtLint.kt:244)
        at com.pinterest.ktlint.internal.FileUtilsKt.formatFile(FileUtils.kt:202)
        at com.pinterest.ktlint.internal.KtlintCommandLine.process(KtlintCommandLine.kt:355)
        at com.pinterest.ktlint.internal.KtlintCommandLine.access$process(KtlintCommandLine.kt:48)
        at com.pinterest.ktlint.internal.KtlintCommandLine$lintFiles$3.invoke$lambda-1(KtlintCommandLine.kt:274)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
        at java.base/java.lang.Thread.run(Thread.java:833)
```


## Expected behavior
Given code should be formatted like that:
```Kotlin
class ClassName {

    companion object {
        private const val EMAIL_ERROR_TEXT = "Введите корректно e-mail"
        private const val LOGIN_ERROR_TEXT = "Не менее 2 символов"
        private const val NAME_ERROR_TEXT = "Не менее 2 символов"
        private const val PATRONYMIC_ERROR_TEXT = "Не менее 2 символов"
        private const val PHONE_NUMBER_ERROR_TEXT = "Введите корректно номер телефона"
        private const val SURNAME_ERROR_TEXT = "Не менее 2 символов"
        private const val TAG = "SignupActivity"
    }
}
```
## Observed behavior
Stack trace (you can find it above)

## Steps to Reproduce
Run `diktatFix` task of `grade-plugin` on this code snippet:
```Kotlin
class ClassName {

    companion object {
        private const val TAG = "SignupActivity"

        private const val NAME_ERROR_TEXT = "Не менее 2 символов"
        private const val SURNAME_ERROR_TEXT = "Не менее 2 символов"
        private const val PATRONYMIC_ERROR_TEXT = "Не менее 2 символов"

        private const val LOGIN_ERROR_TEXT = "Не менее 2 символов"
        private const val EMAIL_ERROR_TEXT = "Введите корректно e-mail"
        private const val PHONE_NUMBER_ERROR_TEXT = "Введите корректно номер телефона"
    }
}
```


## Environment information
* diktat version: diktat 1.2.1 with ktlint 0.46.1
* build tool (maven/gradle): gradle
* how is diktat run (CLI, plugin, etc.): gradle plugin
* kotlin version: 1.6.20-M1
* operating system: macOS Monterey v.12.3.1

upd: Same issue exists when running `diktatCheck`

Pull request:
### What's done:

 * `const` properties named `log` or `logger` are no longer treated as loggers.
 * `lateinit` properties named `log` or `logger` are no longer treated as loggers.
 * Arbitrary properties containing `log` or `logger` in their names (e.g.: `loginName` or `LOGIN_NAME`) are no longer treated as loggers.
 * A diagnostic check added.
 * An IOOBE is no longer thrown.
 * Closes #1516.

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ClassLikeStructuresOrderRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ClassLikeStructuresOrderRule.kt
index a2c51f2bb0..1b47ce6b48 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ClassLikeStructuresOrderRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ClassLikeStructuresOrderRule.kt
@@ -137,6 +137,23 @@ class ClassLikeStructuresOrderRule(configRules: List<RulesConfig>) : DiktatRule(
     @Suppress("UnsafeCallOnNullableType")
     private fun ASTNode.checkAndReorderBlocks(blocks: List<List<ASTNode>>) {
         val classChildren = this.children().filter { it.elementType in childrenTypes }.toList()
+
+        check(blocks.size == classChildren.size) {
+            StringBuilder().apply {
+                append("`classChildren` has a size of ${classChildren.size} while `blocks` has a size of ${blocks.size}$NEWLINE")
+
+                append("`blocks`:$NEWLINE")
+                blocks.forEachIndexed { index, block ->
+                    append("\t$index: ${block.firstOrNull()?.text}$NEWLINE")
+                }
+
+                append("`classChildren`:$NEWLINE")
+                classChildren.forEachIndexed { index, child ->
+                    append("\t$index: ${child.text}$NEWLINE")
+                }
+            }
+        }
+
         if (classChildren != blocks.map { it.first() }) {
             blocks.filterIndexed { index, pair -> classChildren[index] != pair.first() }
                 .forEach { listOfChildren ->
@@ -180,9 +197,22 @@ class ClassLikeStructuresOrderRule(configRules: List<RulesConfig>) : DiktatRule(
                 val allProperties = node.getAllChildrenWithType(PROPERTY)
                 val constProperties = allProperties.filterByModifier(CONST_KEYWORD)
                 val lateInitProperties = allProperties.filterByModifier(LATEINIT_KEYWORD)
-                val loggers = allProperties.filterByModifier(PRIVATE_KEYWORD).filter {
-                    it.getIdentifierName()!!.text.contains(loggerPropertyRegex)
-                }
+                val loggers = allProperties.filterByModifier(PRIVATE_KEYWORD)
+                    .filterNot { astNode ->
+                        /*
+                         * A `const` field named "logger" is unlikely to be a logger.
+                         */
+                        astNode in constProperties
+                    }
+                    .filterNot { astNode ->
+                        /*
+                         * A `lateinit` field named "logger" is unlikely to be a logger.
+                         */
+                        astNode in lateInitProperties
+                    }
+                    .filter { astNode ->
+                        astNode.getIdentifierName()?.text?.matches(loggerPropertyRegex) ?: false
+                    }
                 val properties = allProperties.filter { it !in lateInitProperties && it !in loggers && it !in constProperties }
                 return AllProperties(loggers, constProperties, properties, lateInitProperties)
             }
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt
index 0b74420c5d..a87343297d 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt
@@ -29,7 +29,13 @@ val KOTLIN = KtTokens.KEYWORDS
     .map { line -> line.toString() }
     .plus(KtTokens.SOFT_KEYWORDS.types.map { line -> line.toString() })
 
-val loggerPropertyRegex = "(log|LOG|logger)".toRegex()
+/**
+ * Either `log` or `logger`, case-insensitive.
+ *
+ * A name like `psychologist` or `LOGIN` won't be matched by this regular
+ * expression.
+ */
+val loggerPropertyRegex = "(?iu)^log(?:ger)?$".toRegex()
 
 /**
  * @return whether [this] string represents a Java keyword


Test patch:
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 00de7a6892..2d795bad41 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
@@ -1,6 +1,7 @@
 package org.cqfn.diktat.ruleset.chapter3
 
 import org.cqfn.diktat.common.config.rules.DIKTAT_RULE_SET_ID
+import org.cqfn.diktat.ruleset.chapter3.spaces.describe
 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.rules.chapter3.ClassLikeStructuresOrderRule
@@ -8,6 +9,8 @@ import org.cqfn.diktat.util.LintTestBase
 
 import com.pinterest.ktlint.core.LintError
 import generated.WarningNames
+import org.assertj.core.api.Assertions.assertThat
+import org.intellij.lang.annotations.Language
 import org.junit.jupiter.api.Tag
 import org.junit.jupiter.api.Test
 
@@ -16,6 +19,7 @@ class ClassLikeStructuresOrderRuleWarnTest : LintTestBase(::ClassLikeStructuresO
 
     // ===== order of declarations =====
 
+    @Language("kotlin")
     private fun codeTemplate(keyword: String) = """
                     |$keyword Example {
                     |    private val log = LoggerFactory.getLogger(Example.javaClass)
@@ -213,4 +217,69 @@ class ClassLikeStructuresOrderRuleWarnTest : LintTestBase(::ClassLikeStructuresO
             """.trimMargin()
         )
     }
+
+    /**
+     * An exception to the "loggers on top" rule.
+     *
+     * See [#1516](https://github.com/saveourtool/diktat/issues/1516).
+     */
+    @Test
+    @Tag(WarningNames.WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES)
+    fun `logger-like const property should not be reordered`() {
+        val code = """
+            |object C {
+            |    private const val A = "value"
+            |
+            |    private const val LOG = "value" // Not a logger
+            |}
+        """.trimMargin()
+
+        val actualErrors = lintResult(code)
+        assertThat(actualErrors)
+            .describedAs("lint result for ${code.describe()}")
+            .isEmpty()
+    }
+
+    /**
+     * An exception to the "loggers on top" rule.
+     *
+     * See [#1516](https://github.com/saveourtool/diktat/issues/1516).
+     */
+    @Test
+    @Tag(WarningNames.WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES)
+    fun `logger-like lateinit property should not be reordered`() {
+        val code = """
+            |object C {
+            |    private lateinit val A
+            |
+            |    private lateinit val LOG // Not a logger
+            |}
+        """.trimMargin()
+
+        val actualErrors = lintResult(code)
+        assertThat(actualErrors)
+            .describedAs("lint result for ${code.describe()}")
+            .isEmpty()
+    }
+
+    /**
+     * An exception to the "loggers on top" rule.
+     *
+     * See [#1516](https://github.com/saveourtool/diktat/issues/1516).
+     */
+    @Test
+    @Tag(WarningNames.WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES)
+    fun `property with a name containing 'log' is not a logger`() {
+        val code = """
+            |object C {
+            |    private val a = System.getProperty("os.name")
+            |    private val loginName = LoggerFactory.getLogger({}.javaClass)
+            |}
+        """.trimMargin()
+
+        val actualErrors = lintResult(code)
+        assertThat(actualErrors)
+            .describedAs("lint result for ${code.describe()}")
+            .isEmpty()
+    }
 }