Repository: analysis-dev/diktat (kotlin)

Issue:
Adapt `WHEN_WITHOUT_ELSE` according to new features of Kotlin 1.6
Now our 'generated else block' is treated as a compiler warning sometimes:
```kotlin
when (state.confirmationType) {
                ConfirmationType.NO_BINARY_CONFIRM, ConfirmationType.NO_CONFIRM -> submitExecutionRequest()
                ConfirmationType.DELETE_CONFIRM -> deleteProjectBuilder()
                else -> {
                    // this is a generated else block
                }
            }
```
causes
```
w: 'when' is exhaustive so 'else' is redundant here
```

Pull request description:
This pull request closes #1149 

## Actions checklist
* [ ] Added check if when consists only of enum-like entries (if so, no further check on this rule is performed)
* [ ] Added tests on checks

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/WhenMustHaveElseRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/WhenMustHaveElseRule.kt
index 9ba599ca00..d5ba82136f 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/WhenMustHaveElseRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/WhenMustHaveElseRule.kt
@@ -4,12 +4,17 @@ import org.cqfn.diktat.common.config.rules.RulesConfig
 import org.cqfn.diktat.ruleset.constants.Warnings.WHEN_WITHOUT_ELSE
 import org.cqfn.diktat.ruleset.rules.DiktatRule
 import org.cqfn.diktat.ruleset.utils.appendNewlineMergingWhiteSpace
+import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType
+import org.cqfn.diktat.ruleset.utils.getFirstChildWithType
+import org.cqfn.diktat.ruleset.utils.hasChildOfType
 import org.cqfn.diktat.ruleset.utils.hasParent
 import org.cqfn.diktat.ruleset.utils.isBeginByNewline
 
 import com.pinterest.ktlint.core.ast.ElementType
 import com.pinterest.ktlint.core.ast.ElementType.ARROW
+import com.pinterest.ktlint.core.ast.ElementType.BINARY_EXPRESSION
 import com.pinterest.ktlint.core.ast.ElementType.BLOCK
+import com.pinterest.ktlint.core.ast.ElementType.DOT_QUALIFIED_EXPRESSION
 import com.pinterest.ktlint.core.ast.ElementType.ELSE_KEYWORD
 import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT
 import com.pinterest.ktlint.core.ast.ElementType.EQ
@@ -18,7 +23,11 @@ import com.pinterest.ktlint.core.ast.ElementType.LBRACE
 import com.pinterest.ktlint.core.ast.ElementType.OPERATION_REFERENCE
 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.RETURN
+import com.pinterest.ktlint.core.ast.ElementType.WHEN_CONDITION_IN_RANGE
+import com.pinterest.ktlint.core.ast.ElementType.WHEN_CONDITION_IS_PATTERN
+import com.pinterest.ktlint.core.ast.ElementType.WHEN_CONDITION_WITH_EXPRESSION
 import com.pinterest.ktlint.core.ast.ElementType.WHEN_ENTRY
 import com.pinterest.ktlint.core.ast.prevSibling
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
@@ -48,7 +57,7 @@ class WhenMustHaveElseRule(configRules: List<RulesConfig>) : DiktatRule(
     }
 
     private fun checkEntries(node: ASTNode) {
-        if (!hasElse(node)) {
+        if (!hasElse(node) && !isOnlyEnumEntries(node)) {
             WHEN_WITHOUT_ELSE.warnAndFix(configRules, emitWarn, isFixMode, "else was not found", node.startOffset, node) {
                 val whenEntryElse = CompositeElement(WHEN_ENTRY)
                 if (!node.lastChildNode.isBeginByNewline()) {
@@ -63,6 +72,46 @@ class WhenMustHaveElseRule(configRules: List<RulesConfig>) : DiktatRule(
         }
     }
 
+    private fun isOnlyEnumEntries(node: ASTNode): Boolean {
+        val whenEntries = node.getAllChildrenWithType(WHEN_ENTRY)
+        val hasConditionsIsPattern = whenEntries.any { it.hasChildOfType(WHEN_CONDITION_IS_PATTERN) }
+        if (hasConditionsIsPattern) {
+            return false
+        }
+
+        val conditionsInRange = whenEntries.map {
+            it.getAllChildrenWithType(WHEN_CONDITION_IN_RANGE)
+        }.flatten()
+
+        val conditionsWithExpression = whenEntries.map {
+            it.getAllChildrenWithType(WHEN_CONDITION_WITH_EXPRESSION)
+        }.flatten()
+
+        val areOnlyEnumEntriesWithExpressions = if (conditionsWithExpression.isNotEmpty()) {
+            conditionsWithExpression.all {
+                it.hasChildOfType(DOT_QUALIFIED_EXPRESSION) || it.hasChildOfType(REFERENCE_EXPRESSION)
+            }
+        } else {
+            true
+        }
+
+        val areOnlyEnumEntriesInRanges = if (conditionsInRange.isNotEmpty()) {
+            conditionsInRange.map { it.getFirstChildWithType(BINARY_EXPRESSION) }
+                .all {
+                    val dotExpressionsCount = it?.getAllChildrenWithType(DOT_QUALIFIED_EXPRESSION)?.size ?: 0
+                    val referenceExpressionsCount = it?.getAllChildrenWithType(REFERENCE_EXPRESSION)?.size ?: 0
+                    dotExpressionsCount + referenceExpressionsCount == 2
+                }
+        } else {
+            true
+        }
+
+        if (areOnlyEnumEntriesWithExpressions && areOnlyEnumEntriesInRanges) {
+            return true
+        }
+        return false
+    }
+
     private fun isStatement(node: ASTNode): Boolean {
         // Checks if there is return before when
         if (node.hasParent(RETURN)) {


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/WhenMustHaveElseWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/WhenMustHaveElseWarnTest.kt
index 373eb7827b..575d608983 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/WhenMustHaveElseWarnTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/WhenMustHaveElseWarnTest.kt
@@ -90,4 +90,57 @@ class WhenMustHaveElseWarnTest : LintTestBase(::WhenMustHaveElseRule) {
                 """.trimMargin()
         )
     }
+
+    @Test
+    @Tag(WarningNames.WHEN_WITHOUT_ELSE)
+    fun `when in func only enum entries`() {
+        lintMethod(
+            """
+                |fun foo() {
+                |    val v: Enum
+                |    when (v) {
+                |        Enum.ONE, Enum.TWO -> foo()
+                |        Enum.THREE -> bar()
+                |        in Enum.FOUR..Enum.TEN -> boo()
+                |        ELEVEN -> anotherFoo()
+                |        in TWELVE..Enum.TWENTY -> anotherBar()
+                |    }
+                |}
+            """.trimMargin()
+        )
+    }
+
+    @Test
+    @Tag(WarningNames.WHEN_WITHOUT_ELSE)
+    fun `when in func not only enum entries`() {
+        lintMethod(
+            """
+                |fun foo() {
+                |    val v: Enum
+                |    when (v) {
+                |        Enum.ONE -> foo()
+                |        f(Enum.TWO) -> bar()
+                |    }
+                |}
+            """.trimMargin(),
+            LintError(3, 5, ruleId, "${Warnings.WHEN_WITHOUT_ELSE.warnText()} else was not found", true)
+        )
+    }
+
+    @Test
+    @Tag(WarningNames.WHEN_WITHOUT_ELSE)
+    fun `when in func not only enum entries but in ranges`() {
+        lintMethod(
+            """
+                |fun foo() {
+                |    val v: Enum
+                |    when (v) {
+                |        Enum.ONE -> foo()
+                |        in 1..5 -> bar()
+                |    }
+                |}
+            """.trimMargin(),
+            LintError(3, 5, ruleId, "${Warnings.WHEN_WITHOUT_ELSE.warnText()} else was not found", true)
+        )
+    }
 }
diff --git a/diktat-rules/src/test/resources/test/paragraph3/else_expected/ElseInWhenExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/else_expected/ElseInWhenExpected.kt
index 5de8d4f351..93d892e3b3 100644
--- a/diktat-rules/src/test/resources/test/paragraph3/else_expected/ElseInWhenExpected.kt
+++ b/diktat-rules/src/test/resources/test/paragraph3/else_expected/ElseInWhenExpected.kt
@@ -10,15 +10,21 @@ fun testWhenExpression() {
     when (directoryType) {
         TestEnum.ONE -> "d"
         TestEnum.TWO -> "-"
- else -> {
-// this is a generated else block
-}}
+    }
 
     val noElse = when (directoryType) {
         TestEnum.ONE -> "d"
         TestEnum.TWO -> "a"
     }
 
+    val v = 1
+    when (v) {
+        f(TestEnum.ONE) -> print("1")
+        f(TestEnum.TWO) -> print("2")
+    else -> {
+// this is a generated else block
+}}
+
     val inLambda = {x: Int -> when(x) {
         1 -> print(5)
     }
diff --git a/diktat-rules/src/test/resources/test/paragraph3/else_expected/ElseInWhenTest.kt b/diktat-rules/src/test/resources/test/paragraph3/else_expected/ElseInWhenTest.kt
index ccfb55df98..56d2196c41 100644
--- a/diktat-rules/src/test/resources/test/paragraph3/else_expected/ElseInWhenTest.kt
+++ b/diktat-rules/src/test/resources/test/paragraph3/else_expected/ElseInWhenTest.kt
@@ -9,13 +9,20 @@ fun testWhenExpression() {
 
     when (directoryType) {
         TestEnum.ONE -> "d"
-        TestEnum.TWO -> "-" }
+        TestEnum.TWO -> "-"
+    }
 
     val noElse = when (directoryType) {
         TestEnum.ONE -> "d"
         TestEnum.TWO -> "a"
     }
 
+    val v = 1
+    when (v) {
+        f(TestEnum.ONE) -> print("1")
+        f(TestEnum.TWO) -> print("2")
+    }
+
     val inLambda = {x: Int -> when(x) {
         1 -> print(5)
     }
diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt
index 7d2859d0ed..8b068a2bac 100644
--- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt
+++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt
@@ -36,9 +36,6 @@ internal fun createWithFile(runConfiguration: RunConfiguration,
 private fun foo(node: ASTNode) {
     when (node.elementType) {
         CLASS, FUN, PRIMARY_CONSTRUCTOR, SECONDARY_CONSTRUCTOR -> checkAnnotation(node)
-        else -> {
-            // this is a generated else block
-        }
     }
     val qwe = a && b
     val qwe = a &&