Skip to content

Commit 95556b3

Browse files
SONARKT-301 stop raising issues on abstract/overrided/open/interface methods (#395)
1 parent 3e82ecd commit 95556b3

File tree

7 files changed

+117
-8
lines changed

7 files changed

+117
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package checks
2+
3+
class CollectionShouldBeImmutableCheckSampleNonCompiling {
4+
5+
actual fun actualFun(list: MutableList<Int>): Unit // compliant
6+
expect fun expectFun(list: MutableList<Int>): Unit // compliant
7+
8+
fun qualifiedStrange() {
9+
val list = mutableListOf<Int>()
10+
list.(add(1))
11+
}
12+
}

kotlin-checks-test-sources/src/main/kotlin/checks/CollectionShouldBeImmutableCheckSample.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ class CollectionShouldBeImmutableCheckSample {
7878
l: MutableList<Int>, // Compliant
7979
m: MutableList<Int>, // Compliant
8080
n: MutableList<Int>, // Compliant
81+
o: MutableMap<Int,Int>, // Compliant
8182
): Unit {
8283
a.add(1)
8384
b.iterator()
@@ -95,6 +96,7 @@ class CollectionShouldBeImmutableCheckSample {
9596
l!!.doSomething()
9697
m?.doSomething()
9798
if(true){n}else{n}.doSomething()
99+
o.entries
98100
}
99101

100102

@@ -219,10 +221,32 @@ class CollectionShouldBeImmutableCheckSample {
219221
}
220222
}
221223

222-
fun id(x : AMutableCollections): AMutableCollections = x // compliant, for now don't know how to check that is subtype of MutableCollection
224+
fun id(x : AMutableCollections): AMutableCollections = x // FN, for now don't know how to check that is subtype of MutableCollection
223225

224226

225227
fun foo(configure: (MutableMap<String, Any?>) -> Unit): Unit { // compliant
226228
}
227229

230+
interface A {
231+
fun foo(list : MutableList<Int>): Unit // compliant
232+
fun bar(list : MutableList<Int>): Int { // compliant
233+
return list.reduce { acc, it -> acc + it}
234+
}
235+
}
236+
237+
abstract class B : A{
238+
override fun foo(list: MutableList<Int>) { // compliant
239+
}
240+
241+
abstract fun baz(list: MutableList<Int>): Unit // compliant
242+
243+
open fun qux(list: MutableList<Int>): Unit { // compliant
244+
}
245+
246+
}
247+
248+
}
249+
250+
private fun nonCompliantParameterOnFileLevel(list: MutableList<Int>): Int { // Noncompliant
251+
return list.reduce { acc, it -> acc + it}
228252
}

kotlin-checks-test-sources/src/main/kotlin/checks/CollectionShouldBeImmutableCheckSampleNoSemantics.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class CollectionShouldBeImmutableCheckSampleNoSemantics {
7777
l: MutableList<Int>, // Compliant
7878
m: MutableList<Int>, // Compliant
7979
n: MutableList<Int>, // Compliant
80+
o: MutableMap<Int,Int>, // Compliant
8081
): Unit {
8182
a.add(1)
8283
b.iterator()
@@ -94,6 +95,7 @@ class CollectionShouldBeImmutableCheckSampleNoSemantics {
9495
l!!.doSomething()
9596
m?.doSomething()
9697
if(true){n}else{n}.doSomething()
98+
o.entries
9799
}
98100

99101

sonar-kotlin-api/src/main/java/org/sonarsource/kotlin/api/checks/ApiExtensions.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,18 @@ fun KtNamedFunction.overrides() = modifierList?.hasModifier(KtTokens.OVERRIDE_KE
311311

312312
fun KtNamedFunction.isAbstract() = modifierList?.hasModifier(KtTokens.ABSTRACT_KEYWORD) ?: false
313313

314+
fun KtNamedFunction.isOpen(): Boolean {
315+
return modifierList?.hasModifier(KtTokens.OPEN_KEYWORD) ?: false
316+
}
317+
318+
fun KtNamedFunction.isActual(): Boolean {
319+
return modifierList?.hasModifier(KtTokens.ACTUAL_KEYWORD) ?: false
320+
}
321+
322+
fun KtNamedFunction.isExpect(): Boolean {
323+
return modifierList?.hasModifier(KtTokens.EXPECT_KEYWORD) ?: false
324+
}
325+
314326
fun KtNamedFunction.suspendModifier() = modifierList?.getModifier(KtTokens.SUSPEND_KEYWORD)
315327

316328
fun KtQualifiedExpression.resolveReferenceTarget(bindingContext: BindingContext) =

sonar-kotlin-api/src/test/java/org/sonarsource/kotlin/api/checks/ApiExtensionsKtTest.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import org.jetbrains.kotlin.psi.KtExpression
3737
import org.jetbrains.kotlin.psi.KtFile
3838
import org.jetbrains.kotlin.psi.KtFunction
3939
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
40+
import org.jetbrains.kotlin.psi.KtNamedFunction
4041
import org.jetbrains.kotlin.psi.KtPackageDirective
4142
import org.jetbrains.kotlin.psi.KtParameter
4243
import org.jetbrains.kotlin.psi.KtProperty
@@ -240,6 +241,39 @@ internal class ApiExtensionsKtTest {
240241
assertThat(PsiWhiteSpaceImpl(" ").getVariableType(BindingContext.EMPTY)).isNull()
241242
}
242243

244+
@Test
245+
fun `test Functions modifiers`(){
246+
val tree = parse(
247+
"""
248+
abstract fun abstractFun():Unit{}
249+
open fun openFun():Unit{}
250+
override fun overrideFun():Unit{}
251+
actual fun actualFun():Unit{}
252+
expect fun expectFun():Unit{}
253+
fun defaultFun():Unit{}
254+
""".trimIndent()
255+
)
256+
val textToExpression: MutableMap<String, KtNamedFunction> = TreeMap()
257+
walker(tree.psiFile) {
258+
if (it is KtNamedFunction)
259+
textToExpression[it.name!!] = it
260+
}
261+
assertThat(textToExpression["abstractFun"]!!.isAbstract()).isTrue()
262+
assertThat(textToExpression["defaultFun"]!!.isAbstract()).isFalse()
263+
264+
assertThat(textToExpression["openFun"]!!.isOpen()).isTrue()
265+
assertThat(textToExpression["defaultFun"]!!.isOpen()).isFalse()
266+
267+
assertThat(textToExpression["overrideFun"]!!.overrides()).isTrue()
268+
assertThat(textToExpression["defaultFun"]!!.overrides()).isFalse()
269+
270+
assertThat(textToExpression["actualFun"]!!.isActual()).isTrue()
271+
assertThat(textToExpression["defaultFun"]!!.isActual()).isFalse()
272+
273+
assertThat(textToExpression["expectFun"]!!.isExpect()).isTrue()
274+
assertThat(textToExpression["defaultFun"]!!.isExpect()).isFalse()
275+
}
276+
243277
@Test
244278
fun `test KtTypeReference getType with null`() {
245279
assertThat((null as KtTypeReference?).getType(BindingContext.EMPTY)).isNull()

sonar-kotlin-checks/src/main/java/org/sonarsource/kotlin/checks/CollectionShouldBeImmutableCheck.kt

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor
2424
import org.jetbrains.kotlin.js.descriptorUtils.getKotlinTypeFqName
2525
import org.jetbrains.kotlin.psi.KtBinaryExpression
2626
import org.jetbrains.kotlin.psi.KtCallExpression
27+
import org.jetbrains.kotlin.psi.KtClass
2728
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
2829
import org.jetbrains.kotlin.psi.KtElement
2930
import org.jetbrains.kotlin.psi.KtExpression
31+
import org.jetbrains.kotlin.psi.KtFile
3032
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
3133
import org.jetbrains.kotlin.psi.KtNamedDeclaration
3234
import org.jetbrains.kotlin.psi.KtNamedFunction
3335
import org.jetbrains.kotlin.psi.KtQualifiedExpression
36+
import org.jetbrains.kotlin.psi.KtReferenceExpression
3437
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
3538
import org.jetbrains.kotlin.resolve.BindingContext
3639
import org.jetbrains.kotlin.resolve.BindingContext.DECLARATION_TO_DESCRIPTOR
@@ -40,6 +43,11 @@ import org.jetbrains.kotlin.types.KotlinType
4043
import org.sonar.check.Rule
4144
import org.sonarsource.kotlin.api.checks.AbstractCheck
4245
import org.sonarsource.kotlin.api.checks.determineTypeAsString
46+
import org.sonarsource.kotlin.api.checks.isAbstract
47+
import org.sonarsource.kotlin.api.checks.isActual
48+
import org.sonarsource.kotlin.api.checks.isExpect
49+
import org.sonarsource.kotlin.api.checks.isOpen
50+
import org.sonarsource.kotlin.api.checks.overrides
4351
import org.sonarsource.kotlin.api.frontend.KotlinFileContext
4452

4553
@Rule(key = "S6524")
@@ -52,8 +60,24 @@ class CollectionShouldBeImmutableCheck : AbstractCheck() {
5260
"kotlin.collections.MutableCollection"
5361
)
5462

63+
override fun visitKtFile(file: KtFile, context: KotlinFileContext) {
64+
65+
file
66+
.collectDescendantsOfType<KtNamedFunction>(::notAnInterface) { true }
67+
.forEach { reportNamedFunction(it, context) }
68+
}
69+
70+
private fun notAnInterface(element: PsiElement): Boolean {
71+
return if (element is KtClass) {
72+
!element.isInterface()
73+
} else {
74+
true
75+
}
76+
}
77+
78+
private fun reportNamedFunction(function: KtNamedFunction, context: KotlinFileContext) {
79+
if (function.isAbstract() || function.overrides() || function.isOpen() || function.isExpect() || function.isActual()) return
5580

56-
override fun visitNamedFunction(function: KtNamedFunction, context: KotlinFileContext) {
5781
val bindingContext = context.bindingContext
5882

5983
val referencesToMutableCollections = collectReferenceToMutatedCollections(function, bindingContext)
@@ -155,11 +179,11 @@ class CollectionShouldBeImmutableCheck : AbstractCheck() {
155179
}
156180

157181
private fun KtQualifiedExpression.mutatesCollection(bindingContext: BindingContext): Boolean {
158-
return if (this.selectorExpression is KtCallExpression) {
159-
val selectorExpression = this.selectorExpression as KtCallExpression
160-
selectorExpression.mutatesCollection(bindingContext)
161-
} else {
162-
false
182+
return when(val selector = this.selectorExpression){
183+
null -> true
184+
is KtCallExpression -> selector.mutatesCollection(bindingContext)
185+
is KtReferenceExpression -> selector.mutatesCollection(bindingContext)
186+
else -> false
163187
}
164188
}
165189

sonar-kotlin-checks/src/test/java/org/sonarsource/kotlin/checks/CollectionShouldBeImmutableCheckTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@
1919
*/
2020
package org.sonarsource.kotlin.checks
2121

22-
internal class CollectionShouldBeImmutableCheckTest : CheckTestWithNoSemantics(CollectionShouldBeImmutableCheck())
22+
internal class CollectionShouldBeImmutableCheckTest : CheckTestWithNoSemantics(CollectionShouldBeImmutableCheck()),
23+
CheckTestNonCompiling by DefaultCheckTestNonCompiling(CollectionShouldBeImmutableCheck())

0 commit comments

Comments
 (0)