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