Repository: analysis-dev/diktat (kotlin)

Issue:
Create a rule to check that there are no extension functions in the same file with the class
please create a feature to check that noone creates extension function in the same file with the class

_Originally posted by @akuleshov7 in https://github.com/cqfn/diKTat/pull/722#discussion_r564606302_

Pull request:
### What's done:
  * Logic done
  * Added warn tests

This pull request closes #731 

## Actions checklist
* [X] Implemented Rule, added Warnings
* [X] Added tests on checks
* [ ] Added tests on fixers
* [X] Updated diktat-analysis.yml
* [X] Updated available-rules.md

Patch:
diff --git a/diktat-analysis.yml b/diktat-analysis.yml
index e073757d13..8f48b4030f 100644
--- a/diktat-analysis.yml
+++ b/diktat-analysis.yml
@@ -439,4 +439,7 @@
   enabled: true
 # Checks if class can be converted to inline class
 - name: INLINE_CLASS_CAN_BE_USED
+  enabled: true
+# If file contains class, then it can't contain extension functions for the same class
+- name: EXTENSION_FUNCTION_WITH_CLASS
   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 1053de6254..e8043f0aee 100644
--- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt
+++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt
@@ -121,6 +121,8 @@ public object WarningNames {
 
     public const val FILE_WILDCARD_IMPORTS: String = "FILE_WILDCARD_IMPORTS"
 
+    public const val UNUSED_IMPORT: String = "UNUSED_IMPORT"
+
     public const val NO_BRACES_IN_CONDITIONALS_AND_LOOPS: String =
             "NO_BRACES_IN_CONDITIONALS_AND_LOOPS"
 
@@ -203,8 +205,12 @@ public object WarningNames {
 
     public const val RUN_BLOCKING_INSIDE_ASYNC: String = "RUN_BLOCKING_INSIDE_ASYNC"
 
+    public const val TOO_MANY_LINES_IN_LAMBDA: String = "TOO_MANY_LINES_IN_LAMBDA"
+
     public const val CUSTOM_LABEL: String = "CUSTOM_LABEL"
 
+    public const val INVERSE_FUNCTION_PREFERRED: String = "INVERSE_FUNCTION_PREFERRED"
+
     public const val SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY: String =
             "SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY"
 
@@ -236,11 +242,7 @@ public object WarningNames {
 
     public const val OBJECT_IS_PREFERRED: String = "OBJECT_IS_PREFERRED"
 
-    public const val INVERSE_FUNCTION_PREFERRED: String = "INVERSE_FUNCTION_PREFERRED"
-
-    public const val TOO_MANY_LINES_IN_LAMBDA: String = "TOO_MANY_LINES_IN_LAMBDA"
-
-    public const val UNUSED_IMPORT: String = "UNUSED_IMPORT"
-
     public const val INLINE_CLASS_CAN_BE_USED: String = "INLINE_CLASS_CAN_BE_USED"
+
+    public const val EXTENSION_FUNCTION_WITH_CLASS: String = "EXTENSION_FUNCTION_WITH_CLASS"
 }
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 30aab25f7b..57d5e9cd55 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
@@ -136,6 +136,7 @@ enum class Warnings(
     RUN_BLOCKING_INSIDE_ASYNC(false, "5.2.4", "avoid using runBlocking in asynchronous code"),
     TOO_MANY_LINES_IN_LAMBDA(false, "5.2.5", "long lambdas should have a parameter name instead of it"),
     CUSTOM_LABEL(false, "5.2.6", "avoid using expression with custom label"),
+    INVERSE_FUNCTION_PREFERRED(true, "5.1.4", "it is better to use inverse function"),
 
     // ======== chapter 6 ========
     SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY(true, "6.1.1", "if a class has single constructor, it should be converted to a primary constructor"),
@@ -152,8 +153,8 @@ enum class Warnings(
     NO_CORRESPONDING_PROPERTY(false, "6.1.7", "backing property should have the same name, but there is no corresponding property"),
     AVOID_USING_UTILITY_CLASS(false, "6.4.1", "avoid using utility classes/objects, use extensions functions"),
     OBJECT_IS_PREFERRED(true, "6.4.2", "it is better to use object for stateless classes"),
-    INVERSE_FUNCTION_PREFERRED(true, "5.1.4", "it is better to use inverse function"),
     INLINE_CLASS_CAN_BE_USED(true, "6.1.12", "inline class can be used"),
+    EXTENSION_FUNCTION_WITH_CLASS(false, "6.2.3", "do not use extension functions for the class defined in the same file"),
     ;
 
     /**
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 9cf80aa44e..d5e5c0245e 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
@@ -55,6 +55,7 @@ import org.cqfn.diktat.ruleset.rules.chapter5.OverloadingArgumentsFunction
 import org.cqfn.diktat.ruleset.rules.chapter6.AvoidEmptyPrimaryConstructor
 import org.cqfn.diktat.ruleset.rules.chapter6.AvoidUtilityClass
 import org.cqfn.diktat.ruleset.rules.chapter6.CustomGetterSetterRule
+import org.cqfn.diktat.ruleset.rules.chapter6.ExtensionFunctionsInFileRule
 import org.cqfn.diktat.ruleset.rules.chapter6.ExtensionFunctionsSameNameRule
 import org.cqfn.diktat.ruleset.rules.chapter6.ImplicitBackingPropertyRule
 import org.cqfn.diktat.ruleset.rules.chapter6.PropertyAccessorFields
@@ -155,6 +156,7 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS
             ::CompactInitialization,
             // other rules
             ::InlineClassesRule,
+            ::ExtensionFunctionsInFileRule,
             ::CheckInverseMethodRule,
             ::StatelessClassesRule,
             ::ImplicitBackingPropertyRule,
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt
new file mode 100644
index 0000000000..e9a025ee13
--- /dev/null
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt
@@ -0,0 +1,60 @@
+package org.cqfn.diktat.ruleset.rules.chapter6
+
+import org.cqfn.diktat.common.config.rules.RulesConfig
+import org.cqfn.diktat.ruleset.constants.EmitType
+import org.cqfn.diktat.ruleset.constants.Warnings
+import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType
+import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType
+import org.cqfn.diktat.ruleset.utils.getFirstChildWithType
+
+import com.pinterest.ktlint.core.Rule
+import com.pinterest.ktlint.core.ast.ElementType
+import com.pinterest.ktlint.core.ast.ElementType.CLASS
+import com.pinterest.ktlint.core.ast.ElementType.FUN
+import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER
+import com.pinterest.ktlint.core.ast.ElementType.TYPE_REFERENCE
+import com.pinterest.ktlint.core.ast.prevSibling
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.psi.KtClass
+import org.jetbrains.kotlin.psi.KtFunction
+
+/**
+ * This rule checks if there are any extension functions for the class in the same file, where it is defined
+ */
+class ExtensionFunctionsInFileRule(private val configRules: List<RulesConfig>) : Rule("extension-functions-class-file") {
+    private var isFixMode: Boolean = false
+    private lateinit var emitWarn: EmitType
+
+    override fun visit(node: ASTNode,
+                       autoCorrect: Boolean,
+                       emit: EmitType) {
+        emitWarn = emit
+        isFixMode = autoCorrect
+
+        if (node.elementType == ElementType.FILE) {
+            val classNames = collectAllClassNames(node)
+
+            collectAllExtensionFunctionsWithSameClassName(node, classNames).forEach {
+                fireWarning(it)
+            }
+        }
+    }
+
+    @Suppress("UnsafeCallOnNullableType")
+    private fun collectAllClassNames(node: ASTNode): List<String> {
+        val classes = node.findAllNodesWithSpecificType(CLASS)
+
+        return classes.map { (it.psi as KtClass).name!! }
+    }
+
+    private fun fireWarning(node: ASTNode) {
+        Warnings.EXTENSION_FUNCTION_WITH_CLASS.warn(configRules, emitWarn, isFixMode, "fun ${(node.psi as KtFunction).name}", node.startOffset, node)
+    }
+
+    private fun collectAllExtensionFunctionsWithSameClassName(node: ASTNode, classNames: List<String>): List<ASTNode> =
+            node.getAllChildrenWithType(FUN).filter { isExtensionFunctionWithClassName(it, classNames) }
+
+    @Suppress("UnsafeCallOnNullableType")
+    private fun isExtensionFunctionWithClassName(node: ASTNode, classNames: List<String>): Boolean =
+            node.getFirstChildWithType(IDENTIFIER)!!.prevSibling { it.elementType == TYPE_REFERENCE }?.text in classNames
+}
diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml
index afd9e2d004..f9ad192742 100644
--- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml
+++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml
@@ -439,4 +439,7 @@
   enabled: true
 # Checks if class can be converted to inline class
 - name: INLINE_CLASS_CAN_BE_USED
+  enabled: true
+# If file contains class, then it can't contain extension functions for the same class
+- name: EXTENSION_FUNCTION_WITH_CLASS
   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 ae89b01e41..d88c10efde 100644
--- a/diktat-rules/src/main/resources/diktat-analysis.yml
+++ b/diktat-rules/src/main/resources/diktat-analysis.yml
@@ -436,4 +436,7 @@
   enabled: true
 # Checks if class can be converted to inline class
 - name: INLINE_CLASS_CAN_BE_USED
+  enabled: true
+# If file contains class, then it can't contain extension functions for the same class
+- name: EXTENSION_FUNCTION_WITH_CLASS
   enabled: true
\ No newline at end of file
diff --git a/info/available-rules.md b/info/available-rules.md
index 3d39d6fdb4..685354dd3b 100644
--- a/info/available-rules.md
+++ b/info/available-rules.md
@@ -114,4 +114,5 @@
 |    6  |    6.1.12  |  INLINE_CLASS_CAN_BE_USED                |  Check: warns if class can be transferred to the inline class.                                                                                                                 | no | no | yes |
 |    6  |     6.2.2  |  EXTENSION_FUNCTION_SAME_SIGNATURE       |  Checks if extension function has the same signature as another extension function and their classes are related                                                               |  no  |  no  |  + |
 |    6  |     6.4.1  |  AVOID_USING_UTILITY_CLASS               | Checks if there is class/object that can be replace with extension function                                                                                                  | no  | no | - |
-|    6  |     6.4.2  |  OBJECT_IS_PREFERRED                     | Checks: if class is stateless it is preferred to use `object`                                                                                                                | yes | no | + |
\ No newline at end of file
+|    6  |     6.4.2  |  OBJECT_IS_PREFERRED                     | Checks: if class is stateless it is preferred to use `object`                                                                                                                | yes | no | + |
+|    6  |     6.2.3  | EXTENSION_FUNCTION_WITH_CLASS            | Check: if file contains class, then it can not have extension functions for the same class                                                                                                     | no | no | - |
\ No newline at end of file
diff --git a/info/guide/guide-TOC.md b/info/guide/guide-TOC.md
index b63fecf1c5..4e4a4586b7 100644
--- a/info/guide/guide-TOC.md
+++ b/info/guide/guide-TOC.md
@@ -104,11 +104,12 @@ I [Preface](#c0)
     * [6.1.8 Avoid using custom getters and setters](#r6.1.8) 
     * [6.1.9 Never use the name of a variable in the custom getter or setter (possible_bug)](#r6.1.9) 
     * [6.1.10 No trivial getters and setters are allowed in the code](#r6.1.10)
-    * [6.1.11 Use 'apply' for grouping object initialization](#r6.1.11) 
+    * [6.1.11 Use 'apply' for grouping object initialization](#r6.1.11)
     * [6.1.12 Prefer Inline classes when a class has a single property](#r6.1.12)
-* [6.2 Classes](#c6.2)   
+* [6.2 Extension functions](#c6.2)   
     * [6.2.1 Use extension functions for making logic of classes less coupled](#r6.2.1)
-    * [6.2.2 No extension functions with the same name and signature if they extend base and inheritor classes (possible_bug)](#r6.2.2) 
+    * [6.2.2 No extension functions with the same name and signature if they extend base and inheritor classes (possible_bug)](#r6.2.2)
+    * [6.2.3 Don't use extension functions for the class in the same file](#r6.2.3)
 * [6.3 Interfaces](#c6.3)            
 * [6.4 Objects](#c6.4)      
     * [6.4.1 Instead of using utility classes/objects, use extensions](#r6.4.1)
diff --git a/info/guide/guide-chapter-6.md b/info/guide/guide-chapter-6.md
index db0969b4dd..aaa6d0a36e 100644
--- a/info/guide/guide-chapter-6.md
+++ b/info/guide/guide-chapter-6.md
@@ -406,6 +406,20 @@ fun printClassName(s: A) { println(s.foo()) }
 fun main() { printClassName(B()) }
 ```
 
+#### <a name="r6.2.3"></a> 6.2.3 Don't use extension functions for the class in the same file
+You should not use extension functions for the class in the same file, where it is defined.
+
+**Invalid example**:
+```kotlin
+class SomeClass {
+    
+}
+
+fun SomeClass.deleteAllSpaces() {
+    
+}
+```
+
 <!-- =============================================================================== -->
 ### <a name="c6.3"></a> 6.3 Interfaces
 An `Interface` in Kotlin can contain declarations of abstract methods, as well as method implementations. What makes them different from abstract classes is that interfaces cannot store state.


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsInFileWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsInFileWarnTest.kt
new file mode 100644
index 0000000000..4bcda14567
--- /dev/null
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsInFileWarnTest.kt
@@ -0,0 +1,133 @@
+package org.cqfn.diktat.ruleset.chapter6
+
+import org.cqfn.diktat.ruleset.constants.Warnings
+import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID
+import org.cqfn.diktat.ruleset.rules.chapter6.ExtensionFunctionsInFileRule
+import org.cqfn.diktat.util.LintTestBase
+
+import com.pinterest.ktlint.core.LintError
+import generated.WarningNames.EXTENSION_FUNCTION_WITH_CLASS
+import org.junit.jupiter.api.Tag
+import org.junit.jupiter.api.Test
+
+class ExtensionFunctionsInFileWarnTest : LintTestBase(::ExtensionFunctionsInFileRule) {
+    private val ruleId = "$DIKTAT_RULE_SET_ID:extension-functions-class-file"
+
+    @Test
+    @Tag(EXTENSION_FUNCTION_WITH_CLASS)
+    fun `should warn on function`() {
+        lintMethod(
+            """
+                |class Some private constructor () {
+                |
+                |}
+                |
+                |private fun Some.coolStr() {
+                |
+                |}
+            """.trimMargin(),
+            LintError(5, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_WITH_CLASS.warnText()} fun coolStr")
+        )
+    }
+
+    @Test
+    @Tag(EXTENSION_FUNCTION_WITH_CLASS)
+    fun `should warn on several functions`() {
+        lintMethod(
+            """
+                |class Another private constructor () {
+                |
+                |}
+                |
+                |private fun /* Random comment */ Another.coolStr() {
+                |
+                |}
+                |
+                |private fun Another.extMethod() {
+                |
+                |}
+            """.trimMargin(),
+            LintError(5, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_WITH_CLASS.warnText()} fun coolStr"),
+            LintError(9, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_WITH_CLASS.warnText()} fun extMethod"),
+        )
+    }
+
+    @Test
+    @Tag(EXTENSION_FUNCTION_WITH_CLASS)
+    fun `should not raise a warning when there is no class`() {
+        lintMethod(
+            """
+                |private fun String.coolStr() {
+                |
+                |}
+                |
+                |private fun /* Random comment */ Another.extMethod() {
+                |
+                |}
+            """.trimMargin()
+        )
+    }
+
+    @Test
+    @Tag(EXTENSION_FUNCTION_WITH_CLASS)
+    fun `should not raise a warning when there is no extension functions`() {
+        lintMethod(
+            """
+                |class Some {
+                |
+                |}
+            """.trimMargin()
+        )
+    }
+
+    @Test
+    @Tag(EXTENSION_FUNCTION_WITH_CLASS)
+    fun `should not raise a warning when extension function is in the class`() {
+        lintMethod(
+            """
+                |class Some {
+                |   
+                |   fun Some.str() {
+                |   
+                |   }
+                |}
+            """.trimMargin()
+        )
+    }
+
+    @Test
+    @Tag(EXTENSION_FUNCTION_WITH_CLASS)
+    fun `should not trigger on regular functions in the same file with class`() {
+        lintMethod(
+            """
+                |class Some {
+                |   fun foo() {
+                |   
+                |   }
+                |}
+                |
+                |fun bar() {
+                |
+                |}
+            """.trimMargin()
+        )
+    }
+
+    @Test
+    @Tag(EXTENSION_FUNCTION_WITH_CLASS)
+    fun `should not trigger on extension functions with different class`() {
+        lintMethod(
+            """
+                |class Some {
+                |   fun foo() {
+                |   
+                |   }
+                |}
+                |
+                |fun String.bar() {
+                |
+                |}
+            """.trimMargin()
+        )
+    }
+}