1 package com.android.apifinder
2 
3 import com.android.tools.lint.client.api.UElementHandler
4 import com.android.tools.lint.detector.api.*
5 import com.intellij.lang.jvm.JvmModifier
6 import com.intellij.psi.*
7 import org.jetbrains.uast.UCallExpression
8 import org.jetbrains.uast.UElement
9 import org.jetbrains.uast.UMethod
10 import org.jetbrains.uast.USimpleNameReferenceExpression
11 
12 @Suppress("UnstableApiUsage")
13 class ApiFinderDetector : Detector(), Detector.UastScanner {
14 
getApplicableUastTypesnull15     override fun getApplicableUastTypes(): List<Class<out UElement>>? =
16       listOf(UCallExpression::class.java, USimpleNameReferenceExpression::class.java,
17              UMethod::class.java)
18 
19     override fun createUastHandler(context: JavaContext): UElementHandler? =
20       object : UElementHandler() {
21           // Visits all methods that the module itself has defined in the source code.
22           override fun visitMethod(node: UMethod) {
23               val method = node.sourcePsi as? PsiMethod ?: return
24               visitGenericMethod(method, node, isModuleMethod = true)
25           }
26 
27           // Visits all method call expressions in the source code.
28           override fun visitCallExpression(node: UCallExpression) {
29               val method = node.resolve() ?: return
30               visitGenericMethod(method, node)
31           }
32 
33           // When Kotlin code refers to a Java `getFoo()` or `setFoo()` method with property syntax
34           // * (`obj.foo`), "foo" is represented by a [USimpleNameReferenceExpression].
35           // This ensures that we visit these method calls as well.
36           override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression) {
37               val method = node.resolve() as? PsiMethod ?: return
38               visitGenericMethod(method, node)
39           }
40 
41           private fun visitGenericMethod(
42               method: PsiMethod, node: UElement, isModuleMethod: Boolean = false
43           ) {
44               // Exclude non-public/protected calls.
45               if (!method.hasModifier(JvmModifier.PUBLIC) && !method.hasModifier(JvmModifier.PROTECTED)) {
46                   return
47               }
48               var containingClass = method.containingClass
49               while (containingClass != null) {
50                   if (!containingClass.hasModifier(JvmModifier.PUBLIC) && !containingClass.hasModifier(JvmModifier.PROTECTED)) {
51                       return
52                   }
53                   containingClass = containingClass.containingClass
54               }
55 
56               // Construct message as enclosingClassName.className.methodName(parameterNames).
57               // e.g. <com.android.server.wifi>.<WifiNetworkFactory>.<hasConnectionRequests>()
58               val className = method.containingClass!!.qualifiedName!!
59               val methodName = if (method.isConstructor) {
60                   val containingClasses = mutableListOf<PsiClass>()
61                   containingClass = method.containingClass
62                   while (containingClass != null) {
63                       containingClasses += containingClass
64                       containingClass = containingClass.containingClass
65                   }
66                   containingClasses.asReversed().joinToString(".") { it.name!! }
67               } else {
68                   method.name
69               }
70               val parameterNames = method.parameterList.parameters.map {
71                   it.type.canonicalText.replace(Regex("<.*>"), "")
72               }.joinToString(", ")
73 
74               val methodCall = "$className.$methodName($parameterNames)"
75               if (isModuleMethod) {
76                   val message = "ModuleMethod:" + methodCall
77                   context.report(ISSUE, node, context.getLocation(node), message)
78               } else {
79                   val message = "Method:" + methodCall
80                   context.report(ISSUE, context.getLocation(node), message)
81               }
82           }
83       }
84 
85     companion object {
86         @JvmField
87         val ISSUE = Issue.create(
88           id = "JavaKotlinApiUsedByModule",
89           briefDescription = "API used by Android module",
90           explanation = "Any public/protected items used by an Android module.",
91           category = Category.TESTING,
92           priority = 6,
93           severity = Severity.INFORMATIONAL,
94           implementation = Implementation(ApiFinderDetector::class.java, Scope.JAVA_FILE_SCOPE)
95         )
96     }
97 }
98 
99