/
ArrayPrimitive.kt
96 lines (87 loc) · 4.02 KB
/
ArrayPrimitive.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package io.gitlab.arturbosch.detekt.rules.performance
import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
/**
* Using Array<Primitive> leads to implicit boxing and performance hit. Prefer using Kotlin specialized Array
* Instances.
*
* As stated in the Kotlin [documentation](https://kotlinlang.org/docs/basic-types.html#arrays) Kotlin has
* specialized arrays to represent primitive types without boxing overhead, such as `IntArray`, `ByteArray` and so on.
*
* <noncompliant>
* fun function(array: Array<Int>) { }
*
* fun returningFunction(): Array<Double> { }
* </noncompliant>
*
* <compliant>
* fun function(array: IntArray) { }
*
* fun returningFunction(): DoubleArray { }
* </compliant>
*/
@RequiresTypeResolution
@ActiveByDefault(since = "1.2.0")
class ArrayPrimitive(config: Config = Config.empty) : Rule(config) {
override val issue = Issue(
"ArrayPrimitive",
Severity.Performance,
"Using `Array<Primitive>` leads to implicit boxing and a performance hit.",
Debt.FIVE_MINS
)
override fun visitCallExpression(expression: KtCallExpression) {
super.visitCallExpression(expression)
if (expression.calleeExpression?.text !in factoryMethodNames) return
val descriptor = expression.getResolvedCall(bindingContext)?.resultingDescriptor
if (descriptor != null && isArrayPrimitive(descriptor)) {
report(CodeSmell(issue, Entity.from(expression), issue.description))
}
}
override fun visitNamedDeclaration(declaration: KtNamedDeclaration) {
super.visitNamedDeclaration(declaration)
if (declaration is KtCallableDeclaration) {
declaration.typeReference?.let(this::reportArrayPrimitives)
declaration.receiverTypeReference?.let(this::reportArrayPrimitives)
}
}
private fun reportArrayPrimitives(typeReference: KtTypeReference) {
typeReference.collectDescendantsOfType<KtTypeReference> { isArrayPrimitive(it) }
.forEach { report(CodeSmell(issue, Entity.from(it), issue.description)) }
}
private fun isArrayPrimitive(descriptor: CallableDescriptor): Boolean {
val type = descriptor.returnType?.arguments?.singleOrNull()?.type
return descriptor.fqNameOrNull() in factoryMethodFqNames && type != null && KotlinBuiltIns.isPrimitiveType(type)
}
private fun isArrayPrimitive(it: KtTypeReference): Boolean {
if (it.text?.startsWith("Array<") == true) {
val genericTypeArguments = it.typeElement?.typeArgumentsAsTypes
return genericTypeArguments?.singleOrNull()?.let { primitiveTypes.contains(it.text) } == true
}
return false
}
companion object {
private val primitiveTypes = PrimitiveType.values().map { it.typeName.asString() }
private val factoryMethodFqNames = listOf(FqName("kotlin.arrayOf"), FqName("kotlin.emptyArray"))
private val factoryMethodNames = factoryMethodFqNames.map { it.shortName().asString() }
}
}