From 0af4041a04d384dc750b5ba4ea62fc0caa215e45 Mon Sep 17 00:00:00 2001 From: soWhoAmI Date: Fri, 6 Nov 2020 22:40:40 +0300 Subject: [PATCH 1/7] feature/rule-6.2.2(#447) ### What's done: * Added rule logic * Added warn tests --- diktat-analysis.yml | 4 + .../src/main/kotlin/generated/WarningNames.kt | 2 + .../cqfn/diktat/ruleset/constants/Warnings.kt | 1 + .../ruleset/rules/DiktatRuleSetProvider.kt | 1 + .../rules/ExtensionFunctionsSameNameRule.kt | 136 ++++++++++++++++++ .../main/resources/diktat-analysis-huawei.yml | 4 + .../src/main/resources/diktat-analysis.yml | 4 + .../ExtensionFunctionsSameNameWarnTest.kt | 67 +++++++++ info/available-rules.md | 3 +- 9 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt create mode 100644 diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt diff --git a/diktat-analysis.yml b/diktat-analysis.yml index 5ab0f0885a..8a74ac0cb4 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -344,5 +344,9 @@ configuration: {} # Checks that there are abstract functions in abstract class - name: CLASS_SHOULD_NOT_BE_ABSTRACT + enabled: true + configuration: {} +# Checks if extension function with the same signature don't have related classes +- name: EXTENSION_FUNCTION_SAME_SIGNATURE enabled: true configuration: {} \ 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 159fe3eca0..7b370448a3 100644 --- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt +++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt @@ -203,4 +203,6 @@ public object WarningNames { public const val MULTIPLE_INIT_BLOCKS: String = "MULTIPLE_INIT_BLOCKS" public const val CLASS_SHOULD_NOT_BE_ABSTRACT: String = "CLASS_SHOULD_NOT_BE_ABSTRACT" + + public const val EXTENSION_FUNCTION_SAME_SIGNATURE: String = "EXTENSION_FUNCTION_SAME_SIGNATURE" } 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 ef17b1f977..09d690cc25 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 @@ -124,6 +124,7 @@ enum class Warnings(private val canBeAutoCorrected: Boolean, private val warn: S WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR(false, "Use `field` keyword instead of property name inside property accessors"), MULTIPLE_INIT_BLOCKS(true, "Avoid using multiple `init` blocks, this logic can be moved to constructors or properties declarations"), CLASS_SHOULD_NOT_BE_ABSTRACT(true, "class should not be abstract, because it has no abstract functions"), + EXTENSION_FUNCTION_SAME_SIGNATURE(false, "extension functions should not have same signature if their classes are related") ; /** 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 bae979dcec..c2bdcb2372 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 @@ -98,6 +98,7 @@ class DiktatRuleSetProvider(private val diktatConfigFile: String = "diktat-analy ::NullableTypeRule, ::ImmutableValNoVarRule, ::AvoidNestedFunctionsRule, + ::ExtensionFunctionsSameNameRule, // formatting: moving blocks, adding line breaks, indentations etc. ::ConsecutiveSpacesRule, ::WhiteSpaceRule, // this rule should be after other rules that can cause wrong spacing diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt new file mode 100644 index 0000000000..c13d8db63c --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt @@ -0,0 +1,136 @@ +package org.cqfn.diktat.ruleset.rules + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.CLASS +import com.pinterest.ktlint.core.ast.ElementType.COLON +import com.pinterest.ktlint.core.ast.ElementType.DOT +import com.pinterest.ktlint.core.ast.ElementType.FILE +import com.pinterest.ktlint.core.ast.ElementType.FUN +import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER +import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_CALL_ENTRY +import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_LIST +import com.pinterest.ktlint.core.ast.ElementType.TYPE_REFERENCE +import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST +import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER_LIST +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE +import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findChildAfter +import org.cqfn.diktat.ruleset.utils.findChildBefore +import org.cqfn.diktat.ruleset.utils.findLeafWithSpecificType +import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType +import org.cqfn.diktat.ruleset.utils.getFirstChildWithType +import org.cqfn.diktat.ruleset.utils.hasChildOfType +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtNamedFunction + +typealias RelatedClasses = List> +typealias SimilarSignatures = List> + +/** + * This rule checks if extension functions with the same signature don't have related classes + */ +class ExtensionFunctionsSameNameRule(private val configRules: List) : Rule("extension-functions-same-name") { + private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) + private var isFixMode: Boolean = false + + override fun visit(node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { + emitWarn = emit + isFixMode = autoCorrect + + /** + * 1) Collect all classes that extend other classes + * 2) Collect all extension functions with same signature + * 3) Check if classes of functions are related + */ + if (node.elementType == FILE) { + val relatedClasses = collectAllRelatedClasses(node) + val extFunctionsWithSameName = collectAllExtensionFunctions(node) + handleFunctions(relatedClasses, extFunctionsWithSameName) + } + + } + + private fun collectAllRelatedClasses(node: ASTNode) : List> { + val classListWithInheritance = node.findAllNodesWithSpecificType(CLASS) + .filterNot { (it.psi as KtClass).isInterface() } + .filter { it.hasChildOfType(SUPER_TYPE_LIST) } + + val pairs = mutableListOf>() + classListWithInheritance.forEach { + val callEntries = it.findChildByType(SUPER_TYPE_LIST)!!.getAllChildrenWithType(SUPER_TYPE_CALL_ENTRY) + + callEntries.forEach { entry -> + if (entry.hasChildOfType(VALUE_ARGUMENT_LIST)) { + val className = (it.psi as KtClass).name!! + val entryName = entry.findLeafWithSpecificType(IDENTIFIER)!! + pairs.add(Pair(className, entryName.text)) + } + } + } + return pairs + } + + private fun collectAllExtensionFunctions(node: ASTNode) : SimilarSignatures { + val extensionFunctionList = node.findAllNodesWithSpecificType(FUN).filter { it.hasChildOfType(TYPE_REFERENCE) && it.hasChildOfType(DOT) } + val distinctFunctionSignatures = mutableMapOf() + val extensionFunctions = mutableListOf>() + + extensionFunctionList.forEach { + val functionName = (it.psi as KtNamedFunction).name!! + val params = it.getFirstChildWithType(VALUE_PARAMETER_LIST)!!.text + val returnType = it.findChildAfter(COLON, TYPE_REFERENCE)?.text + val className = it.findChildBefore(DOT, TYPE_REFERENCE)!!.text + val signature = FunctionSignature(functionName, params, returnType) + if (distinctFunctionSignatures.contains(signature)) { + val secondFuncClassName = distinctFunctionSignatures[signature]!!.findChildBefore(DOT, TYPE_REFERENCE)!!.text + extensionFunctions.add(Pair( + ExtensionFunction(secondFuncClassName, signature, distinctFunctionSignatures[signature]!!), + ExtensionFunction(className, signature, it))) + } else { + distinctFunctionSignatures[signature] = it + } + } + + return extensionFunctions + } + + private fun handleFunctions(relatedClasses: RelatedClasses, functions: SimilarSignatures) { + functions.forEach { + val firstClassName = it.first.className + val secondClassName = it.second.className + + if (relatedClasses.hasRelatedClasses(Pair(firstClassName, secondClassName))) { + raiseWarning(it.first.node, it.first, it.second) + raiseWarning(it.second.node, it.first, it.second) + } + } + } + + private fun RelatedClasses.hasRelatedClasses(pair: Pair): Boolean { + forEach { + if (it.first == pair.first && it.second == pair.second + || it.first == pair.second && it.second == pair.first) + return true + } + return false + } + + private fun raiseWarning(node: ASTNode, firstFunc: ExtensionFunction, secondFunc: ExtensionFunction) { + EXTENSION_FUNCTION_SAME_SIGNATURE.warn(configRules, emitWarn, isFixMode, "$firstFunc and $secondFunc", node.startOffset, node) + } + + data class FunctionSignature(val name: String, val parameters: String, val returnType: String?) { + override fun toString(): String { + return "$name$parameters${if(returnType != null) ": $returnType" else ""}" + } + } + data class ExtensionFunction(val className: String, val signature: FunctionSignature, val node: ASTNode) { + override fun toString(): String { + return "fun $className.$signature" + } + } +} diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml index 0d2b898f74..d3db44a8a0 100644 --- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml +++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml @@ -343,5 +343,9 @@ configuration: {} # Checks that there are abstract functions in abstract class - name: CLASS_SHOULD_NOT_BE_ABSTRACT + enabled: true + configuration: {} +# Checks if extension function with the same signature don't have related classes +- name: EXTENSION_FUNCTION_SAME_SIGNATURE enabled: true configuration: {} \ 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 d18ebca442..f39b611471 100644 --- a/diktat-rules/src/main/resources/diktat-analysis.yml +++ b/diktat-rules/src/main/resources/diktat-analysis.yml @@ -345,5 +345,9 @@ configuration: { } # Checks that there are abstract functions in abstract class - name: CLASS_SHOULD_NOT_BE_ABSTRACT + enabled: true + configuration: {} +# Checks if extension function with the same signature don't have related classes +- name: EXTENSION_FUNCTION_SAME_SIGNATURE enabled: true configuration: {} \ No newline at end of file diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt new file mode 100644 index 0000000000..28f2d3decf --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt @@ -0,0 +1,67 @@ +package org.cqfn.diktat.ruleset.chapter6 + +import com.pinterest.ktlint.core.LintError +import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.ruleset.rules.ExtensionFunctionsSameNameRule +import org.cqfn.diktat.util.LintTestBase +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class ExtensionFunctionsSameNameWarnTest : LintTestBase(::ExtensionFunctionsSameNameRule) { + private val ruleId = "$DIKTAT_RULE_SET_ID:extension-functions-same-name" + + @Test + fun `should trigger on functions with same signatures`() { + lintMethod( + """ + |open class A + |class B: A(), C + | + |fun A.foo() = "A" + |fun B.foo() = "B" + | + |fun printClassName(s: A) { print(s.foo()) } + | + |fun main() { printClassName(B()) } + """.trimMargin(), + LintError(4, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE.warnText()} fun A.foo() and fun B.foo()"), + LintError(5, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE.warnText()} fun A.foo() and fun B.foo()") + ) + } + + @Test + fun `should not trigger on functions with different signatures`() { + lintMethod( + """ + |open class A + |class B: A(), C + | + |fun A.foo(): Boolean = return true + |fun B.foo() = "B" + | + |fun printClassName(s: A) { print(s.foo()) } + | + |fun main() { printClassName(B()) } + """.trimMargin() + ) + } + + @Test + fun `should not trigger on functions with unrelated classes`() { + lintMethod( + """ + |interface A + |class B: A + |class C + | + |fun C.foo() = "C" + |fun B.foo() = "B" + | + |fun printClassName(s: A) { print(s.foo()) } + | + |fun main() { printClassName(B()) } + """.trimMargin() + ) + } +} \ No newline at end of file diff --git a/info/available-rules.md b/info/available-rules.md index ff8ef36a4f..030b534870 100644 --- a/info/available-rules.md +++ b/info/available-rules.md @@ -95,4 +95,5 @@ | 6 | 6.1.2 | USE_DATA_CLASS | Check: if class can be made as data class | no | - | yes | | 6 | 6.1.4 | MULTIPLE_INIT_BLOCKS | Checks that classes have only one init block | yes | no | - | | 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
Fix: deletes abstract modifier | yes | - | - | -| 6 | 6.1.9 | WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR | Check: used the name of a variable in the custom getter or setter | no | - | \ No newline at end of file +| 6 | 6.1.9 | WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR | Check: used the name of a variable in the custom getter or setter | no | - | +| 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 newline at end of file From 946c05f19ce21657b530ab68b054d257928704f0 Mon Sep 17 00:00:00 2001 From: soWhoAmI Date: Sat, 7 Nov 2020 11:23:21 +0300 Subject: [PATCH 2/7] feature/rule-6.2.2(#447) ### What's done: * Added rule logic * Added warn tests --- .../rules/ExtensionFunctionsSameNameRule.kt | 13 ++++++----- .../ExtensionFunctionsSameNameWarnTest.kt | 23 +++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt index c13d8db63c..5ac867b023 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt @@ -26,7 +26,7 @@ import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtNamedFunction typealias RelatedClasses = List> -typealias SimilarSignatures = List> +typealias SimilarSignatures = List> /** * This rule checks if extension functions with the same signature don't have related classes @@ -42,7 +42,7 @@ class ExtensionFunctionsSameNameRule(private val configRules: List) isFixMode = autoCorrect /** - * 1) Collect all classes that extend other classes + * 1) Collect all classes that extend other classes (collect related classes) * 2) Collect all extension functions with same signature * 3) Check if classes of functions are related */ @@ -76,8 +76,8 @@ class ExtensionFunctionsSameNameRule(private val configRules: List) private fun collectAllExtensionFunctions(node: ASTNode) : SimilarSignatures { val extensionFunctionList = node.findAllNodesWithSpecificType(FUN).filter { it.hasChildOfType(TYPE_REFERENCE) && it.hasChildOfType(DOT) } - val distinctFunctionSignatures = mutableMapOf() - val extensionFunctions = mutableListOf>() + val distinctFunctionSignatures = mutableMapOf() // maps function signatures on node it is used by + val extensionFunctionsPairs = mutableListOf>() // pairs extension functions with same signature extensionFunctionList.forEach { val functionName = (it.psi as KtNamedFunction).name!! @@ -85,9 +85,10 @@ class ExtensionFunctionsSameNameRule(private val configRules: List) val returnType = it.findChildAfter(COLON, TYPE_REFERENCE)?.text val className = it.findChildBefore(DOT, TYPE_REFERENCE)!!.text val signature = FunctionSignature(functionName, params, returnType) + if (distinctFunctionSignatures.contains(signature)) { val secondFuncClassName = distinctFunctionSignatures[signature]!!.findChildBefore(DOT, TYPE_REFERENCE)!!.text - extensionFunctions.add(Pair( + extensionFunctionsPairs.add(Pair( ExtensionFunction(secondFuncClassName, signature, distinctFunctionSignatures[signature]!!), ExtensionFunction(className, signature, it))) } else { @@ -95,7 +96,7 @@ class ExtensionFunctionsSameNameRule(private val configRules: List) } } - return extensionFunctions + return extensionFunctionsPairs } private fun handleFunctions(relatedClasses: RelatedClasses, functions: SimilarSignatures) { diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt index 28f2d3decf..6711367634 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt @@ -1,6 +1,7 @@ package org.cqfn.diktat.ruleset.chapter6 import com.pinterest.ktlint.core.LintError +import generated.WarningNames.EXTENSION_FUNCTION_SAME_SIGNATURE import org.cqfn.diktat.ruleset.constants.Warnings import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID import org.cqfn.diktat.ruleset.rules.ExtensionFunctionsSameNameRule @@ -12,6 +13,7 @@ class ExtensionFunctionsSameNameWarnTest : LintTestBase(::ExtensionFunctionsSame private val ruleId = "$DIKTAT_RULE_SET_ID:extension-functions-same-name" @Test + @Tag(EXTENSION_FUNCTION_SAME_SIGNATURE) fun `should trigger on functions with same signatures`() { lintMethod( """ @@ -31,6 +33,7 @@ class ExtensionFunctionsSameNameWarnTest : LintTestBase(::ExtensionFunctionsSame } @Test + @Tag(EXTENSION_FUNCTION_SAME_SIGNATURE) fun `should not trigger on functions with different signatures`() { lintMethod( """ @@ -48,6 +51,7 @@ class ExtensionFunctionsSameNameWarnTest : LintTestBase(::ExtensionFunctionsSame } @Test + @Tag(EXTENSION_FUNCTION_SAME_SIGNATURE) fun `should not trigger on functions with unrelated classes`() { lintMethod( """ @@ -64,4 +68,23 @@ class ExtensionFunctionsSameNameWarnTest : LintTestBase(::ExtensionFunctionsSame """.trimMargin() ) } + + @Test + @Tag(EXTENSION_FUNCTION_SAME_SIGNATURE) + fun `should not trigger on regular func`() { + lintMethod( + """ + |interface A + |class B: A + |class C + | + |fun foo() = "C" + |fun bar() = "B" + | + |fun printClassName(s: A) { print(s.foo()) } + | + |fun main() { printClassName(B()) } + """.trimMargin() + ) + } } \ No newline at end of file From 189874642df5c6c5f205847a86f574ece6059006 Mon Sep 17 00:00:00 2001 From: soWhoAmI Date: Sat, 7 Nov 2020 11:33:15 +0300 Subject: [PATCH 3/7] feature/rule-6.2.2(#447) ### What's done: * Added rule logic * Added warn tests --- .../rules/ExtensionFunctionsSameNameRule.kt | 3 +++ .../ExtensionFunctionsSameNameWarnTest.kt | 21 ++++++++++++++++++- info/available-rules.md | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt index 5ac867b023..17b2d70dc8 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt @@ -54,6 +54,8 @@ class ExtensionFunctionsSameNameRule(private val configRules: List) } + // Fixme: should find all related classes in project, not only in file + @Suppress("UnsafeCallOnNullableType") private fun collectAllRelatedClasses(node: ASTNode) : List> { val classListWithInheritance = node.findAllNodesWithSpecificType(CLASS) .filterNot { (it.psi as KtClass).isInterface() } @@ -74,6 +76,7 @@ class ExtensionFunctionsSameNameRule(private val configRules: List) return pairs } + @Suppress("UnsafeCallOnNullableType") private fun collectAllExtensionFunctions(node: ASTNode) : SimilarSignatures { val extensionFunctionList = node.findAllNodesWithSpecificType(FUN).filter { it.hasChildOfType(TYPE_REFERENCE) && it.hasChildOfType(DOT) } val distinctFunctionSignatures = mutableMapOf() // maps function signatures on node it is used by diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt index 6711367634..1c87016838 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt @@ -6,6 +6,7 @@ import org.cqfn.diktat.ruleset.constants.Warnings import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID import org.cqfn.diktat.ruleset.rules.ExtensionFunctionsSameNameRule import org.cqfn.diktat.util.LintTestBase +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test @@ -87,4 +88,22 @@ class ExtensionFunctionsSameNameWarnTest : LintTestBase(::ExtensionFunctionsSame """.trimMargin() ) } -} \ No newline at end of file + + @Test + @Disabled + @Tag(EXTENSION_FUNCTION_SAME_SIGNATURE) + fun `should trigger on classes in other files`() { + lintMethod( + """ + |fun A.foo() = "A" + |fun B.foo() = "B" + | + |fun printClassName(s: A) { print(s.foo()) } + | + |fun main() { printClassName(B()) } + """.trimMargin(), + LintError(1, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE.warnText()} fun A.foo() and fun B.foo()"), + LintError(2, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE.warnText()} fun A.foo() and fun B.foo()") + ) + } +} diff --git a/info/available-rules.md b/info/available-rules.md index 030b534870..5865f020d1 100644 --- a/info/available-rules.md +++ b/info/available-rules.md @@ -96,4 +96,4 @@ | 6 | 6.1.4 | MULTIPLE_INIT_BLOCKS | Checks that classes have only one init block | yes | no | - | | 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
Fix: deletes abstract modifier | yes | - | - | | 6 | 6.1.9 | WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR | Check: used the name of a variable in the custom getter or setter | no | - | -| 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 newline at end of file +| 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 newline at end of file From af5857acd61115b0683898f6d004053a8881cd8f Mon Sep 17 00:00:00 2001 From: soWhoAmI Date: Tue, 10 Nov 2020 11:58:17 +0300 Subject: [PATCH 4/7] feature/rule-6.2.2($447) ### What's done: * Fixed bugs --- .../main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 6d7980fbc6..2e01281769 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 @@ -125,8 +125,8 @@ enum class Warnings(private val canBeAutoCorrected: Boolean, private val warn: S WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR(false, "Use `field` keyword instead of property name inside property accessors"), MULTIPLE_INIT_BLOCKS(true, "Avoid using multiple `init` blocks, this logic can be moved to constructors or properties declarations"), CLASS_SHOULD_NOT_BE_ABSTRACT(true, "class should not be abstract, because it has no abstract functions"), - TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED(true, "trivial property accessors are not recommended") - EXTENSION_FUNCTION_SAME_SIGNATURE(false, "extension functions should not have same signature if their classes are related") + TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED(true, "trivial property accessors are not recommended"), + EXTENSION_FUNCTION_SAME_SIGNATURE(false, "extension functions should not have same signature if their classes are related"), ; /** From 2448a3512b2a0d6039daf22405b59e38720680a0 Mon Sep 17 00:00:00 2001 From: soWhoAmI Date: Tue, 10 Nov 2020 16:16:50 +0300 Subject: [PATCH 5/7] feature/rule-6.2.2(#447) ### What's done: * Fixed bugs --- .../cqfn/diktat/ruleset/constants/Warnings.kt | 2 +- .../rules/ExtensionFunctionsSameNameRule.kt | 39 ++++++++-------- .../ExtensionFunctionsSameNameWarnTest.kt | 46 ++++++++++++++++++- 3 files changed, 63 insertions(+), 24 deletions(-) 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 2e01281769..c622517d94 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 @@ -126,7 +126,7 @@ enum class Warnings(private val canBeAutoCorrected: Boolean, private val warn: S MULTIPLE_INIT_BLOCKS(true, "Avoid using multiple `init` blocks, this logic can be moved to constructors or properties declarations"), CLASS_SHOULD_NOT_BE_ABSTRACT(true, "class should not be abstract, because it has no abstract functions"), TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED(true, "trivial property accessors are not recommended"), - EXTENSION_FUNCTION_SAME_SIGNATURE(false, "extension functions should not have same signature if their classes are related"), + EXTENSION_FUNCTION_SAME_SIGNATURE(false, "extension functions should not have same signature if their receiver classes are related"), ; /** diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt index 17b2d70dc8..5afb08cb9b 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ExtensionFunctionsSameNameRule.kt @@ -24,6 +24,8 @@ import org.cqfn.diktat.ruleset.utils.hasChildOfType import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtParameterList typealias RelatedClasses = List> typealias SimilarSignatures = List> @@ -62,15 +64,13 @@ class ExtensionFunctionsSameNameRule(private val configRules: List) .filter { it.hasChildOfType(SUPER_TYPE_LIST) } val pairs = mutableListOf>() - classListWithInheritance.forEach { - val callEntries = it.findChildByType(SUPER_TYPE_LIST)!!.getAllChildrenWithType(SUPER_TYPE_CALL_ENTRY) + classListWithInheritance.forEach { classNode -> + val callEntries = classNode.findChildByType(SUPER_TYPE_LIST)!!.getAllChildrenWithType(SUPER_TYPE_CALL_ENTRY) callEntries.forEach { entry -> - if (entry.hasChildOfType(VALUE_ARGUMENT_LIST)) { - val className = (it.psi as KtClass).name!! - val entryName = entry.findLeafWithSpecificType(IDENTIFIER)!! - pairs.add(Pair(className, entryName.text)) - } + val className = (classNode.psi as KtClass).name!! + val entryName = entry.findLeafWithSpecificType(IDENTIFIER)!! + pairs.add(Pair(className, entryName.text)) } } return pairs @@ -82,20 +82,21 @@ class ExtensionFunctionsSameNameRule(private val configRules: List) val distinctFunctionSignatures = mutableMapOf() // maps function signatures on node it is used by val extensionFunctionsPairs = mutableListOf>() // pairs extension functions with same signature - extensionFunctionList.forEach { - val functionName = (it.psi as KtNamedFunction).name!! - val params = it.getFirstChildWithType(VALUE_PARAMETER_LIST)!!.text - val returnType = it.findChildAfter(COLON, TYPE_REFERENCE)?.text - val className = it.findChildBefore(DOT, TYPE_REFERENCE)!!.text + extensionFunctionList.forEach { func -> + val functionName = (func.psi as KtNamedFunction).name!! + // List is used to show param names in warning + val params = (func.getFirstChildWithType(VALUE_PARAMETER_LIST)!!.psi as KtParameterList).parameters.map { it.name!! } + val returnType = func.findChildAfter(COLON, TYPE_REFERENCE)?.text + val className = func.findChildBefore(DOT, TYPE_REFERENCE)!!.text val signature = FunctionSignature(functionName, params, returnType) if (distinctFunctionSignatures.contains(signature)) { val secondFuncClassName = distinctFunctionSignatures[signature]!!.findChildBefore(DOT, TYPE_REFERENCE)!!.text extensionFunctionsPairs.add(Pair( ExtensionFunction(secondFuncClassName, signature, distinctFunctionSignatures[signature]!!), - ExtensionFunction(className, signature, it))) + ExtensionFunction(className, signature, func))) } else { - distinctFunctionSignatures[signature] = it + distinctFunctionSignatures[signature] = func } } @@ -115,19 +116,15 @@ class ExtensionFunctionsSameNameRule(private val configRules: List) } private fun RelatedClasses.hasRelatedClasses(pair: Pair): Boolean { - forEach { - if (it.first == pair.first && it.second == pair.second - || it.first == pair.second && it.second == pair.first) - return true - } - return false + return any { it.first == pair.first && it.second == pair.second + || it.first == pair.second && it.second == pair.first } } private fun raiseWarning(node: ASTNode, firstFunc: ExtensionFunction, secondFunc: ExtensionFunction) { EXTENSION_FUNCTION_SAME_SIGNATURE.warn(configRules, emitWarn, isFixMode, "$firstFunc and $secondFunc", node.startOffset, node) } - data class FunctionSignature(val name: String, val parameters: String, val returnType: String?) { + data class FunctionSignature(val name: String, val parameters: List, val returnType: String?) { override fun toString(): String { return "$name$parameters${if(returnType != null) ": $returnType" else ""}" } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt index 1c87016838..1acf19119e 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsSameNameWarnTest.kt @@ -20,16 +20,40 @@ class ExtensionFunctionsSameNameWarnTest : LintTestBase(::ExtensionFunctionsSame """ |open class A |class B: A(), C + |class D: A() | |fun A.foo() = "A" |fun B.foo() = "B" + |fun D.foo() = "D" | |fun printClassName(s: A) { print(s.foo()) } | |fun main() { printClassName(B()) } """.trimMargin(), - LintError(4, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE.warnText()} fun A.foo() and fun B.foo()"), - LintError(5, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE.warnText()} fun A.foo() and fun B.foo()") + LintError(5, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE.warnText()} fun A.foo[] and fun B.foo[]"), + LintError(5, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE.warnText()} fun A.foo[] and fun D.foo[]"), + LintError(6, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE.warnText()} fun A.foo[] and fun B.foo[]"), + LintError(7, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE.warnText()} fun A.foo[] and fun D.foo[]"), + ) + } + + @Test + @Tag(EXTENSION_FUNCTION_SAME_SIGNATURE) + fun `should trigger on functions with same signatures 2`() { + lintMethod( + """ + |open class A + |class B: A(), C + | + |fun A.foo(some: Int) = "A" + |fun B.foo(some: Int) = "B" + | + |fun printClassName(s: A) { print(s.foo()) } + | + |fun main() { printClassName(B()) } + """.trimMargin(), + LintError(4, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE.warnText()} fun A.foo[some] and fun B.foo[some]"), + LintError(5, 1, ruleId, "${Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE.warnText()} fun A.foo[some] and fun B.foo[some]") ) } @@ -51,6 +75,24 @@ class ExtensionFunctionsSameNameWarnTest : LintTestBase(::ExtensionFunctionsSame ) } + @Test + @Tag(EXTENSION_FUNCTION_SAME_SIGNATURE) + fun `should not trigger on functions with different signatures 2`() { + lintMethod( + """ + |open class A + |class B: A(), C + | + |fun A.foo() = return true + |fun B.foo(some: Int) = "B" + | + |fun printClassName(s: A) { print(s.foo()) } + | + |fun main() { printClassName(B()) } + """.trimMargin() + ) + } + @Test @Tag(EXTENSION_FUNCTION_SAME_SIGNATURE) fun `should not trigger on functions with unrelated classes`() { From 163a46c28757e591c4f62b564ce760f5f19c0c0f Mon Sep 17 00:00:00 2001 From: soWhoAmI Date: Mon, 16 Nov 2020 11:36:09 +0300 Subject: [PATCH 6/7] feature/rule-6.2.2(#447) ### What's done: * Merged master in current branch --- diktat-rules/src/main/resources/diktat-analysis-huawei.yml | 3 +++ diktat-rules/src/main/resources/diktat-analysis.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml index dbd6fc74f5..0e56e932c6 100644 --- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml +++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml @@ -285,4 +285,7 @@ enabled: true # Checks explicit supertype qualification - name: USELESS_SUPERTYPE + enabled: true +# Checks if extension function with the same signature don't have related classes +- name: EXTENSION_FUNCTION_SAME_SIGNATURE 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 99cb7048bf..d05bedba56 100644 --- a/diktat-rules/src/main/resources/diktat-analysis.yml +++ b/diktat-rules/src/main/resources/diktat-analysis.yml @@ -287,4 +287,7 @@ enabled: true # Checks explicit supertype qualification - name: USELESS_SUPERTYPE + enabled: true +# Checks if extension function with the same signature don't have related classes +- name: EXTENSION_FUNCTION_SAME_SIGNATURE enabled: true \ No newline at end of file From d8f5a8503c3cd071809a77ea24a66764c67c201e Mon Sep 17 00:00:00 2001 From: soWhoAmI Date: Mon, 16 Nov 2020 11:44:47 +0300 Subject: [PATCH 7/7] feature/rule-6.2.2(#447) ### What's done: * Fixed bugs --- .../main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt | 1 - 1 file changed, 1 deletion(-) 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 a3866aa823..8641368aa7 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 @@ -125,7 +125,6 @@ enum class Warnings(private val canBeAutoCorrected: Boolean, private val warn: S WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR(false, "Use `field` keyword instead of property name inside property accessors"), MULTIPLE_INIT_BLOCKS(true, "Avoid using multiple `init` blocks, this logic can be moved to constructors or properties declarations"), CLASS_SHOULD_NOT_BE_ABSTRACT(true, "class should not be abstract, because it has no abstract functions"), - TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED(true, "trivial property accessors are not recommended"), CUSTOM_GETTERS_SETTERS(false, "Custom getters and setters are not recommended, use class methods instead"), COMPACT_OBJECT_INITIALIZATION(true, "class instance can be initialized in `apply` block"), USELESS_SUPERTYPE(true,"unnecessary supertype specification"),