Skip to content

Commit

Permalink
feature/no-extension-func-with-class(#731)
Browse files Browse the repository at this point in the history
### What's done:
  * Logic remade
  • Loading branch information
aktsay6 committed Jan 27, 2021
1 parent 342dd87 commit a07f93e
Show file tree
Hide file tree
Showing 10 changed files with 48 additions and 27 deletions.
2 changes: 1 addition & 1 deletion diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions diktat-rules/src/main/kotlin/generated/WarningNames.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand All @@ -29,26 +31,33 @@ class ExtensionFunctionsInFileRule(private val configRules: List<RulesConfig>) :
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<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 collectAllExtensionFunctions(node: ASTNode): List<ASTNode> {
return node.findAllNodesWithSpecificType(FUN).filter { isExtensionFunction(it) }
private fun collectAllExtensionFunctionsWithSameClassName(node: ASTNode, classNames: List<String>): List<ASTNode> {
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<String>): Boolean =
node.getFirstChildWithType(IDENTIFIER)!!.treePrev.treePrev.elementType == TYPE_REFERENCE
&& node.getFirstChildWithType(IDENTIFIER)!!.treePrev.treePrev.text in classNames

}
2 changes: 1 addition & 1 deletion diktat-rules/src/main/resources/diktat-analysis-huawei.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion diktat-rules/src/main/resources/diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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() {
|
|}
|
Expand Down Expand Up @@ -87,7 +87,7 @@ class ExtensionFunctionsInFileWarnTest : LintTestBase(::ExtensionFunctionsInFile
"""
|class Some {
|
| fun String.str() {
| fun Some.str() {
|
| }
|}
Expand All @@ -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()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Warnings, Map<String, String>>

Expand Down Expand Up @@ -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)
)
}
Expand Down
2 changes: 1 addition & 1 deletion info/available-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | - |
| 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 | - |

0 comments on commit a07f93e

Please sign in to comment.