Repository: analysis-dev/diktat (kotlin)

Issue:
KdocComments rule should check if all @property tag subjects are actually present in the class

Pull request:
### What's done:
    Added new rule
closed #578

Patch:
diff --git a/diktat-analysis.yml b/diktat-analysis.yml
index a6a0163678..2010f51806 100644
--- a/diktat-analysis.yml
+++ b/diktat-analysis.yml
@@ -254,6 +254,9 @@
 # Checks that property in constructor doesn't contains comment
 - name: KDOC_NO_CONSTRUCTOR_PROPERTY
   enabled: true
+# Checks that property in KDoc present in class
+- name: KDOC_EXTRA_PROPERTY
+  enabled: true
 # Checks that KDoc in constructor has property tag but with comment inside constructor
 - name: KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT
   enabled: true
diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt
index a73ff5a170..52735c2a16 100644
--- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt
+++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt
@@ -81,6 +81,8 @@ public object WarningNames {
 
     public const val KDOC_NO_CONSTRUCTOR_PROPERTY: String = "KDOC_NO_CONSTRUCTOR_PROPERTY"
 
+    public const val KDOC_EXTRA_PROPERTY: String = "KDOC_EXTRA_PROPERTY"
+
     public const val KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT: String =
             "KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT"
 
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt
index f1b5c13b65..e0c7f06f0e 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt
@@ -65,6 +65,7 @@ enum class Warnings(
     KDOC_NO_EMPTY_TAGS(false, "2.2.1", "no empty descriptions in tag blocks are allowed"),
     KDOC_NO_DEPRECATED_TAG(true, "2.1.3", "KDoc doesn't support @deprecated tag, use @Deprecated annotation instead"),
     KDOC_NO_CONSTRUCTOR_PROPERTY(true, "2.1.1", "all properties from the primary constructor should be documented in a @property tag in KDoc"),
+    KDOC_EXTRA_PROPERTY(false, "2.1.1", "There is property in KDoc which is not present in the class"),
     KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT(true, "2.1.1", "replace comment before property with @property tag in class KDoc"),
     HEADER_WRONG_FORMAT(true, "2.2.1", "file header comments should be properly formatted"),
     HEADER_MISSING_OR_WRONG_COPYRIGHT(true, "2.2.1", "file header comment must include copyright information inside a block comment"),
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocComments.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocComments.kt
index c10caefd61..07d89af058 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocComments.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocComments.kt
@@ -4,24 +4,12 @@ import org.cqfn.diktat.common.config.rules.RulesConfig
 import org.cqfn.diktat.common.config.rules.getCommonConfiguration
 import org.cqfn.diktat.ruleset.constants.EmitType
 import org.cqfn.diktat.ruleset.constants.Warnings
+import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_EXTRA_PROPERTY
 import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_CONSTRUCTOR_PROPERTY
 import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT
 import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_CLASS_ELEMENTS
 import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_TOP_LEVEL
-import org.cqfn.diktat.ruleset.utils.KotlinParser
-import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType
-import org.cqfn.diktat.ruleset.utils.getFileName
-import org.cqfn.diktat.ruleset.utils.getFirstChildWithType
-import org.cqfn.diktat.ruleset.utils.getIdentifierName
-import org.cqfn.diktat.ruleset.utils.getRootNode
-import org.cqfn.diktat.ruleset.utils.hasChildOfType
-import org.cqfn.diktat.ruleset.utils.hasTestAnnotation
-import org.cqfn.diktat.ruleset.utils.isAccessibleOutside
-import org.cqfn.diktat.ruleset.utils.isLocatedInTest
-import org.cqfn.diktat.ruleset.utils.isOverridden
-import org.cqfn.diktat.ruleset.utils.isStandardMethod
-import org.cqfn.diktat.ruleset.utils.kDocTags
-import org.cqfn.diktat.ruleset.utils.splitPathToDirs
+import org.cqfn.diktat.ruleset.utils.*
 
 import com.pinterest.ktlint.core.Rule
 import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
@@ -38,6 +26,7 @@ import com.pinterest.ktlint.core.ast.ElementType.MODIFIER_LIST
 import com.pinterest.ktlint.core.ast.ElementType.PRIMARY_CONSTRUCTOR
 import com.pinterest.ktlint.core.ast.ElementType.PROPERTY
 import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER
+import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST
 import com.pinterest.ktlint.core.ast.ElementType.VAL_KEYWORD
 import com.pinterest.ktlint.core.ast.ElementType.VAR_KEYWORD
 import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
@@ -47,6 +36,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.com.intellij.psi.tree.TokenSet
 import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
+import org.jetbrains.kotlin.psi.KtParameterList
 import org.jetbrains.kotlin.psi.psiUtil.parents
 
 /**
@@ -79,6 +69,7 @@ class KdocComments(private val configRules: List<RulesConfig>) : Rule("kdoc-comm
                 FILE -> checkTopLevelDoc(node)
                 CLASS -> checkClassElements(node)
                 VALUE_PARAMETER -> checkValueParameter(node)
+                PRIMARY_CONSTRUCTOR -> checkParameterList(node.findChildByType(VALUE_PARAMETER_LIST))
                 else -> {
                     // this is a generated else block
                 }
@@ -86,6 +77,21 @@ class KdocComments(private val configRules: List<RulesConfig>) : Rule("kdoc-comm
         }
     }
 
+    private fun checkParameterList(node: ASTNode?) {
+        val kdocBeforeClass = node
+            ?.parent({ it.elementType == CLASS })
+            ?.findChildByType(KDOC) ?: return
+        val propertiesInKdoc = kdocBeforeClass
+            .kDocTags()
+            ?.filter { it.knownTag == KDocKnownTag.PROPERTY}
+        val propertyNames = (node.psi as KtParameterList)
+            .parameters
+            .mapNotNull { it.nameIdentifier?.text }
+        propertiesInKdoc
+            ?.filterNot { it.getSubjectName() == null || it.getSubjectName() in propertyNames }
+            ?.forEach { KDOC_EXTRA_PROPERTY.warn(configRules, emitWarn, isFixMode, it.text, it.node.startOffset, node) }
+    }
+
     @Suppress("UnsafeCallOnNullableType", "ComplexMethod")
     private fun checkValueParameter(node: ASTNode) {
         if (node.parents().none { it.elementType == PRIMARY_CONSTRUCTOR } ||
diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml
index 562e152463..99342d1c1e 100644
--- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml
+++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml
@@ -252,6 +252,9 @@
 # Checks that property in constructor doesn't contains comment
 - name: KDOC_NO_CONSTRUCTOR_PROPERTY
   enabled: true
+# Checks that property in KDoc present in class
+- name: KDOC_EXTRA_PROPERTY
+  enabled: true
 # Checks that KDoc in constructor has property tag but with comment inside constructor
 - name: KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT
   enabled: true
diff --git a/diktat-rules/src/main/resources/diktat-analysis.yml b/diktat-rules/src/main/resources/diktat-analysis.yml
index 9030e614e5..05983b1d53 100644
--- a/diktat-rules/src/main/resources/diktat-analysis.yml
+++ b/diktat-rules/src/main/resources/diktat-analysis.yml
@@ -254,6 +254,9 @@
 # Checks that KDoc in constructor has property tag
 - name: KDOC_NO_CONSTRUCTOR_PROPERTY
   enabled: true
+# Checks that property in KDoc present in class
+- name: KDOC_EXTRA_PROPERTY
+  enabled: true
 # Checks that KDoc in constructor has property tag but with comment inside constructor
 - name: KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT
   enabled: true
diff --git a/info/available-rules.md b/info/available-rules.md
index e67aa65cfb..481807504e 100644
--- a/info/available-rules.md
+++ b/info/available-rules.md
@@ -37,6 +37,7 @@
 |    2 |      2.3 | KDOC_NO_EMPTY_TAGS                      | Check: warns if KDoc tags have empty content                                                                                                                                 |  no |  ||
 |    2 |      2.3 | KDOC_NO_DEPRECATED_TAG                  | Check: warns if `@deprecated` is used in KDoc<br>Fix: adds `@Deprecated` annotation with message, removes tag                                                                | yes |  |Annotation's `replaceWith` field can be filled too|
 |    2 |      2.3 | KDOC_NO_CONSTRUCTOR_PROPERTY            | Check: warns if there is no property tag inside KDoc before constructor                                                                                                             | yes | -|
+|    2 |      2.3 | KDOC_EXTRA_PROPERTY                     | Check: warns if there is property in KDoc which doesn't exist in class                                                                                                             | no  | - | - |
 |    2 |      2.3 | KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT            | Check: warns if there is comment before property in constructor                                                                                                             | yes | -|
 |    2 |      2.5 | KDOC_TRIVIAL_KDOC_ON_FUNCTION           | Check: warns if KDoc contains single line with words 'return', 'get' or 'set'                                                                                                |  no |  - | 
 |    2 |      2.4 | HEADER_WRONG_FORMAT                     | Checks: warns if there is no newline after header KDoc<br>Fix: adds newline                                                                                                  | yes |  |Check if header is on the very top of file. It's hard to determine when it's not.|


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocCommentsWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocCommentsWarnTest.kt
index 106ff33cf1..33f3376a88 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocCommentsWarnTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocCommentsWarnTest.kt
@@ -1,5 +1,6 @@
 package org.cqfn.diktat.ruleset.chapter2
 
+import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_EXTRA_PROPERTY
 import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_CONSTRUCTOR_PROPERTY
 import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT
 import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_CLASS_ELEMENTS
@@ -408,19 +409,21 @@ class KdocCommentsWarnTest : LintTestBase(::KdocComments) {
     }
 
     @Test
-    @Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY)
+    @Tags(Tag(WarningNames.KDOC_NO_CONSTRUCTOR_PROPERTY), Tag(WarningNames.KDOC_EXTRA_PROPERTY))
     fun `no property kdoc`() {
         lintMethod(
             """
                     |/**
                     | * @property Name text
+                    | * @property
                     | */
                     |class Example (
                     |   val name: String, 
                     |   ) {
                     |}
                 """.trimMargin(),
-            LintError(5, 4, ruleId, "${KDOC_NO_CONSTRUCTOR_PROPERTY.warnText()} add <name> to KDoc", true)
+            LintError(2, 4, ruleId, "${KDOC_EXTRA_PROPERTY.warnText()} @property Name text", false),
+            LintError(6, 4, ruleId, "${KDOC_NO_CONSTRUCTOR_PROPERTY.warnText()} add <name> to KDoc", true)
         )
     }
 
@@ -439,4 +442,23 @@ class KdocCommentsWarnTest : LintTestBase(::KdocComments) {
             LintError(2, 4, ruleId, "${KDOC_NO_CONSTRUCTOR_PROPERTY.warnText()} add <name> to KDoc", true)
         )
     }
+
+    @Test
+    @Tag(WarningNames.KDOC_EXTRA_PROPERTY)
+    fun `extra property in kdoc`() {
+        lintMethod(
+            """
+                    |/**
+                    | * @property name bla
+                    | * @property kek
+                    | */
+                    |class Example (
+                    |   val name: String, 
+                    |   private val surname: String
+                    |   ) {
+                    |}
+                """.trimMargin(),
+            LintError(3, 4, ruleId, "${KDOC_EXTRA_PROPERTY.warnText()} @property kek", false)
+        )
+    }
 }