Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom lint check: identify generic on expression call

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

like image 944
Redwarp Avatar asked May 12 '21 15:05

Redwarp


1 Answers

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.

like image 64
Redwarp Avatar answered Oct 31 '22 13:10

Redwarp