Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rule 6.4.1: Avoid using utility classes/objects #508

Merged
merged 18 commits into from
Dec 6, 2020
Merged
6 changes: 5 additions & 1 deletion diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -361,4 +361,8 @@
# Use extra functions for it instead.
- name: CUSTOM_GETTERS_SETTERS
enabled: true
configuration: { }
configuration: { }
# Checks if there is class/object that can be replace with extension function
- name: AVOID_USING_UTILITY_CLASS
enabled: true
configuration: {}
2 changes: 2 additions & 0 deletions diktat-rules/src/main/kotlin/generated/WarningNames.kt
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,6 @@ public object WarningNames {
"TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED"

public const val CUSTOM_GETTERS_SETTERS: String = "CUSTOM_GETTERS_SETTERS"

public const val AVOID_USING_UTILITY_CLASS: String = "AVOID_USING_UTILITY_CLASS"
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ enum class Warnings(private val canBeAutoCorrected: Boolean, private val warn: S
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"),
AVOID_USING_UTILITY_CLASS(false, "avoid using utility classes/objects, use extensions functions"),
;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.cqfn.diktat.ruleset.rules

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.CLASS
import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY
import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT
import com.pinterest.ktlint.core.ast.ElementType.FUN
import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER
import com.pinterest.ktlint.core.ast.ElementType.KDOC
import com.pinterest.ktlint.core.ast.ElementType.LBRACE
import com.pinterest.ktlint.core.ast.ElementType.OBJECT_DECLARATION
import com.pinterest.ktlint.core.ast.ElementType.PRIMARY_CONSTRUCTOR
import com.pinterest.ktlint.core.ast.ElementType.RBRACE
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE
import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.ruleset.constants.EmitType
import org.cqfn.diktat.ruleset.constants.Warnings.AVOID_USING_UTILITY_CLASS
import org.cqfn.diktat.ruleset.utils.hasChildOfType
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.psiUtil.children

/**
* Rule 6.4.1 checks that class/object, with a word "util" in its name, has only functions.
*/
class AvoidUtilityClass(private val configRules: List<RulesConfig>) : Rule("avoid-utility-class") {

companion object {
private val UTILITY_CLASS_CHILDREN = listOf(LBRACE, WHITE_SPACE, FUN, RBRACE, KDOC,
EOL_COMMENT, BLOCK_COMMENT, OBJECT_DECLARATION)
}

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 == OBJECT_DECLARATION || node.elementType == CLASS ) {
checkClass(node)
}
}

@Suppress("UnsafeCallOnNullableType")
private fun checkClass(node: ASTNode) {
//checks that class/object doesn't contain primary constructor and its identifier doesn't has "utli"
if (node.hasChildOfType(PRIMARY_CONSTRUCTOR)
|| ( node.hasChildOfType(IDENTIFIER) &&
!node.findChildByType(IDENTIFIER)!!.text.toLowerCase().contains("util")))
return
node.findChildByType(CLASS_BODY)
?.children()
?.toList()
?.takeIf { childList -> childList.all { it.elementType in UTILITY_CLASS_CHILDREN } }
?.filter { it.elementType == FUN }
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
?.ifEmpty { return }
?: return
AVOID_USING_UTILITY_CLASS.warn(configRules, emitWarn, isFixMode, node.findChildByType(IDENTIFIER)?.text ?: node.text, node.startOffset, node)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class DiktatRuleSetProvider(private val diktatConfigFile: String = "diktat-analy
::DataClassesRule,
::LocalVariablesRule,
::SmartCastRule,
::AvoidUtilityClass,
::PropertyAccessorFields,
::AbstractClassesRule,
::SingleInitRule,
Expand Down
6 changes: 5 additions & 1 deletion diktat-rules/src/main/resources/diktat-analysis-huawei.yml
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,8 @@
# Use extra functions for it instead.
- name: CUSTOM_GETTERS_SETTERS
enabled: true
configuration: { }
configuration: { }
# Checks if there is class/object that can be replace with extension function
- name: AVOID_USING_UTILITY_CLASS
enabled: true
configuration: {}
6 changes: 5 additions & 1 deletion diktat-rules/src/main/resources/diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,8 @@
# Use extra functions for it instead.
- name: CUSTOM_GETTERS_SETTERS
enabled: true
configuration: { }
configuration: { }
# Checks if there is class/object that can be replace with extension function
- name: AVOID_USING_UTILITY_CLASS
enabled: true
configuration: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.cqfn.diktat.ruleset.chapter6

import com.pinterest.ktlint.core.LintError
import generated.WarningNames
import org.cqfn.diktat.ruleset.constants.Warnings.AVOID_USING_UTILITY_CLASS
import org.cqfn.diktat.ruleset.rules.AvoidUtilityClass
import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID
import org.cqfn.diktat.util.LintTestBase
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test

class AvoidUtilityClassWarnTest: LintTestBase(::AvoidUtilityClass) {
private val ruleId = "$DIKTAT_RULE_SET_ID:avoid-utility-class"

@Test
@Tag(WarningNames.AVOID_USING_UTILITY_CLASS)
fun `simple test`() {
lintMethod(
"""
|object StringUtil {
| fun stringInfo(myString: String): Int {
| return myString.count{ "something".contains(it) }
| }
|}
|
|class A() {
| fun foo() { }
|}
|
|class StringUtils {
| fun goo(tex: String): Int {
| return myString.count{ "something".contains(it) }
| }
|}
|
|class StringUtil {
| val z = "hello"
| fun goo(tex: String): Int {
| return myString.count{ "something".contains(it) }
| }
|}
""".trimMargin(),
LintError(1,1, ruleId, "${AVOID_USING_UTILITY_CLASS.warnText()} StringUtil"),
LintError(11,1, ruleId, "${AVOID_USING_UTILITY_CLASS.warnText()} StringUtils")
)
}

@Test
@Tag(WarningNames.AVOID_USING_UTILITY_CLASS)
fun `test with comment anf companion`() {
lintMethod(
"""
|
|class StringUtils {
| companion object {
| private val name = "Hello"
| }
| /**
| * @param tex
| */
| fun goo(tex: String): Int {
| //hehe
| return myString.count{ "something".contains(it) }
| }
|}
|
|class StringUtil {
| /*
|
| */
| companion object {
| }
| val z = "hello"
| fun goo(tex: String): Int {
| return myString.count{ "something".contains(it) }
| }
|}
""".trimMargin(),
LintError(2,1, ruleId, "${AVOID_USING_UTILITY_CLASS.warnText()} StringUtils")
)
}

@Test
@Tag(WarningNames.AVOID_USING_UTILITY_CLASS)
fun `test with class without identifier`() {
lintMethod(
"""
fun foo() {
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { /*...*/ }

override fun mouseEntered(e: MouseEvent) { /*...*/ }
})
}
""".trimMargin(),
LintError(2,45, ruleId, "${AVOID_USING_UTILITY_CLASS.warnText()} object : MouseAdapter() {...")
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
)
}
}
1 change: 1 addition & 0 deletions info/available-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,4 @@
| 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.1.10 | TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED | Check: if there are any trivial getters or setters <br> Fix: Delete trivial getter or setter | yes | - | - |
| 6 | 6.1.8 | CUSTOM_GETTERS_SETTERS | Check: Inspection that checks that no custom getters and setters are used for properties | no | - | - |
| 6 | 6.4.1 | AVOID_USING_UTILITY_CLASS | Checks if there is class/object that can be replace with extension function | no | - | - |