-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature. Recommendation 6.4.2: Objects should be used for Stateless I…
…nterfaces * feature/recommendation-6.4.2(#449) ### What's done: * Added rule logic * Added warn tests * Added fix tests
- Loading branch information
Showing
13 changed files
with
246 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/StatelessClassesRule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package org.cqfn.diktat.ruleset.rules.classes | ||
|
||
import org.cqfn.diktat.common.config.rules.RulesConfig | ||
import org.cqfn.diktat.ruleset.constants.EmitType | ||
import org.cqfn.diktat.ruleset.constants.Warnings | ||
import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType | ||
import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType | ||
import org.cqfn.diktat.ruleset.utils.getFirstChildWithType | ||
import org.cqfn.diktat.ruleset.utils.hasChildOfType | ||
|
||
import com.pinterest.ktlint.core.Rule | ||
import com.pinterest.ktlint.core.ast.ElementType.CLASS | ||
import com.pinterest.ktlint.core.ast.ElementType.CLASS_KEYWORD | ||
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.INTERFACE_KEYWORD | ||
import com.pinterest.ktlint.core.ast.ElementType.OBJECT_DECLARATION | ||
import com.pinterest.ktlint.core.ast.ElementType.OBJECT_KEYWORD | ||
import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_ENTRY | ||
import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_LIST | ||
import com.pinterest.ktlint.core.ast.children | ||
import org.jetbrains.kotlin.com.intellij.lang.ASTNode | ||
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement | ||
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement | ||
import org.jetbrains.kotlin.psi.KtClass | ||
|
||
/** | ||
* This rule checks if class is stateless and if so changes it to object. | ||
*/ | ||
class StatelessClassesRule(private val configRule: List<RulesConfig>) : Rule("stateless-class") { | ||
private var isFixMode: Boolean = false | ||
private lateinit var emitWarn: EmitType | ||
|
||
override fun visit(node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: EmitType) { | ||
emitWarn = emit | ||
isFixMode = autoCorrect | ||
|
||
// Fixme: We should find interfaces in all project and then check them | ||
if (node.elementType == FILE) { | ||
val interfacesNodes = node | ||
.findAllNodesWithSpecificType(CLASS) | ||
.filter { it.hasChildOfType(INTERFACE_KEYWORD) } | ||
node | ||
.findAllNodesWithSpecificType(CLASS) | ||
.filterNot { it.hasChildOfType(INTERFACE_KEYWORD) } | ||
.forEach { handleClass(it, interfacesNodes) } | ||
} | ||
} | ||
|
||
@Suppress("UnsafeCallOnNullableType") | ||
private fun handleClass(node: ASTNode, interfaces: List<ASTNode>) { | ||
if (isClassExtendsValidInterface(node, interfaces) && isStatelessClass(node)) { | ||
Warnings.OBJECT_IS_PREFERRED.warnAndFix(configRule, emitWarn, isFixMode, | ||
"class ${(node.psi as KtClass).name!!}", node.startOffset, node) { | ||
val newObjectNode = CompositeElement(OBJECT_DECLARATION) | ||
node.treeParent.addChild(newObjectNode, node) | ||
node.children().forEach { | ||
newObjectNode.addChild(it.copyElement(), null) | ||
} | ||
newObjectNode.addChild(LeafPsiElement(OBJECT_KEYWORD, "object"), | ||
newObjectNode.getFirstChildWithType(CLASS_KEYWORD)) | ||
newObjectNode.removeChild(newObjectNode.getFirstChildWithType(CLASS_KEYWORD)!!) | ||
node.treeParent.removeChild(node) | ||
} | ||
} | ||
} | ||
|
||
private fun isStatelessClass(node: ASTNode): Boolean { | ||
val properties = (node.psi as KtClass).getProperties() | ||
val functions = node.findAllNodesWithSpecificType(FUN) | ||
return properties.isNullOrEmpty() && | ||
functions.isNotEmpty() && | ||
!(node.psi as KtClass).hasExplicitPrimaryConstructor() | ||
} | ||
|
||
private fun isClassExtendsValidInterface(node: ASTNode, interfaces: List<ASTNode>): Boolean = | ||
node.findChildByType(SUPER_TYPE_LIST) | ||
?.getAllChildrenWithType(SUPER_TYPE_ENTRY) | ||
?.isNotEmpty() | ||
?.and(isClassInheritsStatelessInterface(node, interfaces)) | ||
?: false | ||
|
||
@Suppress("UnsafeCallOnNullableType") | ||
private fun isClassInheritsStatelessInterface(node: ASTNode, interfaces: List<ASTNode>): Boolean { | ||
val classInterfaces = node | ||
.findChildByType(SUPER_TYPE_LIST) | ||
?.getAllChildrenWithType(SUPER_TYPE_ENTRY) | ||
|
||
val foundInterfaces = interfaces.filter { inter -> | ||
classInterfaces!!.any { it.text == inter.getFirstChildWithType(IDENTIFIER)!!.text } | ||
} | ||
|
||
return foundInterfaces.any { (it.psi as KtClass).getProperties().isEmpty() } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/StatelessClassesRuleFixTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package org.cqfn.diktat.ruleset.chapter6 | ||
|
||
import org.cqfn.diktat.ruleset.rules.classes.StatelessClassesRule | ||
import org.cqfn.diktat.util.FixTestBase | ||
|
||
import generated.WarningNames.OBJECT_IS_PREFERRED | ||
import org.junit.jupiter.api.Tag | ||
import org.junit.jupiter.api.Test | ||
|
||
class StatelessClassesRuleFixTest : FixTestBase("test/chapter6/stateless_classes", ::StatelessClassesRule) { | ||
@Test | ||
@Tag(OBJECT_IS_PREFERRED) | ||
fun `fix class to object keyword`() { | ||
fixAndCompare("StatelessClassExpected.kt", "StatelessClassTest.kt") | ||
} | ||
} |
83 changes: 83 additions & 0 deletions
83
...at-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/StatelessClassesRuleWarnTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package org.cqfn.diktat.ruleset.chapter6 | ||
|
||
import org.cqfn.diktat.ruleset.constants.Warnings | ||
import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID | ||
import org.cqfn.diktat.ruleset.rules.classes.StatelessClassesRule | ||
import org.cqfn.diktat.util.LintTestBase | ||
|
||
import com.pinterest.ktlint.core.LintError | ||
import generated.WarningNames.OBJECT_IS_PREFERRED | ||
import org.junit.jupiter.api.Tag | ||
import org.junit.jupiter.api.Test | ||
|
||
class StatelessClassesRuleWarnTest : LintTestBase(::StatelessClassesRule) { | ||
private val ruleId = "$DIKTAT_RULE_SET_ID:stateless-class" | ||
|
||
@Test | ||
@Tag(OBJECT_IS_PREFERRED) | ||
fun `should not trigger on class not extending any interface`() { | ||
lintMethod( | ||
""" | ||
|class Some : I() { | ||
| override fun some() | ||
|} | ||
""".trimMargin() | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(OBJECT_IS_PREFERRED) | ||
fun `should trigger on class extending interface`() { | ||
lintMethod( | ||
""" | ||
|class Some : I { | ||
| override fun some() | ||
|} | ||
| | ||
|interface I { | ||
| fun some() | ||
|} | ||
""".trimMargin(), | ||
LintError(1, 1, ruleId, "${Warnings.OBJECT_IS_PREFERRED.warnText()} class Some", true) | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(OBJECT_IS_PREFERRED) | ||
fun `should not trigger on class with constructor`() { | ||
lintMethod( | ||
""" | ||
|class Some(b: Int) : I { | ||
| | ||
| override fun some() | ||
|} | ||
""".trimMargin() | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(OBJECT_IS_PREFERRED) | ||
fun `should not trigger on class with no interface in this file`() { | ||
lintMethod( | ||
""" | ||
|class Some : I { | ||
| | ||
| override fun some() | ||
|} | ||
""".trimMargin() | ||
) | ||
} | ||
|
||
@Test | ||
@Tag(OBJECT_IS_PREFERRED) | ||
fun `should not trigger on class with state`() { | ||
lintMethod( | ||
""" | ||
|class Some : I { | ||
| val a = 5 | ||
| override fun some() | ||
|} | ||
""".trimMargin() | ||
) | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
diktat-rules/src/test/resources/test/chapter6/stateless_classes/StatelessClassExpected.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package test.chapter6.stateless_classes | ||
|
||
interface I { | ||
fun foo() | ||
} | ||
|
||
object O: I { | ||
override fun foo() {} | ||
} | ||
|
||
/** | ||
* Some KDOC | ||
*/ | ||
object A: I { | ||
override fun foo() {} | ||
} |
16 changes: 16 additions & 0 deletions
16
diktat-rules/src/test/resources/test/chapter6/stateless_classes/StatelessClassTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package test.chapter6.stateless_classes | ||
|
||
interface I { | ||
fun foo() | ||
} | ||
|
||
class O: I { | ||
override fun foo() {} | ||
} | ||
|
||
/** | ||
* Some KDOC | ||
*/ | ||
class A: I { | ||
override fun foo() {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters