Repository: analysis-dev/diktat (kotlin)

Issue:
MULTIPLE_INIT_BLOCKS: fixer can break the code
Diktat 1.0.0-RC2:

```
class A {
    var a: String

    init {
        val b: String = "a"
        a = b
    }
}
```

MULTIPLE_INIT_BLOCKS fixer can break the code:
```
class A {
    var a: String = b

    init {
        val b: String = "a"
    }
}
```

Pull request description:
### What's done:
* fixed bug in MULTIPLE_INIT_BLOCKS
Closes #966

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/SingleInitRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/SingleInitRule.kt
index 8e0fbf7649..4029a8855d 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/SingleInitRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/SingleInitRule.kt
@@ -3,14 +3,19 @@ package org.cqfn.diktat.ruleset.rules.chapter6.classes
 import org.cqfn.diktat.common.config.rules.RulesConfig
 import org.cqfn.diktat.ruleset.constants.Warnings.MULTIPLE_INIT_BLOCKS
 import org.cqfn.diktat.ruleset.rules.DiktatRule
+import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType
 import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType
 import org.cqfn.diktat.ruleset.utils.getIdentifierName
 
+import com.pinterest.ktlint.core.ast.ElementType
 import com.pinterest.ktlint.core.ast.ElementType.BLOCK
 import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY
 import com.pinterest.ktlint.core.ast.ElementType.CLASS_INITIALIZER
 import com.pinterest.ktlint.core.ast.ElementType.EQ
+import com.pinterest.ktlint.core.ast.ElementType.PRIMARY_CONSTRUCTOR
 import com.pinterest.ktlint.core.ast.ElementType.PROPERTY
+import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
+import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST
 import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
 import com.pinterest.ktlint.core.ast.parent
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
@@ -18,6 +23,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
 import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
 import org.jetbrains.kotlin.psi.KtBlockExpression
 import org.jetbrains.kotlin.psi.KtNameReferenceExpression
+import org.jetbrains.kotlin.psi.KtParameter
 import org.jetbrains.kotlin.psi.KtProperty
 import org.jetbrains.kotlin.psi.psiUtil.asAssignment
 import org.jetbrains.kotlin.psi.psiUtil.children
@@ -53,11 +59,20 @@ class SingleInitRule(configRules: List<RulesConfig>) : DiktatRule(
 
         // move property assignments from init block to property declarations
         node.findChildByType(CLASS_INITIALIZER)?.let { initBlock ->
-            val properties = node
+            val propertiesFromPrimaryConstructor = node
+                .treeParent
+                .findChildByType(PRIMARY_CONSTRUCTOR)
+                ?.findChildByType(VALUE_PARAMETER_LIST)
+                ?.children()
+                ?.filter { it.elementType == ElementType.VALUE_PARAMETER }
+                ?.map { it.psi as KtParameter }
+                ?.map { it.name }
+                ?.toList()
+            val propertiesFromClassBody = node
                 .children()
                 .filter { it.elementType == PROPERTY }
                 .toList()
-            moveAssignmentsToProperties(properties, initBlock)
+            moveAssignmentsToProperties(propertiesFromClassBody, propertiesFromPrimaryConstructor, initBlock)
         }
     }
 
@@ -80,7 +95,10 @@ class SingleInitRule(configRules: List<RulesConfig>) : DiktatRule(
     }
 
     @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION")
-    private fun moveAssignmentsToProperties(properties: List<ASTNode>, initBlock: ASTNode) {
+    private fun moveAssignmentsToProperties(
+        propertiesFromClassBody: List<ASTNode>,
+        propertiesFromPrimaryConstructor: List<String?>?,
+        initBlock: ASTNode) {
         initBlock
             .findChildByType(BLOCK)
             ?.run {
@@ -88,9 +106,14 @@ class SingleInitRule(configRules: List<RulesConfig>) : DiktatRule(
                     .statements
                     .mapNotNull { it.asAssignment() }
                     .filter { it.left is KtNameReferenceExpression }
+                    .filter { statement ->
+                        statement.right?.node?.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION)?.all { arg ->
+                            propertiesFromClassBody.any { (it.psi as KtProperty).name == arg.text } || propertiesFromPrimaryConstructor?.find { it == arg.text } != null
+                        } ?: false
+                    }
                     .groupBy { assignment ->
                         val assignedRef = assignment.left as KtNameReferenceExpression
-                        properties.find { (it.psi as KtProperty).name == assignedRef.getReferencedName() }
+                        propertiesFromClassBody.find { (it.psi as KtProperty).name == assignedRef.getReferencedName() }
                     }
                     .filterKeys { it != null }
                     .mapKeys { (k, _) -> k as ASTNode }


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/SingleInitRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/SingleInitRuleWarnTest.kt
index 283c280976..042199d84f 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/SingleInitRuleWarnTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/SingleInitRuleWarnTest.kt
@@ -47,7 +47,7 @@ class SingleInitRuleWarnTest : LintTestBase(::SingleInitRule) {
     fun `should warn if properties are assigned in init block`() {
         lintMethod(
             """
-                |class A(baseUrl: String) {
+                |class A(baseUrl: String, hardUrl: String) {
                 |    private val customUrl: String
                 |    init {
                 |        customUrl = "${'$'}baseUrl/myUrl"
@@ -57,4 +57,25 @@ class SingleInitRuleWarnTest : LintTestBase(::SingleInitRule) {
             LintError(3, 5, ruleId, "${Warnings.MULTIPLE_INIT_BLOCKS.warnText()} `init` block has assignments that can be moved to declarations", true)
         )
     }
+
+    @Test
+    @Tag(WarningNames.MULTIPLE_INIT_BLOCKS)
+    fun `shouldn't warn if property are assigned on property in init block`() {
+        lintMethod(
+            """
+                |class A {
+                |    var a: String
+                |    var c: String
+                |    
+                |    init {
+                |        val b: String = "a"
+                |        a = b
+                |        
+                |        val d: String = "c"
+                |        c = foo(d)
+                |    }
+                |}
+            """.trimMargin()
+        )
+    }
 }