Repository: analysis-dev/diktat (kotlin)

Issue:
Fix of the class members naming is inconsistent with KDoc fix
## Describe the bug
The coding style for class members is fixed, but KDOC annotations are created with original names instead of fixed ones.

## Source 

```kotlin
data class abc(
        val an_abc_member: String,
        val another_abc_member: String
)
```

## Expected behavior

```kotlin
/**
 * @property anAbcMember
 * @property anotherAbcMember
 */
data class Abc(
    val anAbcMember: String,
    val anotherAbcMember: String,
)
```

## Observed behavior

```kotlin
/**
 * @property an_abc_member
 * @property another_abc_member
 */
data class Abc(
    val anAbcMember: String,
    val anotherAbcMember: String,
)
```

## Environment information
https://ktlint-demo.herokuapp.com/

Pull request description:
### What's done:
* Updated logic
* Added test
* Fixed inconsistency: diktat fixes only private class properties from class body, but all from primary constructor

This pull request closes #1025

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/IdentifierNaming.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/IdentifierNaming.kt
index 5b7f8dae07..426a5f5402 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/IdentifierNaming.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/IdentifierNaming.kt
@@ -21,6 +21,7 @@ import org.cqfn.diktat.ruleset.constants.Warnings.VARIABLE_NAME_INCORRECT_FORMAT
 import org.cqfn.diktat.ruleset.rules.DiktatRule
 import org.cqfn.diktat.ruleset.utils.Style
 import org.cqfn.diktat.ruleset.utils.containsOneLetterOrZero
+import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType
 import org.cqfn.diktat.ruleset.utils.findChildAfter
 import org.cqfn.diktat.ruleset.utils.findLeafWithSpecificType
 import org.cqfn.diktat.ruleset.utils.findParentNodeWithSpecificType
@@ -37,6 +38,7 @@ import org.cqfn.diktat.ruleset.utils.isOverridden
 import org.cqfn.diktat.ruleset.utils.isPascalCase
 import org.cqfn.diktat.ruleset.utils.isTextLengthInRange
 import org.cqfn.diktat.ruleset.utils.isUpperSnakeCase
+import org.cqfn.diktat.ruleset.utils.kDocTags
 import org.cqfn.diktat.ruleset.utils.removePrefix
 import org.cqfn.diktat.ruleset.utils.search.findAllVariablesWithUsages
 import org.cqfn.diktat.ruleset.utils.toLowerCamelCase
@@ -46,10 +48,13 @@ import org.cqfn.diktat.ruleset.utils.toUpperSnakeCase
 import com.pinterest.ktlint.core.ast.ElementType
 import com.pinterest.ktlint.core.ast.ElementType.CATCH
 import com.pinterest.ktlint.core.ast.ElementType.CATCH_KEYWORD
+import com.pinterest.ktlint.core.ast.ElementType.CLASS
 import com.pinterest.ktlint.core.ast.ElementType.DESTRUCTURING_DECLARATION
 import com.pinterest.ktlint.core.ast.ElementType.DESTRUCTURING_DECLARATION_ENTRY
 import com.pinterest.ktlint.core.ast.ElementType.FILE
 import com.pinterest.ktlint.core.ast.ElementType.FUNCTION_TYPE
+import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER
+import com.pinterest.ktlint.core.ast.ElementType.KDOC
 import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
 import com.pinterest.ktlint.core.ast.ElementType.TYPE_PARAMETER
 import com.pinterest.ktlint.core.ast.ElementType.TYPE_REFERENCE
@@ -60,9 +65,14 @@ import org.jetbrains.kotlin.builtins.PrimitiveType
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
 import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
 import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
+import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
+import org.jetbrains.kotlin.psi.KtParameter
+import org.jetbrains.kotlin.psi.KtPrimaryConstructor
 import org.jetbrains.kotlin.psi.KtProperty
+import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
 import org.jetbrains.kotlin.psi.psiUtil.isPrivate
 import org.jetbrains.kotlin.psi.psiUtil.parents
+
 import java.util.Locale
 
 /**
@@ -156,14 +166,19 @@ class IdentifierNaming(configRules: List<RulesConfig>) : DiktatRule(
     @Suppress(
         "SAY_NO_TO_VAR",
         "TOO_LONG_FUNCTION",
-        "ComplexMethod"
+        "ComplexMethod",
+        "UnsafeCallOnNullableType",
     )
     private fun checkVariableName(node: ASTNode): List<ASTNode> {
         // special case for Destructuring declarations that can be treated as parameters in lambda:
         var namesOfVariables = extractVariableIdentifiers(node)
         // Only local private properties will be autofix in order not to break code if there are usages in other files.
         // Destructuring declarations are only allowed for local variables/values, so we don't need to calculate `isFix` for every node in `namesOfVariables`
-        val isFix = isFixMode && if (node.elementType == ElementType.PROPERTY) (node.psi as KtProperty).run { isLocal || isPrivate() } else true
+        val isPublicOrNonLocalProperty = if (node.elementType == ElementType.PROPERTY) (node.psi as KtProperty).run { !isLocal && !isPrivate() } else false
+        val isNonPrivatePrimaryConstructorParameter = (node.psi as? KtParameter)?.run {
+            hasValOrVar() && getParentOfType<KtPrimaryConstructor>(true)?.valueParameters?.contains(this) == true && !isPrivate()
+        } ?: false
+        val isFix = isFixMode && !(isPublicOrNonLocalProperty || isNonPrivatePrimaryConstructorParameter)
         namesOfVariables
             .forEach { variableName ->
                 // variable should not contain only one letter in it's name. This is a bad example: b512
@@ -195,6 +210,20 @@ class IdentifierNaming(configRules: List<RulesConfig>) : DiktatRule(
                             ?.findAllVariablesWithUsages { it.name == variableName.text }
                             ?.flatMap { it.value.toList() }
                             ?.forEach { (it.node.firstChildNode as LeafPsiElement).rawReplaceWithText(correctVariableName) }
+                        if (variableName.treeParent.psi.run {
+                            (this is KtProperty && isMember) ||
+                                    (this is KtParameter && getParentOfType<KtPrimaryConstructor>(true)?.valueParameters?.contains(this) == true)
+                        }) {
+                            // For class members also check `@property` KDoc tag.
+                            // If we are here, then `variableName` is definitely a node from a class.
+                            variableName.parent(CLASS)!!.findChildByType(KDOC)?.kDocTags()
+                                ?.firstOrNull {
+                                    it.knownTag == KDocKnownTag.PROPERTY && it.getSubjectName() == variableName.text
+                                }
+                                ?.run {
+                                    (getSubjectLink()!!.node.findAllDescendantsWithSpecificType(IDENTIFIER).single() as LeafPsiElement).rawReplaceWithText(correctVariableName)
+                                }
+                        }
                         (variableName as LeafPsiElement).rawReplaceWithText(correctVariableName)
                     }
                 }


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingFixTest.kt
index 5769ee761a..41c9ff2e81 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingFixTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingFixTest.kt
@@ -63,4 +63,10 @@ class IdentifierNamingFixTest : FixTestBase(
     fun `typeAlias name incorrect`() {
         fixAndCompare("identifiers/TypeAliasNameExpected.kt", "identifiers/TypeAliasNameTest.kt")
     }
+
+    @Test
+    @Tag(WarningNames.TYPEALIAS_NAME_INCORRECT_CASE)
+    fun `should update property name in KDoc after fixing`() {
+        fixAndCompare("identifiers/PropertyInKdocExpected.kt", "identifiers/PropertyInKdocTest.kt")
+    }
 }
diff --git a/diktat-rules/src/test/resources/test/paragraph1/naming/identifiers/PropertyInKdocExpected.kt b/diktat-rules/src/test/resources/test/paragraph1/naming/identifiers/PropertyInKdocExpected.kt
new file mode 100644
index 0000000000..2466f015a9
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph1/naming/identifiers/PropertyInKdocExpected.kt
@@ -0,0 +1,15 @@
+package test.paragraph1.naming.identifiers
+
+/**
+ * @property anAbcMember
+ * @property another_abc_member
+ * @property anDefMember
+ * @property anotherDefMember
+ */
+data class Abc(
+    private val anAbcMember: String,
+    val another_abc_member: String,
+) {
+    private val anDefMember: String = ""
+    private val anotherDefMember: String = ""
+}
diff --git a/diktat-rules/src/test/resources/test/paragraph1/naming/identifiers/PropertyInKdocTest.kt b/diktat-rules/src/test/resources/test/paragraph1/naming/identifiers/PropertyInKdocTest.kt
new file mode 100644
index 0000000000..9b157f9f1f
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/paragraph1/naming/identifiers/PropertyInKdocTest.kt
@@ -0,0 +1,15 @@
+package test.paragraph1.naming.identifiers
+
+/**
+ * @property an_abc_member
+ * @property another_abc_member
+ * @property an_def_member
+ * @property another_def_member
+ */
+data class abc(
+    private val an_abc_member: String,
+    val another_abc_member: String,
+) {
+    private val an_def_member: String = ""
+    private val another_def_member: String = ""
+}