Skip to content

Commit

Permalink
SONARKT-302 - Implement rule S6526: abstract class should be interface (
Browse files Browse the repository at this point in the history
  • Loading branch information
erwan-serandour authored Nov 17, 2023
1 parent eadf3d3 commit 8c052ee
Show file tree
Hide file tree
Showing 11 changed files with 433 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"kotlin-android-architecture-components-project:sources/kotlin/android-architecture-components/GithubBrowserSample/app/src/main/java/com/android/example/github/di/FragmentBuildersModule.kt": [
28
],
"kotlin-android-architecture-components-project:sources/kotlin/android-architecture-components/GithubBrowserSample/app/src/main/java/com/android/example/github/di/MainActivityModule.kt": [
26
],
"kotlin-android-architecture-components-project:sources/kotlin/android-architecture-components/GithubBrowserSample/app/src/main/java/com/android/example/github/di/ViewModelModule.kt": [
33
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"kotlin-corda-project:sources/kotlin/corda/client/jackson/src/main/kotlin/net/corda/client/jackson/JacksonSupport.kt": [
487,
501
],
"kotlin-corda-project:sources/kotlin/corda/core/src/main/kotlin/net/corda/core/node/services/vault/QueryCriteriaUtils.kt": [
146
],
"kotlin-corda-project:sources/kotlin/corda/node/src/integration-test/kotlin/net/corda/node/amqp/AMQPBridgeTest.kt": [
46
],
"kotlin-corda-project:sources/kotlin/corda/node/src/integration-test/kotlin/net/corda/node/amqp/CertificateRevocationListNodeTests.kt": [
75
],
"kotlin-corda-project:sources/kotlin/corda/node/src/integration-test/kotlin/net/corda/node/amqp/ProtonWrapperTests.kt": [
50
],
"kotlin-corda-project:sources/kotlin/corda/node/src/test/kotlin/net/corda/node/internal/NodeTest.kt": [
33
],
"kotlin-corda-project:sources/kotlin/corda/node/src/test/kotlin/net/corda/node/services/messaging/ArtemisMessagingTest.kt": [
67
],
"kotlin-corda-project:sources/kotlin/corda/node/src/test/kotlin/net/corda/node/utilities/registration/NetworkRegistrationHelperTest.kt": [
54
],
"kotlin-corda-project:sources/kotlin/corda/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/EvolutionSerializer.kt": [
250
],
"kotlin-corda-project:sources/kotlin/corda/serialization/src/main/kotlin/net/corda/serialization/internal/amqp/PropertySerializers.kt": [
12
],
"kotlin-corda-project:sources/kotlin/corda/serialization/src/test/kotlin/net/corda/serialization/internal/CordaClassResolverTests.kt": [
68
],
"kotlin-corda-project:sources/kotlin/corda/serialization/src/test/kotlin/net/corda/serialization/internal/amqp/SerializationOutputTests.kt": [
212
],
"kotlin-corda-project:sources/kotlin/corda/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/InternalMockNetwork.kt": [
461
],
"kotlin-corda-project:sources/kotlin/corda/testing/test-utils/src/test/kotlin/net/corda/testing/internal/RigorousMockTest.kt": [
18
]
}
115 changes: 115 additions & 0 deletions its/ruling/src/test/resources/expected/kotlin/kotlin/kotlin-S6526.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
{
"kotlin-kotlin-project:sources/kotlin/kotlin/compiler/container/tests/org/jetbrains/kotlin/container/tests/ComponentContainerTest.kt": [
316
],
"kotlin-kotlin-project:sources/kotlin/kotlin/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt": [
310
],
"kotlin-kotlin-project:sources/kotlin/kotlin/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt": [
86
],
"kotlin-kotlin-project:sources/kotlin/kotlin/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/tower/TowerResolver.kt": [
291
],
"kotlin-kotlin-project:sources/kotlin/kotlin/compiler/tests/org/jetbrains/kotlin/codegen/AbstractCustomScriptCodegenTest.kt": [
139,
150
],
"kotlin-kotlin-project:sources/kotlin/kotlin/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt": [
927
],
"kotlin-kotlin-project:sources/kotlin/kotlin/compiler/tests/org/jetbrains/kotlin/scripts/ScriptTemplateTest.kt": [
563,
587,
590,
593
],
"kotlin-kotlin-project:sources/kotlin/kotlin/core/builtins/native/kotlin/Number.kt": [
22
],
"kotlin-kotlin-project:sources/kotlin/kotlin/core/descriptors.runtime/src/kotlin/reflect/jvm/internal/structure/ReflectJavaElement.kt": [
21
],
"kotlin-kotlin-project:sources/kotlin/kotlin/idea/formatter/src/org/jetbrains/kotlin/idea/formatter/CommonAlignmentStrategy.kt": [
22
],
"kotlin-kotlin-project:sources/kotlin/kotlin/idea/idea-analysis/src/org/jetbrains/kotlin/idea/configuration/BuildSystemType.kt": [
12
],
"kotlin-kotlin-project:sources/kotlin/kotlin/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/stubBuilder/flags/flags.kt": [
24
],
"kotlin-kotlin-project:sources/kotlin/kotlin/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinStepActionFactory.kt": [
36
],
"kotlin-kotlin-project:sources/kotlin/kotlin/js/js.translator/src/org/jetbrains/kotlin/js/translate/intrinsic/functions/basic/FunctionIntrinsic.kt": [
26
],
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/examples/scripting/jvm-maven-deps/script/src/org/jetbrains/kotlin/script/examples/jvm/resolve/maven/scriptDef.kt": [
28
],
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/examples/scripting/jvm-simple-script/script/src/org/jetbrains/kotlin/script/examples/jvm/simple/scriptDef.kt": [
16
],
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/stdlib/js/src/org.w3c/org.khronos.webgl.kt": [
69,
75,
81,
87,
93,
99,
105,
111
],
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/stdlib/js/src/org.w3c/org.w3c.dom.kt": [
280,
335,
341,
356,
543,
581,
719
],
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/stdlib/js/src/org.w3c/org.w3c.dom.svg.kt": [
368,
371,
377,
383,
389,
395,
401,
436,
540,
587,
615,
621,
661,
667,
735,
779,
782,
785,
814,
817
],
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/stdlib/js/src/org.w3c/org.w3c.xhr.kt": [
39
],
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/tools/binary-compatibility-validator/src/test/kotlin/cases/companions/companions.kt": [
13,
47,
80
],
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/tools/binary-compatibility-validator/src/test/kotlin/cases/nestedClasses/publicAbstractClass.kt": [
3
],
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/tools/kotlin-maven-plugin-test/src/it/test-dagger-maven-example/src/main/kotlin/PumpModule.kt": [
7
],
"kotlin-kotlin-project:sources/kotlin/kotlin/libraries/tools/kotlin-maven-plugin-test/src/it/test-kapt-allopen/src/main/kotlin/PumpModule.kt": [
7
],
"kotlin-kotlin-project:sources/kotlin/kotlin/plugins/sam-with-receiver/sam-with-receiver-cli/test/org/jetbrains/kotlin/samWithReceiver/AbstractSamWithReceiverScriptTest.kt": [
41
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"kotlin-ktor-project:sources/kotlin/ktor/ktor-io/common/src/io/ktor/utils/io/charsets/Encoding.kt": [
20,
90
],
"kotlin-ktor-project:sources/kotlin/ktor/ktor-utils/common/src/io/ktor/util/internal/LockFreeLinkedList.kt": [
58,
114
],
"kotlin-ktor-project:sources/kotlin/ktor/ktor-utils/common/src/io/ktor/util/network/NetworkAddress.kt": [
14
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package checks

class AbstractClassShouldBeInterfaceCheckSample {

abstract class ShapeA { // Noncompliant {{Replace this abstract class with an interface, or add function implementations or state properties to the class.}}
// ^^^^^^
abstract fun getPath(): Int
abstract fun getBoundingBox(): Pair<Int,Int>
}

interface ShapeB { // Compliant, we are using an interface here
fun getPath(): Int
fun getBoundingBox(): Pair<Int,Int>
}


abstract class ShapeC { // Compliant, abstract class has function implementations
abstract fun getPath(): Int
fun getBoundingBox(): Pair<Int,Int> {
return Pair(0,0)
}
}

abstract class Foo { // Noncompliant
abstract var bar: String
abstract val baz: String
}

abstract class Bar { // compliant
var bar: String = ""
abstract val baz: String
}

abstract class A { // Noncompliant
abstract class Inner { // Noncompliant
abstract fun foo()
}
}

abstract class B { // Compliant
abstract class Inner {
fun foo() {}
}
}

abstract class C { // Noncompliant
object Inner {

}
}


abstract class D { // Compliant
companion object Inner {
fun foo() {}
}
}

interface E { // Compliant
object Inner {

}
}

abstract class F { // Compliant, at least one property in the initializer
init {
val x = 0
}
}

abstract class G { // Compliant
constructor() {
val x = 0
}
}

abstract class I { // Noncompliant
init {
}
}

abstract class J { // Compliant
constructor() {
}
}

abstract class K {} // Noncompliant
abstract class L : K() {} // compliant, extend an abstract as he may have no other choice

interface M {} // compliant
abstract class N : M {} // Noncompliant
abstract class O : K(), M {} // compliant

abstract interface ZZZ { // Compliant
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* SonarSource Kotlin
* Copyright (C) 2018-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarsource.kotlin.checks

import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtSuperTypeCallEntry
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.sonar.check.Rule
import org.sonarsource.kotlin.api.checks.AbstractCheck
import org.sonarsource.kotlin.api.frontend.KotlinFileContext

@Rule(key = "S6526")
class AbstractClassShouldBeInterfaceCheck : AbstractCheck() {

override fun visitClass(klass: KtClass, context: KotlinFileContext) {

if (!klass.isAbstract() || klass.isInterface() || klass.extendsClass()) return

val allMethods = klass.collectDescendantsOfType<KtFunction>()
val allProperties: List<KtProperty> by lazy { klass.collectDescendantsOfType<KtProperty>() }

if (allMethods.all { it.isAbstract() } && allProperties.all { it.isAbstract() }) {
context.reportIssue(
klass.nameIdentifier!!,
"Replace this abstract class with an interface, or add function implementations or state properties to the class."
)
}
}

private fun KtClass.extendsClass(): Boolean {
return superTypeListEntries.any { it is KtSuperTypeCallEntry }
}

private fun KtDeclaration.isAbstract() = hasModifier(KtTokens.ABSTRACT_KEYWORD)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* SonarSource Kotlin
* Copyright (C) 2018-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarsource.kotlin.checks

internal class AbstractClassShouldBeInterfaceCheckTest : CheckTest(AbstractClassShouldBeInterfaceCheck())
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
package org.sonarsource.kotlin.plugin

import org.sonarsource.kotlin.checks.AbstractClassShouldBeInterfaceCheck
import org.sonarsource.kotlin.checks.AllBranchesIdenticalCheck
import org.sonarsource.kotlin.checks.AnchorPrecedenceCheck
import org.sonarsource.kotlin.checks.AndroidBroadcastingCheck
Expand Down Expand Up @@ -149,6 +150,7 @@ import org.sonarsource.kotlin.checks.WebViewsFileAccessCheck
import org.sonarsource.kotlin.checks.WrongAssignmentOperatorCheck

val KOTLIN_CHECKS = listOf(
AbstractClassShouldBeInterfaceCheck::class.java,
AllBranchesIdenticalCheck::class.java,
AnchorPrecedenceCheck::class.java,
AndroidBroadcastingCheck::class.java,
Expand Down
Loading

0 comments on commit 8c052ee

Please sign in to comment.