I'm trying to write a lint check, that visits call expressions, and report on some violation based on a generic type.
To make it more clear, let's say I have this code:
object Foo {
inline fun <reified T> bar() = T::class.java
}
And let's say that I want to write a lint check that complains when I call the bar
method with Int
as the generic type, but accept everything else.
So, with the following code, the second call to bar should trigger a warning:
object Whatever {
fun someMethod() {
val stringClass = Foo.bar<String>() // Should not complain
val intClass = Foo.bar<Int>() // Should raise a warning
}
}
How would one implement that? This is of course not the real use case, and what I'm really trying to do is have a proper detection of bar<Int>
.
So far, this is what I have:
class MyDetector : Detector(), SourceCodeScanner {
companion object Issues {
val ISSUE = Issue.create(
id = "IntBarTest",
briefDescription = "You used bar with an Int type!",
explanation = "Explanation",
category = Category.CORRECTNESS,
severity = FATAL,
implementation = Implementation(MyDetector::class.java, Scope.JAVA_FILE_SCOPE)
)
}
override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)
override fun createUastHandler(context: JavaContext): UElementHandler {
return GenericTypeHandler(context)
}
inner class GenericTypeHandler(val context: JavaContext) : UElementHandler() {
override fun visitCallExpression(node: UCallExpression) {
if (isCallExpressionAnIntBar(node)) {
context.report(ISSUE,
context.getNameLocation(node),
"Oh no, I should not use bar<Int>")
}
}
private fun isCallExpressionAnIntBar(node: UCallExpression): Boolean {
return if ("bar".equals(node.methodName) ||
"Foo" == (node.receiverType as? PsiClassReferenceType)?.resolve()?.qualifiedName) {
// We know it's the method we are looking for but now we must identify the generic
TODO("Identify the generic")
} else {
false
}
}
}
}
As you can see, there is a big TODO :-P
The answer is actually quite simple:
The UCallExpression exposes type arguments. So you simply have to do the following:
private fun isCallExpressionAnIntBar(node: UCallExpression): Boolean {
return if ("bar".equals(node.methodName) ||
"Foo" == (node.receiverType as? PsiClassReferenceType)?.resolve()?.qualifiedName) {
// We know it's the method we are looking for but now we must identify the generic
node.typeArguments.getOrNull(0)?.canonicalText == Int::class.qualifiedName
} else {
false
}
}
The reason it eluded me was that I'm developing with tests, and breakpoints in the detector. But my test code was wrong (didn't define properly dependencies and so on), and so the detector would of course not work properly: my object's type arguments was an empty array.
Once I fixed my test setup, typeArguments
properly reported the the generic type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With