Repository: analysis-dev/diktat (kotlin)

Issue:
Unused_import rule should ignore all standard operator funs
`getValue`, `plus` etc.

## Environment information
* diktat version: 0.5.0

Pull request description:
### What's done:
* Changed logic
* Added operator funs with multiple corresponding operators
* Added test

This pull request closes #837

Patch:
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt
index 00ab8e5310..1ef2ae388c 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt
@@ -16,6 +16,7 @@ import org.cqfn.diktat.ruleset.utils.StandardPlatforms
 import org.cqfn.diktat.ruleset.utils.copyrightWords
 import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType
 import org.cqfn.diktat.ruleset.utils.handleIncorrectOrder
+import org.cqfn.diktat.ruleset.utils.ignoreImports
 import org.cqfn.diktat.ruleset.utils.moveChildBefore
 import org.cqfn.diktat.ruleset.utils.operatorMap
 
@@ -73,7 +74,12 @@ class FileStructureRule(configRules: List<RulesConfig>) : DiktatRule(
         }
     private val refSet: MutableSet<String> = mutableSetOf()
     private var packageName = ""
-    private val ignoreImports = setOf("invoke", "get", "set", "getValue")
+    
+    /**
+     * There are groups of methods, which should be excluded from usage check without type resolution.
+     * `componentN` is a method for N-th component in destructuring declarations.
+     */
+    private val ignoreImportsPatterns = setOf("component\\d+".toRegex())
 
     override fun logic(node: ASTNode) {
         if (node.elementType == FILE) {
@@ -231,10 +237,13 @@ class FileStructureRule(configRules: List<RulesConfig>) : DiktatRule(
                 ) {
                     // this branch corresponds to imports from the same package
                     deleteImport(importPath, node, ktImportDirective)
-                } else if (importName != null && !ignoreImports.contains(importName) && !refSet.contains(importName) && !operatorMap.containsKey(importName)) {
-                    // this import is not used anywhere
-                    // Fixme: operatorMap imports and `getValue` should be deleted if unused
-                    deleteImport(importPath, node, ktImportDirective)
+                } else if (importName != null && !refSet.contains(importName)) {
+                    // Fixme: operatorMap imports and `getValue` should be deleted if unused, but we can't detect for sure
+                    val shouldImportBeIgnored = ignoreImports.contains(importName) || ignoreImportsPatterns.any { it.matches(importName) }
+                    if (!shouldImportBeIgnored) {
+                        // this import is not used anywhere
+                        deleteImport(importPath, node, ktImportDirective)
+                    }
                 }
             }
     }
@@ -254,7 +263,7 @@ class FileStructureRule(configRules: List<RulesConfig>) : DiktatRule(
     private fun findAllReferences(node: ASTNode) {
         node.findAllDescendantsWithSpecificType(OPERATION_REFERENCE).forEach { ref ->
             if (!ref.isPartOf(IMPORT_DIRECTIVE)) {
-                val references = operatorMap.filterValues { it == ref.text }
+                val references = operatorMap.filterValues { ref.text in it }
                 if (references.isNotEmpty()) {
                     references.keys.forEach { key -> refSet.add(key) }
                 } else {
diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstConstants.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstConstants.kt
index 571fd396b7..f6637ac519 100644
--- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstConstants.kt
+++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstConstants.kt
@@ -40,9 +40,16 @@ internal val operatorMap = mapOf(
     "unaryPlus" to "+", "unaryMinus" to "-", "not" to "!",
     "plus" to "+", "minus" to "-", "times" to "*", "div" to "/", "rem" to "%", "mod" to "%", "rangeTo" to "..",
     "inc" to "++", "dec" to "--", "contains" to "in",
-    "plusAssign" to "+=", "minusAssign" to "-=", "timesAssign" to "*=", "divAssign" to "/=", "modAssign" to "%="
+    "plusAssign" to "+=", "minusAssign" to "-=", "timesAssign" to "*=", "divAssign" to "/=", "modAssign" to "%=",
+).mapValues { (_, value) ->
+    listOf(value)
+} + mapOf(
+    "equals" to listOf("==", "!="),
+    "compareTo" to listOf("<", "<=", ">", ">="),
 )
 
+internal val ignoreImports = setOf("invoke", "get", "set", "getValue", "setValue", "provideDelegate")
+
 /**
  * Enum that represents some standard platforms that can appear in kotlin code
  * @property packages beginnings of fully qualified names of packages belonging to a particular platform


Test patch:
diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/FileStructureRuleTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/FileStructureRuleTest.kt
index c7bf303eef..26a2e7fe25 100644
--- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/FileStructureRuleTest.kt
+++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/FileStructureRuleTest.kt
@@ -422,13 +422,18 @@ class FileStructureRuleTest : LintTestBase(::FileStructureRule) {
             """
                 |package org.cqfn.diktat.example
                 |
+                |import com.example.equals
                 |import com.example.get
                 |import com.example.invoke
                 |import com.example.set
+                |import org.gradle.kotlin.dsl.provideDelegate
                 |import tasks.getValue
+                |import tasks.setValue
                 |
                 |fun main() {
-                |   val a by tasks.getting
+                |   val a by tasks.getting  // `getValue` is used
+                |   val b by project.tasks  // `provideDelegate` is used
+                |   a != 0
                 |}
             """.trimMargin(),
         )
@@ -442,15 +447,17 @@ class FileStructureRuleTest : LintTestBase(::FileStructureRule) {
                 |package org.cqfn.diktat.example
                 |
                 |import Delegate
+                |import com.example.equals
                 |import com.example.get
                 |import com.example.invoke
                 |import com.example.set
                 |
                 |fun main() {
-                |   val a
+                |   val a: Foo
                 |}
             """.trimMargin(),
-            LintError(1, 1, ruleId, "${Warnings.UNUSED_IMPORT.warnText()} Delegate - unused import", true)
+            LintError(1, 1, ruleId, "${Warnings.UNUSED_IMPORT.warnText()} Delegate - unused import", true),
+            LintError(1, 1, ruleId, "${Warnings.UNUSED_IMPORT.warnText()} com.example.equals - unused import", true)
         )
     }