Repository: analysis-dev/diktat (kotlin)

Issue:
DataClassesRule when class has non-properties in the constructor
## Describe the bug
```kotlin
class Example(map: Map) {
   val field = map.getOrDefault...
    val secondFiled = map.get ...
}
```
can be *theoretically* converted to data class by moving proporties to the primary constructor and creating a secondary one with `map` parameter which delegates to the primary, like this:
```kotlin
data class Example (val field: Type, val secondField: Type) {
    constructor(map: Map) : this (map.get(...), map.get(...))
}
```
But it creates more code and also leads to creating several constructors (primary constructor of `data class` can't be made `private` because it is exposed via `copy` method).

Should it be considered a case for triggering of `DataClassRule`?

## Environment information
* diktat version: 0.1.7

Pull request:
### What's done:
   * [x] Fixed bug
   * [x] Added test
closes #666

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/DataClassesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/DataClassesRule.kt
index 8bc64a4bf1..1baefc23d9 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/DataClassesRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/DataClassesRule.kt
@@ -18,13 +18,16 @@ import com.pinterest.ktlint.core.ast.ElementType.FUN
 import com.pinterest.ktlint.core.ast.ElementType.INNER_KEYWORD
 import com.pinterest.ktlint.core.ast.ElementType.MODIFIER_LIST
 import com.pinterest.ktlint.core.ast.ElementType.OPEN_KEYWORD
+import com.pinterest.ktlint.core.ast.ElementType.PRIMARY_CONSTRUCTOR
 import com.pinterest.ktlint.core.ast.ElementType.PROPERTY
 import com.pinterest.ktlint.core.ast.ElementType.PROPERTY_ACCESSOR
 import com.pinterest.ktlint.core.ast.ElementType.SEALED_KEYWORD
 import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_LIST
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
 import org.jetbrains.kotlin.psi.KtClass
+import org.jetbrains.kotlin.psi.KtClassBody
 import org.jetbrains.kotlin.psi.KtExpression
+import org.jetbrains.kotlin.psi.KtPrimaryConstructor
 
 /**
  * This rule checks if class can be made as data class
@@ -59,8 +62,18 @@ class DataClassesRule(private val configRule: List<RulesConfig>) : Rule("data-cl
         USE_DATA_CLASS.warn(configRule, emitWarn, isFixMode, "${(node.psi as KtClass).name}", node.startOffset, node)
     }
 
-    @Suppress("UnsafeCallOnNullableType", "FUNCTION_BOOLEAN_PREFIX")
+    @Suppress("UnsafeCallOnNullableType", "FUNCTION_BOOLEAN_PREFIX", "ComplexMethod")
     private fun ASTNode.canBeDataClass(): Boolean {
+        val isNotPropertyInClassBody = findChildByType(CLASS_BODY)?.let { (it.psi as KtClassBody).properties.isEmpty() } ?: true
+        val hasPropertyInConstructor = findChildByType(PRIMARY_CONSTRUCTOR)
+            ?.let { constructor ->
+                (constructor.psi as KtPrimaryConstructor)
+                    .valueParameters
+                    .run { isNotEmpty() && all { it.hasValOrVar() } }
+            } ?: false
+        if (isNotPropertyInClassBody && !hasPropertyInConstructor) {
+            return false
+        }
         val classBody = getFirstChildWithType(CLASS_BODY)
         if (hasChildOfType(MODIFIER_LIST)) {
             val list = getFirstChildWithType(MODIFIER_LIST)!!


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/DataClassesRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/DataClassesRuleWarnTest.kt
index 64d76db66c..db405a5ea8 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/DataClassesRuleWarnTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/DataClassesRuleWarnTest.kt
@@ -96,4 +96,56 @@ class DataClassesRuleWarnTest : LintTestBase(::DataClassesRule) {
                 """.trimMargin()
         )
     }
+
+    @Test
+    @Tag(USE_DATA_CLASS)
+    fun `should not trigger on classes with no property in constructor`() {
+        lintMethod(
+            """
+                    |class B(map: Map<Int,Int>) {}
+                    |
+                    |class Ab(val map: Map<Int, Int>, map: Map<Int, Int>) {}
+                    |
+                    |class A(val map: Map<Int, Int>) {}
+                    |
+                """.trimMargin(),
+            LintError(5, 1, ruleId, "${Warnings.USE_DATA_CLASS.warnText()} A")
+        )
+    }
+
+    @Test
+    @Tag(USE_DATA_CLASS)
+    fun `should not trigger on empty class`() {
+        lintMethod(
+            """
+                    |class B() {}
+                    |
+                    |class Ab{}
+                    |
+                """.trimMargin()
+        )
+    }
+
+    @Test
+    @Tag(USE_DATA_CLASS)
+    fun `should trigger on class without constructor but with property`() {
+        lintMethod(
+            """
+                    |class B() {
+                    |   val q = 10
+                    |}
+                    |
+                    |class Ab {
+                    |   val qw = 10 
+                    |}
+                    |
+                    |class Ba {
+                    |   val q = 10
+                    |   fun foo() = 10
+                    |}
+                """.trimMargin(),
+            LintError(1, 1, ruleId, "${Warnings.USE_DATA_CLASS.warnText()} B"),
+            LintError(5, 1, ruleId, "${Warnings.USE_DATA_CLASS.warnText()} Ab")
+        )
+    }
 }