diff --git a/diktat-analysis.yml b/diktat-analysis.yml index 2fc9a8d98f..0fc89f8d7a 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -434,6 +434,6 @@ # 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 +# 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-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt index c154188c28..c2d2b51be4 100644 --- a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt +++ b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt @@ -2,8 +2,6 @@ * Classes and extensions needed to read and parse rules configuration file */ -@file:Suppress("EXTENSION_FUNCTION_WITH_CLASS") - package org.cqfn.diktat.common.config.rules import org.cqfn.diktat.common.config.reader.JsonResourceConfigReader diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt index 6b768542c7..9c508bc30b 100644 --- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt +++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt @@ -240,7 +240,7 @@ public object WarningNames { public const val OBJECT_IS_PREFERRED: String = "OBJECT_IS_PREFERRED" - public const val EXTENSION_FUNCTION_WITH_CLASS: String = "EXTENSION_FUNCTION_WITH_CLASS" - 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 cc4e0b37be..9e9a2d152b 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 @@ -153,7 +153,7 @@ enum class Warnings( 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"), 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 with class defined in the same file"), + 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/chapter6/ExtensionFunctionsInFileRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt index 1fef929795..8e274abe0f 100644 --- 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 @@ -13,7 +13,9 @@ import com.pinterest.ktlint.core.ast.ElementType.CLASS import com.pinterest.ktlint.core.ast.ElementType.DOT 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 org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtFunction /** @@ -29,26 +31,33 @@ class ExtensionFunctionsInFileRule(private val configRules: List) : emitWarn = emit isFixMode = autoCorrect - if (node.elementType == ElementType.FILE && node.hasChildOfType(CLASS)) { - collectAllExtensionFunctions(node).forEach { + if (node.elementType == ElementType.FILE) { + val classNames = collectAllClassNames(node) + + collectAllExtensionFunctionsWithSameClassName(node, classNames).forEach { fireWarning(it) } } } + @Suppress("UnsafeCallOnNullableType") + private fun collectAllClassNames(node: ASTNode): List { + 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 collectAllExtensionFunctions(node: ASTNode): List { - return node.findAllNodesWithSpecificType(FUN).filter { isExtensionFunction(it) } + private fun collectAllExtensionFunctionsWithSameClassName(node: ASTNode, classNames: List): List { + return node.findAllNodesWithSpecificType(FUN).filter { isExtensionFunctionWithClassName(it, classNames) } } @Suppress("UnsafeCallOnNullableType") - private fun isExtensionFunction(node: ASTNode): Boolean = - node - .getFirstChildWithType(IDENTIFIER)!! - .treePrev - .elementType == DOT + private fun isExtensionFunctionWithClassName(node: ASTNode, classNames: List): Boolean = + node.getFirstChildWithType(IDENTIFIER)!!.treePrev.treePrev.elementType == TYPE_REFERENCE + && node.getFirstChildWithType(IDENTIFIER)!!.treePrev.treePrev.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 8d7f741288..e2dce970d8 100644 --- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml +++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml @@ -434,6 +434,6 @@ # 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 +# 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 0c726738c1..5c3d4cf66d 100644 --- a/diktat-rules/src/main/resources/diktat-analysis.yml +++ b/diktat-rules/src/main/resources/diktat-analysis.yml @@ -431,6 +431,6 @@ # 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 +# 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/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsInFileWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ExtensionFunctionsInFileWarnTest.kt index 9f25c2a460..1bee46026c 100644 --- 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 @@ -18,11 +18,11 @@ class ExtensionFunctionsInFileWarnTest : LintTestBase(::ExtensionFunctionsInFile fun `should warn on function`() { lintMethod( """ - |class Some1 private constructor () { + |class Some private constructor () { | |} | - |private fun String.coolStr() { + |private fun Some.coolStr() { | |} """.trimMargin(), @@ -35,11 +35,11 @@ class ExtensionFunctionsInFileWarnTest : LintTestBase(::ExtensionFunctionsInFile fun `should warn on several functions`() { lintMethod( """ - |class Some1 private constructor () { + |class Another private constructor () { | |} | - |private fun /* Random comment */ String.coolStr() { + |private fun /* Random comment */ Another.coolStr() { | |} | @@ -87,7 +87,7 @@ class ExtensionFunctionsInFileWarnTest : LintTestBase(::ExtensionFunctionsInFile """ |class Some { | - | fun String.str() { + | fun Some.str() { | | } |} @@ -113,4 +113,22 @@ class ExtensionFunctionsInFileWarnTest : LintTestBase(::ExtensionFunctionsInFile """.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() + ) + } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt index 3536d6d852..8492b01a95 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt @@ -27,7 +27,6 @@ import org.junit.jupiter.api.Test import java.time.LocalDate import kotlinx.serialization.encodeToString -import org.cqfn.diktat.ruleset.constants.Warnings.EXTENSION_FUNCTION_WITH_CLASS typealias ruleToConfig = Map> @@ -167,15 +166,12 @@ class DiktatSmokeTest : FixTestBase("test/smoke/src/main/kotlin", LintError(3, 6, "$DIKTAT_RULE_SET_ID:kdoc-comments", "${MISSING_KDOC_TOP_LEVEL.warnText()} Example", false), LintError(3, 26, "$DIKTAT_RULE_SET_ID:kdoc-comments", "${MISSING_KDOC_CLASS_ELEMENTS.warnText()} isValid", false), LintError(6, 9, "$DIKTAT_RULE_SET_ID:kdoc-comments", "${MISSING_KDOC_CLASS_ELEMENTS.warnText()} foo", false), - LintError(8, 8, "$DIKTAT_RULE_SET_ID:extension-functions-class-file", "${EXTENSION_FUNCTION_WITH_CLASS.warnText()} fun foo", false), LintError(8, 8, "$DIKTAT_RULE_SET_ID:kdoc-comments", "${MISSING_KDOC_CLASS_ELEMENTS.warnText()} foo", false), LintError(8, 8, "$DIKTAT_RULE_SET_ID:kdoc-methods", "${MISSING_KDOC_ON_FUNCTION.warnText()} foo", false), LintError(9, 3, "$DIKTAT_RULE_SET_ID:empty-block-structure", EMPTY_BLOCK_STRUCTURE_ERROR.warnText() + " empty blocks are forbidden unless it is function with override keyword", false), LintError(12, 10, "$DIKTAT_RULE_SET_ID:kdoc-formatting", "${KDOC_NO_EMPTY_TAGS.warnText()} @return", false), - LintError(13, 6, "$DIKTAT_RULE_SET_ID:extension-functions-class-file", "${EXTENSION_FUNCTION_WITH_CLASS.warnText()} fun countSubStringOccurrences", false), LintError(14, 3, "$DIKTAT_RULE_SET_ID:kdoc-formatting", "${KDOC_NO_EMPTY_TAGS.warnText()} @return", false), - LintError(19, 4, "$DIKTAT_RULE_SET_ID:extension-functions-class-file", "${EXTENSION_FUNCTION_WITH_CLASS.warnText()} fun splitPathToDirs", false), LintError(19, 15, "$DIKTAT_RULE_SET_ID:kdoc-formatting", "${KDOC_NO_EMPTY_TAGS.warnText()} @return", false) ) } diff --git a/info/available-rules.md b/info/available-rules.md index 817915a550..8819fdb11a 100644 --- a/info/available-rules.md +++ b/info/available-rules.md @@ -114,4 +114,4 @@ | 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 | + | -| 6 | 6.2.3 | EXTENSION_FUNCTION_WITH_CLASS | Check: if file contains class, then it can not have extension functions | no | no | - | \ No newline at end of file +| 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