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.1.5: Explicit supertype qualification should not be used if there is not clash between called methods #458

Merged
merged 15 commits into from
Nov 16, 2020
4 changes: 4 additions & 0 deletions diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -339,5 +339,9 @@
configuration: {}
# Checks that there are abstract functions in abstract class
- name: CLASS_SHOULD_NOT_BE_ABSTRACT
enabled: true
configuration: {}
# Checks that override function is useful
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
- name: USELESS_SUPERTYPE
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 @@ -200,4 +200,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 USELESS_SUPERTYPE: String = "USELESS_SUPERTYPE"
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,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"),
USELESS_SUPERTYPE(true,"unnecessary supertype specification"),
;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class DiktatRuleSetProvider(private val diktatConfigFile: String = "diktat-analy
::PackageNaming,
::IdentifierNaming,
// code structure
::UselessSupertype,
::LocalVariablesRule,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you move LocalVariablesRule on purpose?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it was my a mistake during the merge

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, github shows it's still there

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move LocalVariablesRule back if it's unintended move, the rest seems fine.

::ClassLikeStructuresOrderRule,
::WhenMustHaveElseRule,
::BracesInConditionalsAndLoopsRule,
Expand All @@ -73,7 +75,6 @@ class DiktatRuleSetProvider(private val diktatConfigFile: String = "diktat-analy
// other rules
::StringTemplateFormatRule,
::DataClassesRule,
::LocalVariablesRule,
::SmartCastRule,
::PropertyAccessorFields,
::AbstractClassesRule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package org.cqfn.diktat.ruleset.rules

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.CLASS
import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY
import com.pinterest.ktlint.core.ast.ElementType.CLASS_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.DOT_QUALIFIED_EXPRESSION
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.MODIFIER_LIST
import com.pinterest.ktlint.core.ast.ElementType.OPEN_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.SUPER_EXPRESSION
import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_CALL_ENTRY
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.ElementType.TYPE_REFERENCE
import com.pinterest.ktlint.core.ast.parent
import org.cqfn.diktat.common.config.rules.RulesConfig
import org.cqfn.diktat.ruleset.constants.Warnings.USELESS_SUPERTYPE
import org.cqfn.diktat.ruleset.utils.*
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.psiUtil.siblings

/**
* rule 6.1.5
* Explicit supertype qualification should not be used if there is not clash between called methods
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
* fixme can't fix supertypes that are defined in other files.
*/
class UselessSupertype(private val configRules: List<RulesConfig>) : Rule("useless-override") {

companion object {
private val SUPER_TYPE = listOf(SUPER_TYPE_CALL_ENTRY, SUPER_TYPE_ENTRY)
}

private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit)
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
private var isFixMode: Boolean = false

override fun visit(node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
emitWarn = emit
isFixMode = autoCorrect

if (node.elementType == CLASS)
checkClass(node)
}

private fun checkClass(node: ASTNode) {
val superNodes = node.findChildByType(SUPER_TYPE_LIST)?.findAllNodesWithCondition({ it.elementType in SUPER_TYPE })?.takeIf { it.isNotEmpty() } ?: return
val qualifiedSuperCalls = node.findAllNodesWithSpecificType(DOT_QUALIFIED_EXPRESSION)
.mapNotNull { findFunWithSuper(it) }.ifEmpty { return }
if (superNodes.size == 1) {
qualifiedSuperCalls.map { removeSupertype(it.first) }
} else {
handleManyImpl(superNodes, qualifiedSuperCalls)
}
}

private fun handleManyImpl(superNodes: List<ASTNode>, overrideNodes: List<Pair<ASTNode, ASTNode>>) {
val uselessSuperType = findAllSupers(superNodes, overrideNodes.map { it.second.text })?.filter { it.value == 1 }?.map { it.key }
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
?: return
overrideNodes
.filter {
it.second.text in uselessSuperType
}.map {
removeSupertype(it.first)
}
}

@Suppress("UnsafeCallOnNullableType")
private fun removeSupertype(node: ASTNode) {
USELESS_SUPERTYPE.warnAndFix(configRules, emitWarn, isFixMode, node.text, node.startOffset, node) {
val startNode = node.parent({ it.elementType == SUPER_EXPRESSION })!!.findChildByType(REFERENCE_EXPRESSION)!!
val lastNode = startNode.siblings(true).last()
startNode.treeParent.removeRange(startNode.treeNext, lastNode)
startNode.treeParent.removeChild(lastNode)
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* Method finds pair of identifier supertype and method name else return null
* example: super<A>.foo() -> return Pair(A, foo)
* super.foo() -> null
* @param node - node of type DOT_QUALIFIED_EXPRESSION
*
* @return pair of identifier
*/
@Suppress("UnsafeCallOnNullableType")
private fun findFunWithSuper(node: ASTNode): Pair<ASTNode, ASTNode>? {
return Pair(
node.findChildByType(SUPER_EXPRESSION)
?.findChildByType(TYPE_REFERENCE)
?.findAllNodesWithSpecificType(IDENTIFIER)
?.firstOrNull() ?: return null,
node.findChildByType(CALL_EXPRESSION)
?.findAllNodesWithSpecificType(IDENTIFIER)
?.firstOrNull() ?: return null)
}

/**
* The method looks in the same file for all super interfaces or a class, in each it looks for methods
* that can be overridden and creates a map with a key - the name of the method and value - the number of times it meets
*
* @param superTypeList - list of identifiers super classes
* @param methodsName - name of overrides methods
*
* @return map name of method and the number of times it meets
*/
@Suppress("UnsafeCallOnNullableType")
private fun findAllSupers(superTypeList: List<ASTNode>, methodsName: List<String>): Map<String, Int>? {
val fileNode = superTypeList.first().parent({ it.elementType == FILE })!!
val superNodesIdentifier = superTypeList.map { it.findAllNodesWithSpecificType(IDENTIFIER).first().text }
val superNodes = fileNode.findAllNodesWithCondition({ superClass ->
superClass.elementType == CLASS &&
superClass.getIdentifierName()!!.text in superNodesIdentifier
}).mapNotNull { it.findChildByType(CLASS_BODY) }
if (superNodes.size != superTypeList.size)
return null
val functionNameMap = mutableMapOf<String, Int>()
superNodes.forEach { classBody ->
val overrideFunctions = classBody.findAllNodesWithSpecificType(FUN)
.filter {
(if (classBody.treeParent.hasChildOfType(CLASS_KEYWORD)) it.findChildByType(MODIFIER_LIST)!!.hasChildOfType(OPEN_KEYWORD) else true) &&
it.getIdentifierName()!!.text in methodsName
}
overrideFunctions.forEach {
functionNameMap[it.getIdentifierName()!!.text] = functionNameMap.getOrDefault(it.getIdentifierName()!!.text, 0) + 1
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
/*if (functionNameMap.containsKey(it.getIdentifierName()!!.text))
kentr0w marked this conversation as resolved.
Show resolved Hide resolved
functionNameMap[it.getIdentifierName()!!.text] = functionNameMap[it.getIdentifierName()!!.text]!! + 1
else
functionNameMap[it.getIdentifierName()!!.text] = 1*/
}
}
return functionNameMap.toMap()
}
}
4 changes: 4 additions & 0 deletions diktat-rules/src/main/resources/diktat-analysis-huawei.yml
Original file line number Diff line number Diff line change
Expand Up @@ -338,5 +338,9 @@
configuration: {}
# Checks that there are abstract functions in abstract class
- name: CLASS_SHOULD_NOT_BE_ABSTRACT
enabled: true
configuration: {}
# Checks that override function is useful
- name: USELESS_SUPERTYPE
enabled: true
configuration: {}
4 changes: 4 additions & 0 deletions diktat-rules/src/main/resources/diktat-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -340,5 +340,9 @@
configuration: { }
# Checks that there are abstract functions in abstract class
- name: CLASS_SHOULD_NOT_BE_ABSTRACT
enabled: true
configuration: {}
# Checks that override function is useful
- name: USELESS_SUPERTYPE
enabled: true
configuration: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.cqfn.diktat.ruleset.chapter6

import generated.WarningNames
import org.cqfn.diktat.util.FixTestBase
import org.cqfn.diktat.ruleset.rules.UselessSupertype
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test

class UselessSupertypeFixTest : FixTestBase("test/paragraph6/useless-override", ::UselessSupertype) {

@Test
@Tag(WarningNames.USELESS_SUPERTYPE)
fun `fix example with one super`() {
fixAndCompare("UselessOverrideExpected.kt", "UselessOverrideTest.kt")
}

@Test
@Tag(WarningNames.USELESS_SUPERTYPE)
fun `fix several super`() {
fixAndCompare("SeveralSuperTypesExpected.kt", "SeveralSuperTypesTest.kt")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.cqfn.diktat.ruleset.chapter6

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

class UselessSupertypeWarnTest: LintTestBase(::UselessSupertype) {

private val ruleId = "$DIKTAT_RULE_SET_ID:useless-override"

@Test
@Tag(WarningNames.USELESS_SUPERTYPE)
fun `check simple wrong example`() {
lintMethod(
"""
open class Rectangle {
open fun draw() { /* ... */ }
}

class Square() : Rectangle() {
override fun draw() {
/**
*
* hehe
*/
super<Rectangle>.draw()
}
}

class Square2() : Rectangle() {
override fun draw() {
//hehe
/*
hehe
*/
super<Rectangle>.draw()
}
}

class Square2() : Rectangle() {
override fun draw() {
val q = super.draw()
}
}

class A: Runnable {
override fun run() {

}
}
""".trimMargin(),
LintError(11,35, ruleId, "${USELESS_SUPERTYPE.warnText()} Rectangle", true),
LintError(21,35, ruleId, "${USELESS_SUPERTYPE.warnText()} Rectangle", true)
)
}

@Test
@Tag(WarningNames.USELESS_SUPERTYPE)
fun `check example with two super`() {
lintMethod(
"""
open class Rectangle {
open fun draw() { /* ... */ }
}

interface KK {
fun draw() {}
fun kk() {}
}

class Square2() : Rectangle(), KK {
override fun draw() {
super<Rectangle>.draw()
super<KK>.draw()
}

private fun goo() {
super<KK>.kk()
}

}
""".trimMargin(),
LintError(17,35, ruleId, "${USELESS_SUPERTYPE.warnText()} KK", true)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package test.paragraph6.`useless-override`

open class A {
open fun foo(){}
}

interface C {
fun foo(){}
fun goo(){}
}

class B: A(){
override fun foo() {
super.foo()
}
}

class D: A(), C {
override fun foo() {
super<C>.foo()
super<A>.foo()
super.goo()
}

private fun qwe(){
val q = super.goo()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package test.paragraph6.`useless-override`

open class A {
open fun foo(){}
}

interface C {
fun foo(){}
fun goo(){}
}

class B: A(){
override fun foo() {
super<A>.foo()
}
}

class D: A(), C {
override fun foo() {
super<C>.foo()
super<A>.foo()
super<C>.goo()
}

private fun qwe(){
val q = super<C>.goo()
}
}
Loading