Repository: analysis-dev/diktat (kotlin)

Issue:
Rule 6.1.3: Do not use the primary constructor if it is empty and has no sense
The primary constructor is part of the class header: it goes after the class name (and optional type parameters).
But in is useless - it can be omitted. 

Bad example:
```kotlin
// simple case that does not need a primary constructor
class Test() {
    var a: Int = 0
    var b: Int = 0
}

// empty primary constructor is not needed here also
// it can be replaced with a primary contructor with one argument or removed
class Test() {
    var a  = "Property"

    init {
        println("some init")
    }

    constructor(a: String): this() {
        this.a = a
    }
}
```

Good example:
```kotlin
// the good example here is a data class, but this example shows that you should get rid of braces for primary constructor
class Test {
    var a: Int = 0
    var b: Int = 0
}
```

Pull request description:
## Actions checklist
* [x] Implemented Rule, added Warnings
* [x] Added tests on checks
* [x] Added tests on fixers
* [x] Updated diktat-analysis.yml
* [x] Updated available-rules.md
* [x] closes #439
## Fixme
yes

Patch:
diff --git a/diktat-analysis.yml b/diktat-analysis.yml
index 4fdbdf87cf..df66840744 100644
--- a/diktat-analysis.yml
+++ b/diktat-analysis.yml
@@ -297,4 +297,7 @@
   enabled: true
 # Checks if extension function with the same signature don't have related classes
 - name: EXTENSION_FUNCTION_SAME_SIGNATURE
+  enabled: true
+# Checks if there is empty primary constructor
+- name: EMPTY_PRIMARY_CONSTRUCTOR
   enabled: true
\ No newline at end of file
diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt
index 4cce40c1bb..387e6f33a4 100644
--- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt
+++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt
@@ -219,4 +219,6 @@ public object WarningNames {
             "TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED"
 
     public const val EXTENSION_FUNCTION_SAME_SIGNATURE: String = "EXTENSION_FUNCTION_SAME_SIGNATURE"
+
+    public const val EMPTY_PRIMARY_CONSTRUCTOR: String = "EMPTY_PRIMARY_CONSTRUCTOR"
 }
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 29eb0cf743..6bf1d364fe 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
@@ -130,6 +130,7 @@ enum class Warnings(val canBeAutoCorrected: Boolean, val ruleId: String, private
     USELESS_SUPERTYPE(true, "6.1.5", "unnecessary supertype specification"),
     TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED(true, "6.1.10", "trivial property accessors are not recommended"),
     EXTENSION_FUNCTION_SAME_SIGNATURE(false, "6.2.2", "extension functions should not have same signature if their receiver classes are related"),
+    EMPTY_PRIMARY_CONSTRUCTOR(true,"6.1.3", "avoid empty primary constructor"),
     ;
 
     /**
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AvoidEmptyPrimaryConstructor.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AvoidEmptyPrimaryConstructor.kt
new file mode 100644
index 0000000000..00694a13be
--- /dev/null
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AvoidEmptyPrimaryConstructor.kt
@@ -0,0 +1,35 @@
+package org.cqfn.diktat.ruleset.rules
+
+import com.pinterest.ktlint.core.Rule
+import com.pinterest.ktlint.core.ast.ElementType.CLASS
+import org.cqfn.diktat.common.config.rules.RulesConfig
+import org.cqfn.diktat.ruleset.constants.Warnings.EMPTY_PRIMARY_CONSTRUCTOR
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.psi.KtClass
+
+class AvoidEmptyPrimaryConstructor(private val configRules: List<RulesConfig>) : Rule("avoid-empty-primary-constructor") {
+
+
+    private var isFixMode: Boolean = false
+    private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit)
+
+    override fun visit(node: ASTNode,
+                       autoCorrect: Boolean,
+                       emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+        emitWarn = emit
+        isFixMode = autoCorrect
+
+        if (node.elementType == CLASS)
+            checkCLass(node.psi as KtClass)
+    }
+
+    @Suppress("UnsafeCallOnNullableType")
+    private fun checkCLass(ktClass: KtClass) {
+        if(ktClass.primaryConstructor?.valueParameters?.isNotEmpty() != false || ktClass.primaryConstructorModifierList != null)
+            return
+        EMPTY_PRIMARY_CONSTRUCTOR.warnAndFix(configRules, emitWarn, isFixMode, ktClass.nameIdentifier!!.text,
+                ktClass.node.startOffset, ktClass.node) {
+            ktClass.node.removeChild(ktClass.primaryConstructor!!.node)
+        }
+    }
+}
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt
index 14fae1bc4d..64d98bf7e9 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt
@@ -71,6 +71,7 @@ class DiktatRuleSetProvider(private val diktatConfigFile: String = "diktat-analy
                 ::BracesInConditionalsAndLoopsRule,
                 ::BlockStructureBraces,
                 ::EmptyBlock,
+                ::AvoidEmptyPrimaryConstructor,
                 ::EnumsSeparated,
                 ::SingleLineStatementsRule,
                 ::MultipleModifiersSequence,
diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml
index 6a09997c0d..9be9160d39 100644
--- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml
+++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml
@@ -296,3 +296,6 @@
 # Checks if extension function with the same signature don't have related classes
 - name: EXTENSION_FUNCTION_SAME_SIGNATURE
   enabled: true
+# Checks if there is empty primary constructor
+- name: EMPTY_PRIMARY_CONSTRUCTOR
+  enabled: true
\ No newline at end of file
diff --git a/diktat-rules/src/main/resources/diktat-analysis.yml b/diktat-rules/src/main/resources/diktat-analysis.yml
index 17d166c0b1..9b5b082ba9 100644
--- a/diktat-rules/src/main/resources/diktat-analysis.yml
+++ b/diktat-rules/src/main/resources/diktat-analysis.yml
@@ -298,3 +298,6 @@
 # Checks if extension function with the same signature don't have related classes
 - name: EXTENSION_FUNCTION_SAME_SIGNATURE
   enabled: true
+# Checks if there is empty primary constructor
+- name: EMPTY_PRIMARY_CONSTRUCTOR
+  enabled: true
\ No newline at end of file
diff --git a/info/available-rules.md b/info/available-rules.md
index 02aeda2454..e1e15fdf8d 100644
--- a/info/available-rules.md
+++ b/info/available-rules.md
@@ -95,6 +95,7 @@
 |    5 |     5.2.3 | WRONG_OVERLOADING_FUNCTION_ARGUMENTS   | Check: function has overloading instead use default arguments                                                                                                                |  no |  -|
 |    6 |     6.1.1 | SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY   | Check: warns if there is only one secondary constructor in a class<br>Fix: converts it to a primary constructor | yes | no |  Support more complicated logic of constructor conversion |
 |    6 |     6.1.2 | USE_DATA_CLASS                         | Check: if class can be made as data class                                                                                                                                    | no | - | yes |
+|    6 |     6.1.3 | EMPTY_PRIMARY_CONSTRUCTOR              | Check: if there is empty primary constructor                                                                                                                                 | yes| - | yes |
 |    6 |     6.1.4 | MULTIPLE_INIT_BLOCKS                   | Checks that classes have only one init block | yes | no | - |
 |    6 |     6.1.5 | USELESS_SUPERTYPE                       | Check: if override function can be removed                                                                                                                                   | yes| - |     |
 |    6 |     6.1.6 | CLASS_SHOULD_NOT_BE_ABSTRACT           | Checks: if abstract class has any abstract method. If not, warns that class should not be abstract<br>Fix: deletes abstract modifier                                         | yes | - | - |


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorFixTest.kt
new file mode 100644
index 0000000000..2498d08324
--- /dev/null
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorFixTest.kt
@@ -0,0 +1,16 @@
+package org.cqfn.diktat.ruleset.chapter6
+
+import generated.WarningNames
+import org.cqfn.diktat.ruleset.rules.AvoidEmptyPrimaryConstructor
+import org.cqfn.diktat.util.FixTestBase
+import org.junit.jupiter.api.Tag
+import org.junit.jupiter.api.Test
+
+class EmptyPrimaryConstructorFixTest: FixTestBase("test/chapter6/primary_constructor", ::AvoidEmptyPrimaryConstructor) {
+
+    @Test
+    @Tag(WarningNames.EMPTY_PRIMARY_CONSTRUCTOR)
+    fun `should remove empty primary constructor`() {
+        fixAndCompare("EmptyPCExpected.kt", "EmptyPCTest.kt")
+    }
+}
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorWarnTest.kt
new file mode 100644
index 0000000000..ec2e7d244c
--- /dev/null
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorWarnTest.kt
@@ -0,0 +1,62 @@
+package org.cqfn.diktat.ruleset.chapter6
+
+import com.pinterest.ktlint.core.LintError
+import generated.WarningNames
+import org.cqfn.diktat.ruleset.constants.Warnings.EMPTY_PRIMARY_CONSTRUCTOR
+import org.cqfn.diktat.ruleset.rules.AvoidEmptyPrimaryConstructor
+import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID
+import org.cqfn.diktat.util.LintTestBase
+import org.junit.jupiter.api.Tag
+import org.junit.jupiter.api.Test
+
+class EmptyPrimaryConstructorWarnTest: LintTestBase(::AvoidEmptyPrimaryConstructor) {
+    private val ruleId = "$DIKTAT_RULE_SET_ID:avoid-empty-primary-constructor"
+
+    @Test
+    @Tag(WarningNames.EMPTY_PRIMARY_CONSTRUCTOR)
+    fun `simple classes with empty primary constructor`() {
+        lintMethod(
+                """
+                    |class Some() {
+                    |   val a = 10
+                    |   constructor(a: String): this() {
+                    |       this.a = a
+                    |   }
+                    |}
+                    |
+                    |class Some1() {
+                    |   val a = 10
+                    |   companion object {}
+                    |}
+                    |
+                    |class Some2 {
+                    |   val a = 10
+                    |   constructor(a: String): this() {
+                    |       this.a = a
+                    |   }
+                    |}
+                    |
+                    |class Some3 private constructor () {
+                    |
+                    |}
+                """.trimMargin(),
+                LintError(1,1,ruleId, "${EMPTY_PRIMARY_CONSTRUCTOR.warnText()} Some", true),
+                LintError(8,1,ruleId, "${EMPTY_PRIMARY_CONSTRUCTOR.warnText()} Some1", true)
+        )
+    }
+
+    @Test
+    @Tag(WarningNames.EMPTY_PRIMARY_CONSTRUCTOR)
+    fun `correct example with empty primary constructor and modifiers`() {
+        lintMethod(
+                """
+                    |class Some1 private constructor () {
+                    |
+                    |}
+                    |
+                    |class Some2 @Inject constructor() {
+                    |}
+                """.trimMargin()
+        )
+    }
+}
diff --git a/diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCExpected.kt b/diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCExpected.kt
new file mode 100644
index 0000000000..750cd85cc3
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCExpected.kt
@@ -0,0 +1,18 @@
+package test.chapter6.primary_constructor
+
+class Test {
+    var a: Int = 0
+    var b: Int = 0
+}
+
+class Test {
+    var a  = "Property"
+
+    init {
+        println("some init")
+    }
+
+    constructor(a: String): this() {
+        this.a = a
+    }
+}
diff --git a/diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCTest.kt b/diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCTest.kt
new file mode 100644
index 0000000000..04369eab8b
--- /dev/null
+++ b/diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCTest.kt
@@ -0,0 +1,18 @@
+package test.chapter6.primary_constructor
+
+class Test() {
+    var a: Int = 0
+    var b: Int = 0
+}
+
+class Test() {
+    var a  = "Property"
+
+    init {
+        println("some init")
+    }
+
+    constructor(a: String): this() {
+        this.a = a
+    }
+}